更新自研插件

This commit is contained in:
2026-10-04 18:32:58 +08:00
parent b99952d3c9
commit 604612baac
12 changed files with 219 additions and 97 deletions
@@ -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
@@ -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
@@ -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
View File
@@ -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]