更新自研插件
This commit is contained in:
@@ -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/ 辅助脚本
|
||||
@@ -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.
|
||||
@@ -0,0 +1 @@
|
||||
uid://byxlq5nu6745l
|
||||
+95
-76
@@ -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")
|
||||
@@ -0,0 +1 @@
|
||||
uid://dkjs4o4ligbxn
|
||||
+79
-12
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
uid://brsdbmd42dcqs
|
||||
@@ -1 +0,0 @@
|
||||
uid://c8dekkagt8ty7
|
||||
@@ -1 +0,0 @@
|
||||
uid://c7bgv57rbnt8f
|
||||
@@ -1 +0,0 @@
|
||||
uid://cd0a87lji34em
|
||||
+1
-1
@@ -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]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user