更新自研插件

This commit is contained in:
2026-10-04 19:27:54 +08:00
parent 604612baac
commit a17800db9c
7 changed files with 275 additions and 147 deletions
+36 -28
View File
@@ -30,15 +30,24 @@
## 用法
**拖进去就行。** 插件会轮询当前编辑的场景,发现新拖入的模型实例就自动加碰撞,并且整个操作走编辑器的撤销系统 —— **Ctrl+Z 可以撤销,Ctrl+Shift+Z 可以重做**。
**拖进去就行。** 把 FBX / GLB 拖进场景,插件会弹窗问一句要不要加碰撞:
菜单里还有三个手动操作:
| 弹窗里的选择 | 结果 |
| --- | --- |
| **添加碰撞** | 给这一批模型加上碰撞体,走撤销系统 —— **Ctrl+Z 可以撤销,Ctrl+Shift+Z 可以重做** |
| **不添加** | 什么都不做,模型留在场景里 |
| 勾上**不再询问**再选 | 记住这次的选择,以后不再弹窗(想恢复询问,去 项目设置 → mesh_collider 打开 `ask_before_adding`) |
一次拖入多个模型只弹**一个**弹窗,确认后整批处理。不想被问就把 `ask_before_adding` 关掉,改由 `auto_add_collision` 决定。
菜单里还有四个手动操作:
| 菜单项 | 作用 |
| --- | --- |
| 扫描场景中的模型 | 列出场景里所有还没加碰撞的模型(`auto_add_collision` 打开时直接加) |
| 给选中模型加碰撞 | 对当前选中的模型实例执行一次 |
| 移除选中模型的碰撞 | 移除本插件加的那个碰撞体(同样可撤销) |
| 设置 | 跳到项目设置页 |
| 打开项目设置 | 跳到项目设置页 |
---
@@ -73,36 +82,34 @@ Chair_PC_04 ← 模型实例,仍然引用 res://.../chair.
## 设置
设置有两个入口,值完全同步:
设置只有**一个入口**:**项目设置 → mesh_collider**(菜单里的『打开项目设置』会直接跳过去)。分节名显示为 **Mesh Collider**,每一项的名字由 Godot 按 key 自动美化:
- **右侧停靠栏的 `MC` 标签页**(检查器那一列)—— 装了插件就有;
- **项目设置 → mesh_collider** —— 想和其他项目设置一起看时用。
```
Ask Before Adding [✓ 启用]
Auto Add Collision [ 启用]
Collision Shape [Trimesh ▾]
Collision Body [StaticBody3D ▾]
Skip Meshes with Collision [ 启用]
碰撞层 [1][2][3][4] … [32] ← Godot 原生的层勾选网格
碰撞遮罩 [1][2][3][4] … [32]
Model Extensions fbx,glb,gltf,…
```
> **关于位置。** 你希望它出现在「检查器 / 信号 / 分组」那一排。那三个标签属于 Godot **内建的检查器容器**,插件 API 没有向它注入标签页的接口,所以做不到完全并排。可达的最接近位置是**右列停靠栏**,面板会和检查器出现在同一列里。`MC` 之上悬停会显示完整名称。
>
> **关于悬浮说明。** 面板**没有**使用 `EditorInspector`,而是手写表单 —— 因为 `EditorInspector` 仅在少数列出的枚举类型上把 `hint_string` 当 tooltip,勾选框和数值框悬停时什么都不显示,做不到"每个设置项都有说明"。手写之后每个设置项的标签和控件都能带 tooltip。
- 改动**立即写入**,插件下一次扫描就读到,没有"应用"这一步;
- 保存交给编辑器(退出或保存项目时落盘)—— 唯一的例外是下面那个"不再询问";
- **没有逐项说明**:项目设置的每一行只有名字和控件,Godot 没给插件留"显示名 / 描述"的位置。每项的含义看下面这张表。
面板不使用 `EditorInspector`,而是逐项构建控件,但值仍然读写**同一批 ProjectSettings 键**,所以和项目设置页不会漂移。改动**立即写入**,插件下一次扫描就读到,没有"应用"这一步。
页面底部三个按钮:
| 按钮 | 作用 |
| --- | --- |
| 恢复默认值 | 把插件拥有的**全部**设置改回默认值 |
| 保存到 project.godot | 立即落盘(不点的话,编辑器退出或保存项目时也会写) |
| 在项目设置中打开 | 跳到项目设置页 |
> 早期版本还有一个右列停靠面板(`MC` 标签页),只是把同一批键换了种画法,现已删掉:两处 UI 意味着两处要维护的布局问题,而设置本来就该在一个地方看。顺带一提,项目设置里 `collision_layer` / `collision_mask` 用的是 Godot 原生的 32 层勾选网格,比面板里的数字输入框好用。
| 设置 | 默认 | 说明 |
| --- | --- | --- |
| `ask_before_adding` | **`true`** | 拖入模型时**弹窗询问**是否添加碰撞 |
| `auto_add_collision` | `false` | 不询问时直接添加;仅在 `ask_before_adding` 关闭后生效 |
| `always_add_without_asking` | `false` | 由弹窗的「不再询问」写入 |
| `collision_shape` | **`Trimesh`** | `Convex Hull` / `Trimesh` / `Box` / `Sphere` / `Capsule` / `Cylinder` |
| `collision_body` | `StaticBody3D` | `StaticBody3D` / `RigidBody3D` / `Area3D` / `CharacterBody3D` |
| `skip_meshes_with_collision` | `false` | 跳过已经位于物理体下的网格(模型自带碰撞时打开) |
| `collision_layer` / `collision_mask` | `1` | 碰撞层 / 掩码 |
| `model_extensions` | `fbx glb gltf dae obj blend …` | 识别为模型的后缀 |
| `skip_dirs` | `addons/`, `.godot/` | 永不扫描的目录 |
### 拖入模型时的三种行为
@@ -110,13 +117,13 @@ Chair_PC_04 ← 模型实例,仍然引用 res://.../chair.
| `ask_before_adding` | `auto_add_collision` | 拖入模型时 |
| --- | --- | --- |
| **`true`**(默认) | 忽略 | **弹窗询问**,包含「不再询问」勾选框和设置入口提示 |
| **`true`**(默认) | 忽略 | **弹窗询问**,带「不再询问」勾选框 |
| `false` | `true` | 直接添加碰撞,不问 |
| `false` | `false` | 什么都不做,只能从 项目 > 工具 手动执行 |
弹窗里的「不再询问」按你本次的选择记忆:选「添加碰撞」→ 以后直接添加;选「不添加」→ 以后不再添加也不再询问。两种情况都会把 `ask_before_adding` 关掉,想恢复询问就在 `MC` 面板里重新打开。
弹窗里的「不再询问」按你本次的选择记忆:选「添加碰撞」→ 以后直接添加;选「不添加」→ 以后不再添加也不再询问。两种情况都会把 `ask_before_adding` 关掉,想恢复询问就去 **项目设置 → mesh_collider** 把它重新打开。
一次拖入多个模型只会弹**一个**弹窗,确认后整批处理。
> 这一项是唯一会**主动落盘**的设置(`ProjectSettings.save()`),因为"不再询问"必须活过这次编辑器会话。其余设置不主动落盘,交给编辑器自己保存。
### 形状怎么选
@@ -200,9 +207,11 @@ Godot 的物理按"1 单位 = 1 米"调参。如果模型导入后只有几厘
## 已知取舍
- **检测靠轮询**(0.4 秒一次)。Godot 没有暴露"模型被拖进来"的信号,这是唯一可靠的做法。
- **检测靠轮询**(0.4 秒一次)。Godot 没有暴露"模型被拖进来"的信号 —— 编辑器自己在内部处理这次 drop,不通知任何人 —— 所以只能盯着场景。为了不重蹈"每帧扫全场景"的覆辙,轮询有两道闸:先用 `SceneTree.get_node_count()`(O(1))判断整棵树有没有增删,**没变就直接跳过扫描**;变了也只扫当前编辑的场景。编辑器空转时这个定时器不做任何事。
- **基线不等于"新拖入"**。切换场景时,场景里已有的模型会被记进"已知"集合,不弹窗 —— 否则每开一个场景都会被问一遍。代价是:在场景刚打开后的 0.4 秒内拖入的模型会被当成基线,不弹窗(用菜单手动处理即可)。
- **撤销/重做会当成新拖入**。Ctrl+Z 删掉模型再 Ctrl+Shift+Z 放回来,节点实例 id 变了,插件会再问一次。功能上没错,只是多问一句。
- **变换在注入前一次性快照**。所有网格的世界变换在**模型确实处于场景树中**时解析好,再交给构建函数。这有两个原因:刚体是**故意游离**的(由撤销系统负责挂载),而 `global_transform` 在树外会打印 `Condition "!is_inside_tree()" is true` 并返回单位阵;更重要的是,依赖"读取瞬间节点仍在树里"会让碰撞在边界情况下静默烘焙到原点。快照把这件事变成确定的,缺了变换的网格会被跳过而不是猜一个。
- **已验证的 API 兼容性**:本插件只使用在 Godot 4.7.2 上实测过的 API。踩过的坑包括 `MeshInstance3D.skeleton` 是 `NodePath` 而不是 `Skeleton3D`、`ClassDB.can_instantiate()` 才是判断节点能否容纳子节点的正确方法、以及 GDScript 不允许 `const` 字典的值引用其他常量(所以库里的枚举默认值写成字面量索引)。
- **已验证的 API 兼容性**:本插件只使用在 Godot 4.7.2 上实测过的 API。踩过的坑包括 `MeshInstance3D.skeleton` 是 `NodePath` 而不是 `Skeleton3D`、`ClassDB.can_instantiate()` 才是判断节点能否容纳子节点的正确方法、GDScript 不允许 `const` 字典的值引用其他常量(所以库里的枚举默认值写成字面量索引)、以及 `AcceptDialog.add_child()` 会把子控件**铺满**内容区而不是排在正文下面(所以弹窗的正文和勾选框要自己装进一个 `VBoxContainer`)。
---
@@ -212,10 +221,9 @@ Godot 的物理按"1 单位 = 1 米"调参。如果模型导入后只有几厘
demo_project/
├─ addons/mesh_collider/ ← 插件本体(复制这个目录即可)
│ ├─ plugin.cfg
│ ├─ mesh_collider_plugin.gd 编辑器集成(检测 / 注入 / 撤销 / 停靠面板注册)
│ ├─ mesh_collider_plugin.gd 编辑器集成(检测 / 拖入弹窗 / 注入 / 撤销)
│ ├─ mesh_collider.gd 核心逻辑(纯逻辑,不依赖编辑器)
│ ├─ mesh_collider_settings.gd 项目设置 + 面板用的 label / tooltip 文本
│ ├─ mesh_collider_panel.gd 右侧 `MC` 标签页的设置面板
│ ├─ mesh_collider_settings.gd 项目设置 + 每项的 label / tooltip 文本
│ └─ README.md
├─ assets/models/ 测试模型(GLB + 带骨骼动画的 FBX)
└─ tests/ 辅助脚本