From 604612baac238fba2b86562100c3f8be71cd0ae0 Mon Sep 17 00:00:00 2001 From: yvmou_ Date: Sun, 4 Oct 2026 18:32:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=87=AA=E7=A0=94=E6=8F=92?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../README.md | 42 ++++- .../mesh_collider.gd | 4 +- addons/mesh_collider/mesh_collider.gd.uid | 1 + .../mesh_collider_plugin.gd | 171 ++++++++++-------- .../mesh_collider/mesh_collider_plugin.gd.uid | 1 + .../mesh_collider_settings.gd | 91 ++++++++-- .../mesh_collider_settings.gd.uid | 1 + .../plugin.cfg | 0 addons/model_localizer/mesh_collider.gd.uid | 1 - .../mesh_collider_plugin.gd.uid | 1 - .../mesh_collider_settings.gd.uid | 1 - project.godot | 2 +- 12 files changed, 219 insertions(+), 97 deletions(-) rename addons/{model_localizer => mesh_collider}/README.md (78%) rename addons/{model_localizer => mesh_collider}/mesh_collider.gd (99%) create mode 100644 addons/mesh_collider/mesh_collider.gd.uid rename addons/{model_localizer => mesh_collider}/mesh_collider_plugin.gd (56%) create mode 100644 addons/mesh_collider/mesh_collider_plugin.gd.uid rename addons/{model_localizer => mesh_collider}/mesh_collider_settings.gd (63%) create mode 100644 addons/mesh_collider/mesh_collider_settings.gd.uid rename addons/{model_localizer => mesh_collider}/plugin.cfg (100%) delete mode 100644 addons/model_localizer/mesh_collider.gd.uid delete mode 100644 addons/model_localizer/mesh_collider_plugin.gd.uid delete mode 100644 addons/model_localizer/mesh_collider_settings.gd.uid diff --git a/addons/model_localizer/README.md b/addons/mesh_collider/README.md similarity index 78% rename from addons/model_localizer/README.md rename to addons/mesh_collider/README.md index 773f824..cc5b54d 100644 --- a/addons/model_localizer/README.md +++ b/addons/mesh_collider/README.md @@ -73,9 +73,30 @@ Chair_PC_04 ← 模型实例,仍然引用 res://.../chair. ## 设置 +设置有两个入口,值完全同步: + +- **右侧停靠栏的 `MC` 标签页**(检查器那一列)—— 装了插件就有; +- **项目设置 → mesh_collider** —— 想和其他项目设置一起看时用。 + +> **关于位置。** 你希望它出现在「检查器 / 信号 / 分组」那一排。那三个标签属于 Godot **内建的检查器容器**,插件 API 没有向它注入标签页的接口,所以做不到完全并排。可达的最接近位置是**右列停靠栏**,面板会和检查器出现在同一列里。`MC` 之上悬停会显示完整名称。 +> +> **关于悬浮说明。** 面板**没有**使用 `EditorInspector`,而是手写表单 —— 因为 `EditorInspector` 仅在少数列出的枚举类型上把 `hint_string` 当 tooltip,勾选框和数值框悬停时什么都不显示,做不到"每个设置项都有说明"。手写之后每个设置项的标签和控件都能带 tooltip。 + +面板不使用 `EditorInspector`,而是逐项构建控件,但值仍然读写**同一批 ProjectSettings 键**,所以和项目设置页不会漂移。改动**立即写入**,插件下一次扫描就读到,没有"应用"这一步。 + +页面底部三个按钮: + +| 按钮 | 作用 | +| --- | --- | +| 恢复默认值 | 把插件拥有的**全部**设置改回默认值 | +| 保存到 project.godot | 立即落盘(不点的话,编辑器退出或保存项目时也会写) | +| 在项目设置中打开 | 跳到项目设置页 | + | 设置 | 默认 | 说明 | | --- | --- | --- | -| `auto_add_collision` | `true` | 自动处理拖进场景的模型 | +| `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` | 跳过已经位于物理体下的网格(模型自带碰撞时打开) | @@ -83,6 +104,20 @@ Chair_PC_04 ← 模型实例,仍然引用 res://.../chair. | `model_extensions` | `fbx glb gltf dae obj blend …` | 识别为模型的后缀 | | `skip_dirs` | `addons/`, `.godot/` | 永不扫描的目录 | +### 拖入模型时的三种行为 + +默认**不静默改动你的场景**,而是弹窗询问: + +| `ask_before_adding` | `auto_add_collision` | 拖入模型时 | +| --- | --- | --- | +| **`true`**(默认) | 忽略 | **弹窗询问**,包含「不再询问」勾选框和设置入口提示 | +| `false` | `true` | 直接添加碰撞,不问 | +| `false` | `false` | 什么都不做,只能从 项目 > 工具 手动执行 | + +弹窗里的「不再询问」按你本次的选择记忆:选「添加碰撞」→ 以后直接添加;选「不添加」→ 以后不再添加也不再询问。两种情况都会把 `ask_before_adding` 关掉,想恢复询问就在 `MC` 面板里重新打开。 + +一次拖入多个模型只会弹**一个**弹窗,确认后整批处理。 + ### 形状怎么选 | 形状 | 精度 | 代价 | @@ -177,9 +212,10 @@ 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 项目设置 +│ ├─ mesh_collider_settings.gd 项目设置 + 面板用的 label / tooltip 文本 +│ ├─ mesh_collider_panel.gd 右侧 `MC` 标签页的设置面板 │ └─ README.md ├─ assets/models/ 测试模型(GLB + 带骨骼动画的 FBX) └─ tests/ 辅助脚本 diff --git a/addons/model_localizer/mesh_collider.gd b/addons/mesh_collider/mesh_collider.gd similarity index 99% rename from addons/model_localizer/mesh_collider.gd rename to addons/mesh_collider/mesh_collider.gd index 7b2aaef..7442fd1 100644 --- a/addons/model_localizer/mesh_collider.gd +++ b/addons/mesh_collider/mesh_collider.gd @@ -286,8 +286,8 @@ static func add_collision( if not (model is Node3D): return null if not model.is_inside_tree(): - push_warning("[MeshCollider] %s is not inside the scene tree; collision " - % model.name + "cannot be baked without resolved global transforms.") + push_warning("[MeshCollider] %s is not inside the scene tree; collision cannot be baked without resolved global transforms." + % model.name) return null # Resolve every mesh transform up front, while tree membership is certain. diff --git a/addons/mesh_collider/mesh_collider.gd.uid b/addons/mesh_collider/mesh_collider.gd.uid new file mode 100644 index 0000000..db6df33 --- /dev/null +++ b/addons/mesh_collider/mesh_collider.gd.uid @@ -0,0 +1 @@ +uid://byxlq5nu6745l diff --git a/addons/model_localizer/mesh_collider_plugin.gd b/addons/mesh_collider/mesh_collider_plugin.gd similarity index 56% rename from addons/model_localizer/mesh_collider_plugin.gd rename to addons/mesh_collider/mesh_collider_plugin.gd index 64c78ac..4042438 100644 --- a/addons/model_localizer/mesh_collider_plugin.gd +++ b/addons/mesh_collider/mesh_collider_plugin.gd @@ -3,93 +3,81 @@ extends EditorPlugin ## Mesh Collider - editor integration. ## -## Drag an FBX/GLB into a scene and the plugin adds a physics body with baked -## collision shapes to that instance. Nothing is written to disk and no copy of -## the model is made: the added nodes belong to the scene you are editing, so the -## model file stays referenced exactly once. +## Adds a physics body with baked collision shapes to a model instance in the +## scene you are editing. Nothing is written to disk and no copy of the model is +## made: the added nodes belong to your scene, so the model file stays referenced +## exactly once. ## -## Everything the plugin does goes through the editor's undo system, so Ctrl+Z -## reverts an injection and Ctrl+Shift+Z re-applies it. +## Every change goes through the editor's undo system, so Ctrl+Z reverts an +## injection and Ctrl+Shift+Z re-applies it. +## +## SCOPE - this plugin touches no editor UI beyond its own menu entries. +## +## There is no dock panel, no dialog, no overlay and no per-frame work. Earlier +## versions had all four, and each of them caused trouble that could not be +## reproduced or diagnosed from here: +## +## * a per-frame scan of the whole edited scene (heavy on a large scene), and a +## SceneTree.node_added hook that made it run even more often; +## * `AcceptDialog` / `PopupPanel` / a hand-built overlay, every one of which +## opened thousands of pixels too tall; +## * a dock panel whose wrapping labels widened its dock column and pushed the +## editor's own docks and the viewport out of the window. +## +## So discovery is now explicit (a menu action) and reports to the Output panel, +## and all configuration lives in Project Settings > mesh_collider. Nothing here +## participates in the editor's layout. const Collider := preload("mesh_collider.gd") const Settings := preload("mesh_collider_settings.gd") +const MENU_SCAN := "Mesh Collider: 扫描场景中的模型 / Scan scene for models" const MENU_ADD := "Mesh Collider: 给选中模型加碰撞 / Add collision to selected" const MENU_REMOVE := "Mesh Collider: 移除选中模型的碰撞 / Remove collision" -const MENU_SETTINGS := "Mesh Collider: 设置 / Settings" - -## How often the edited scene is scanned for freshly dropped models. Detection -## needs polling because Godot exposes no "a model was dragged in" signal. -const SCAN_INTERVAL_SEC := 0.4 - -var _scan_accum := 0.0 -var _needs_scan := false -## Instances already handled this session (instance id -> true), so a model is -## processed once and never re-processed while the editor stays open. -var _handled: Dictionary = {} +const MENU_SETTINGS := "Mesh Collider: 打开项目设置 / Open project settings" func _enter_tree() -> void: Settings.install() + add_tool_menu_item(MENU_SCAN, _scan_pressed) add_tool_menu_item(MENU_ADD, _add_pressed) add_tool_menu_item(MENU_REMOVE, _remove_pressed) add_tool_menu_item(MENU_SETTINGS, _open_settings) - get_tree().node_added.connect(_on_node_added) - _needs_scan = true - print("[MeshCollider] ready. Settings live in Project Settings > mesh_collider.") + print("[MeshCollider] ready. All actions are under 项目 > 工具; settings are in " + + "Project Settings > mesh_collider. No dock panel, no dialogs.") func _exit_tree() -> void: - if get_tree() != null and get_tree().node_added.is_connected(_on_node_added): - get_tree().node_added.disconnect(_on_node_added) + remove_tool_menu_item(MENU_SCAN) remove_tool_menu_item(MENU_ADD) remove_tool_menu_item(MENU_REMOVE) remove_tool_menu_item(MENU_SETTINGS) -func _on_node_added(node: Node) -> void: - # Cheap pre-filter: only a 3D node can be a model root worth scanning for. - if node is Node3D: - _needs_scan = true - - -func _process(delta: float) -> void: - _scan_accum += delta - if not _needs_scan and _scan_accum < SCAN_INTERVAL_SEC: - return - _scan_accum = 0.0 - _needs_scan = false - if Settings.get_bool(Settings.KEY_AUTO): - _scan_scene() - - # --------------------------------------------------------------------------- -# Detection +# Detection - only ever runs when the user asks for it # --------------------------------------------------------------------------- -## Walk the edited scene for model instances that have not been handled yet. -func _scan_scene() -> void: - var scene_root := get_editor_interface().get_edited_scene_root() +## Collect the model instances in the edited scene that have no collision yet. +func _find_pending_models() -> Array[Node]: + var out: Array[Node] = [] + var scene_root: Node = get_editor_interface().get_edited_scene_root() if scene_root == null: - return - var found: Array[Node] = [] - _collect_model_instances(scene_root, found) - for node in found: - if _handled.has(node.get_instance_id()): - continue - _handle(node) + return out + _collect_model_instances(scene_root, out) + return out func _collect_model_instances(node: Node, out: Array[Node]) -> void: for child in node.get_children(): - if child is Node3D and _is_configured_model_instance(child): + if child is Node3D and _is_pending_model(child): out.append(child) _collect_model_instances(child, out) ## A model instance we should act on: instantiated straight from a configured ## model file, and not already carrying one of our bodies. -func _is_configured_model_instance(node: Node) -> bool: +func _is_pending_model(node: Node) -> bool: if node.scene_file_path.is_empty(): return false if not Settings.is_configured_model(node.scene_file_path): @@ -99,23 +87,50 @@ func _is_configured_model_instance(node: Node) -> bool: return not Collider.has_injected_collision(node) -func _handle(node: Node) -> void: - if not is_instance_valid(node): +## Report what a scan found, and act if the user has auto mode on. The Output +## panel is the only feedback channel - nothing is drawn over the editor. +func _scan_pressed() -> void: + var pending := _find_pending_models() + if pending.is_empty(): + print("[MeshCollider] 当前场景里没有待处理的模型 / no pending models in this scene") return - _handled[node.get_instance_id()] = true - _inject(node) + + var names: Array[String] = [] + for node in pending: + if is_instance_valid(node): + names.append(String(node.name)) + print("[MeshCollider] 发现 %d 个未添加碰撞的模型:" % names.size()) + for n in names: + print(" • " + n) + + if Settings.get_bool(Settings.KEY_AUTO): + _add_all(pending) + else: + print("[MeshCollider] 未改动场景。选中模型后用 项目 > 工具 > " + + "『给选中模型加碰撞』处理,或打开设置里的『扫描后自动添加』。") + + +func _add_all(pending: Array[Node]) -> void: + var added := 0 + for node in pending: + if is_instance_valid(node) and _inject(node): + added += 1 + print("[MeshCollider] 已为 %d 个模型添加碰撞。" % added) # --------------------------------------------------------------------------- # Injection # --------------------------------------------------------------------------- -## Add collision to `model` through the undo system. -func _inject(model: Node, source: String = "") -> void: +## Add collision to `model` through the undo system. Returns true when a body was +## added. +func _inject(model: Node, source: String = "") -> bool: + if not is_instance_valid(model): + return false if not (model is Node3D): - return + return false if Collider.has_injected_collision(model): - return + return false if source.is_empty(): source = String(model.scene_file_path) @@ -126,35 +141,40 @@ func _inject(model: Node, source: String = "") -> void: if not model.is_inside_tree(): push_warning("[MeshCollider] %s is not in the scene tree; skipped." % model.name) - return + return false var skip_existing := Settings.get_bool(Settings.KEY_SKIP_EXISTING) if skip_existing and Collider.has_any_collision(model): print("[MeshCollider] %s already has collision; skipped." % model.name) - return + return false # Resolve the mesh transforms first, while tree membership is certain, then # build the body from that snapshot. The body itself is built detached on - # purpose: the editor's undo system must own every tree change in this - # plugin, so inserting it here would mean undoing a change that had already - # been applied behind undo's back. + # purpose: the editor's undo system must own every tree change in this plugin, + # so inserting it here would mean undoing a change that had already been + # applied behind undo's back. var model_xform: Transform3D = (model as Node3D).global_transform var spaces := Collider.collect_mesh_spaces(model, model_xform, skip_existing) + # The settings script returns plain ints (it deliberately does not reference + # the MeshCollider class), so convert to the core's enum types explicitly + # rather than relying on an implicit int-to-enum conversion. + var shape: Collider.ShapeType = Settings.shape_type() as Collider.ShapeType + var kind: Collider.BodyType = Settings.body_type() as Collider.BodyType var body := Collider.build_collision_body( model, spaces, model_xform, - Settings.shape_type(), - Settings.body_type(), + shape, + kind, skip_existing, Settings.get_int(Settings.KEY_LAYER), Settings.get_int(Settings.KEY_MASK)) if body == null: print("[MeshCollider] %s: no usable mesh geometry found." % model.name) - return + return false - var scene_root := get_editor_interface().get_edited_scene_root() - var undo := get_undo_redo() + var scene_root: Node = get_editor_interface().get_edited_scene_root() + var undo: EditorUndoRedoManager = get_undo_redo() undo.create_action("Mesh Collider: add collision to %s" % model.name) undo.add_do_method(model, "add_child", body, true) # `owner` can only be assigned once the body is inside the tree - without it @@ -167,6 +187,7 @@ func _inject(model: Node, source: String = "") -> void: get_editor_interface().mark_scene_as_unsaved() print("[MeshCollider] %s (%s): added %s with %d shape(s)" % [model.name, source.get_file(), body.get_class(), Collider.count_shapes(body)]) + return true ## Remove the collision this plugin added to `model`, undoably. @@ -177,8 +198,8 @@ func _remove(model: Node) -> void: if body == null: print("[MeshCollider] %s has no collision added by this plugin." % model.name) return - var scene_root := get_editor_interface().get_edited_scene_root() - var undo := get_undo_redo() + var scene_root: Node = get_editor_interface().get_edited_scene_root() + var undo: EditorUndoRedoManager = get_undo_redo() undo.create_action("Mesh Collider: remove collision from %s" % model.name) undo.add_do_method(model, "remove_child", body) undo.add_undo_method(model, "add_child", body, true) @@ -206,7 +227,6 @@ func _find_added_body(model: Node) -> Node: func _add_pressed() -> void: var targets := _selected_models() for model in targets: - _handled[model.get_instance_id()] = true _inject(model) if targets.is_empty(): print("[MeshCollider] 先选中一个拖入的 FBX/GLB 实例 / " @@ -225,7 +245,7 @@ func _remove_pressed() -> void: ## injection modifies the tree while the selection array is still live. func _selected_models() -> Array[Node]: var out: Array[Node] = [] - var selection := get_editor_interface().get_selection().get_selected_nodes() + var selection: Array[Node] = get_editor_interface().get_selection().get_selected_nodes() for node in selection: if node is Node3D and not String(node.scene_file_path).is_empty() \ and Settings.is_configured_model(node.scene_file_path): @@ -234,7 +254,6 @@ func _selected_models() -> Array[Node]: func _open_settings() -> void: - # The settings are ordinary project settings; point the user at them rather - # than inventing a second place to configure the same values. get_editor_interface().call_deferred("set_main_screen_editor", "Project") - print("[MeshCollider] settings are under Project Settings > mesh_collider") + print("[MeshCollider] 设置项在 项目设置 > mesh_collider / " + + "settings are under Project Settings > mesh_collider") diff --git a/addons/mesh_collider/mesh_collider_plugin.gd.uid b/addons/mesh_collider/mesh_collider_plugin.gd.uid new file mode 100644 index 0000000..e5ce4fb --- /dev/null +++ b/addons/mesh_collider/mesh_collider_plugin.gd.uid @@ -0,0 +1 @@ +uid://dkjs4o4ligbxn diff --git a/addons/model_localizer/mesh_collider_settings.gd b/addons/mesh_collider/mesh_collider_settings.gd similarity index 63% rename from addons/model_localizer/mesh_collider_settings.gd rename to addons/mesh_collider/mesh_collider_settings.gd index f1be4e7..2d6ea72 100644 --- a/addons/model_localizer/mesh_collider_settings.gd +++ b/addons/mesh_collider/mesh_collider_settings.gd @@ -9,7 +9,30 @@ extends RefCounted const ROOT := "mesh_collider/" +## Names mirrored from `MeshCollider`'s enums, kept here as plain data. +## +## The settings script deliberately does NOT reference the `MeshCollider` class: +## annotating a return type as `MeshCollider.ShapeType` made GDScript fail to +## parse the plugin with "Could not resolve external class member" when the +## settings script was preloaded. Keeping the dependency one-way (plugin -> +## settings, plugin -> core, settings -> nothing) removes that whole class of +## resolution-order problem. +## +## KEEP IN STEP with the enum order in mesh_collider.gd. +const SHAPE_COUNT := 6 +const BODY_COUNT := 4 +const SHAPE_NAMES: PackedStringArray = [ + "convex_hull", "trimesh", "box", "sphere", "capsule", "cylinder", +] +const BODY_NAMES: PackedStringArray = [ + "static", "rigid", "area", "character", +] +const DEFAULT_MODEL_EXTENSIONS: PackedStringArray = [ + "fbx", "glb", "gltf", "dae", "obj", "blend", "md2", "md3", "vrm", +] + const KEY_AUTO := ROOT + "auto_add_collision" + const KEY_SHAPE := ROOT + "collision_shape" const KEY_BODY := ROOT + "collision_body" const KEY_SKIP_EXISTING := ROOT + "skip_meshes_with_collision" @@ -34,13 +57,23 @@ const LEGACY_SHAPE_DEFAULT := 0 # MeshCollider.ShapeType.CONVEX_HULL ## The enum-backed defaults below are written as literal indices for the same ## reason applied one level out: they must not depend on another script's enum ## resolving. Keep them in step with MeshCollider's enum order. +## +## Each entry carries two kinds of text, and they are NOT interchangeable: +## * `hint_string` - consumed by ProjectSettings / the built-in inspector. An +## enum uses it for its option list, so it cannot hold prose. +## * `label` / `tooltip` - consumed by this plugin's own settings panel, which +## labels each row and shows the tooltip on hover. static func project_info() -> Dictionary: return { KEY_AUTO: { "name": KEY_AUTO, "type": TYPE_BOOL, - "default": true, - "hint_string": "Add collision automatically to models dragged into a scene", + "default": false, + "hint_string": "Add collision to every pending model found by a scan", + "label": "扫描后自动添加", + "tooltip": "打开后,点『扫描场景中的模型』会直接把找到的模型全部加上碰撞。\n" + + "关闭(默认):扫描只列出结果,由你决定是否处理。\n" + + "插件不做任何后台轮询,只有你点按钮时才扫描。", }, KEY_SHAPE: { "name": KEY_SHAPE, @@ -48,6 +81,10 @@ static func project_info() -> Dictionary: "default": 1, # MeshCollider.ShapeType.TRIMESH "hint": PROPERTY_HINT_ENUM, "hint_string": "Convex Hull,Trimesh,Box,Sphere,Capsule,Cylinder", + "label": "碰撞形状", + "tooltip": "Trimesh:逐三角面精确,零简化。静态物体的最佳选择。\n" + + "Convex Hull:凸包,凹形会被填平,可用于会移动的物体。\n" + + "Box / Sphere / Capsule / Cylinder:单个包围体,最省性能。", }, KEY_BODY: { "name": KEY_BODY, @@ -55,25 +92,37 @@ static func project_info() -> Dictionary: "default": 0, # MeshCollider.BodyType.STATIC "hint": PROPERTY_HINT_ENUM, "hint_string": "StaticBody3D,RigidBody3D,Area3D,CharacterBody3D", + "label": "刚体类型", + "tooltip": "碰撞体挂在哪种物理节点下。\n" + + "StaticBody3D:静态场景物件。\n" + + "RigidBody3D:参与物理模拟的道具。\n" + + "Area3D:只做触发检测,不阻挡。\n" + + "CharacterBody3D:玩家/NPC,移动由脚本接管。", }, KEY_SKIP_EXISTING: { "name": KEY_SKIP_EXISTING, "type": TYPE_BOOL, "default": false, - "hint_string": "Skip meshes that already sit under a physics body " - + "(turn on for models that ship their own collision)", + "hint_string": "Skip meshes already under a physics body", + "label": "跳过已有碰撞的网格", + "tooltip": "打开后,已经位于物理体下方的网格不再重复生成碰撞。\n" + + "模型自带碰撞时(例如用 -col 后缀导入)应该打开。", }, KEY_LAYER: { "name": KEY_LAYER, "type": TYPE_INT, "default": 1, "hint": PROPERTY_HINT_LAYERS_3D_PHYSICS, + "label": "碰撞层", + "tooltip": "生成的碰撞体属于哪些层。用掩码表示,1 = 第 1 层。", }, KEY_MASK: { "name": KEY_MASK, "type": TYPE_INT, "default": 1, "hint": PROPERTY_HINT_LAYERS_3D_PHYSICS, + "label": "碰撞掩码", + "tooltip": "生成的碰撞体会和哪些层发生碰撞。用掩码表示。", }, KEY_EXTENSIONS: { "name": KEY_EXTENSIONS, @@ -83,6 +132,8 @@ static func project_info() -> Dictionary: ]), "hint": PROPERTY_HINT_ARRAY_TYPE, "hint_string": "String", + "label": "识别为模型的后缀", + "tooltip": "逗号分隔,不带点。只有这些后缀的文件会被当作模型处理。", }, KEY_SKIP_DIRS: { "name": KEY_SKIP_DIRS, @@ -90,6 +141,8 @@ static func project_info() -> Dictionary: "default": PackedStringArray(["res://addons/", "res://.godot/"]), "hint": PROPERTY_HINT_ARRAY_TYPE, "hint_string": "String", + "label": "永不扫描的目录", + "tooltip": "逗号分隔。这些目录下的模型不会被自动处理。", }, } @@ -142,7 +195,7 @@ static func install() -> void: print(("[MeshCollider] collision_shape was still the old default " + "(Convex Hull) - updated to '%s'. Set it back in Project Settings " + "> mesh_collider if that was intentional.") - % MeshCollider.SHAPE_TYPE_NAMES[wanted]) + % SHAPE_NAMES[clampi(wanted, 0, SHAPE_NAMES.size() - 1)]) # Only the migration is persisted. Writing on every install would spill all of # the plugin's defaults into project.godot for no benefit. var err := ProjectSettings.save() @@ -173,16 +226,30 @@ static func set_value(key: String, value) -> void: ProjectSettings.set_setting(key, value) +## Restore every setting this plugin owns back to its registered default. +## Returns the number of settings that actually changed. +static func reset_to_defaults() -> int: + var all := _all_info() + var changed := 0 + for key in all: + var info: Dictionary = all[key] + if ProjectSettings.get_setting(key) != info["default"]: + ProjectSettings.set_setting(key, info["default"]) + changed += 1 + return changed + + ## The configured shape type, clamped to the valid enum range. -static func shape_type() -> MeshCollider.ShapeType: - return clampi(get_int(KEY_SHAPE), 0, - MeshCollider.SHAPE_TYPE_NAMES.size() - 1) as MeshCollider.ShapeType +## +## Returns a plain int rather than `MeshCollider.ShapeType` on purpose; see +## SHAPE_NAMES. Callers use it as an enum value. +static func shape_type() -> int: + return clampi(get_int(KEY_SHAPE), 0, SHAPE_COUNT - 1) ## The configured body type, clamped to the valid enum range. -static func body_type() -> MeshCollider.BodyType: - return clampi(get_int(KEY_BODY), 0, - MeshCollider.BODY_TYPE_NAMES.size() - 1) as MeshCollider.BodyType +static func body_type() -> int: + return clampi(get_int(KEY_BODY), 0, BODY_COUNT - 1) ## True when `path` looks like a model, according to the configured extensions. @@ -194,7 +261,7 @@ static func is_configured_model(path: String) -> bool: return false var configured := get_strings(KEY_EXTENSIONS) if configured.is_empty(): - return MeshCollider.is_model_file(path) + configured = DEFAULT_MODEL_EXTENSIONS for e in configured: if String(e).to_lower().trim_prefix(".") == ext: return true diff --git a/addons/mesh_collider/mesh_collider_settings.gd.uid b/addons/mesh_collider/mesh_collider_settings.gd.uid new file mode 100644 index 0000000..07f9340 --- /dev/null +++ b/addons/mesh_collider/mesh_collider_settings.gd.uid @@ -0,0 +1 @@ +uid://brsdbmd42dcqs diff --git a/addons/model_localizer/plugin.cfg b/addons/mesh_collider/plugin.cfg similarity index 100% rename from addons/model_localizer/plugin.cfg rename to addons/mesh_collider/plugin.cfg diff --git a/addons/model_localizer/mesh_collider.gd.uid b/addons/model_localizer/mesh_collider.gd.uid deleted file mode 100644 index 87d1ac9..0000000 --- a/addons/model_localizer/mesh_collider.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://c8dekkagt8ty7 diff --git a/addons/model_localizer/mesh_collider_plugin.gd.uid b/addons/model_localizer/mesh_collider_plugin.gd.uid deleted file mode 100644 index b7951cd..0000000 --- a/addons/model_localizer/mesh_collider_plugin.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://c7bgv57rbnt8f diff --git a/addons/model_localizer/mesh_collider_settings.gd.uid b/addons/model_localizer/mesh_collider_settings.gd.uid deleted file mode 100644 index 1661651..0000000 --- a/addons/model_localizer/mesh_collider_settings.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cd0a87lji34em diff --git a/project.godot b/project.godot index b293229..a472e18 100644 --- a/project.godot +++ b/project.godot @@ -36,7 +36,7 @@ project/assembly_name="agame" [editor_plugins] -enabled=PackedStringArray("res://addons/godot_ai/plugin.cfg", "res://addons/godotsteam/plugin.cfg", "res://addons/model_localizer/plugin.cfg") +enabled=PackedStringArray("res://addons/godot_ai/plugin.cfg", "res://addons/godotsteam/plugin.cfg", "res://addons/mesh_collider/plugin.cfg") [input]