Files
agame/addons/model_localizer/README.md
T
2026-10-04 15:01:12 +08:00

10 KiB

Model Localizer

把 FBX / GLB 拖进 Godot 4.7 编辑器后,自动生成独立的本地 .tscn 场景并从网格烘焙凸包碰撞体。

针对 Godot 4.7.2 编写并实测。

生成的本地场景

由 Chair_PC_04.glb 生成的 Chair_PC_04_local.tscn,几何、材质、贴图完整。

碰撞可视化

绿色为烘焙出的三角网格碰撞体叠加显示 —— 它逐面贴合模型(连椅子脚轮的细节都覆盖到了),这是 Trimesh 模式的效果。用 tests/capture_dbg.tscn 渲染。


它做什么

往 3D 视口里拖入一个模型,插件会:

  1. 感知拖入 —— 外部文件先被移动到 res://models/(可配置),不再散落在 res:// 根目录;
  2. 生成本地场景 —— 在模型旁边写出 模型名_local.tscn,节点树完全独立,不再引用模型的导入结果;
  3. 烘焙碰撞 —— 每个网格生成一个 ConvexPolygonShape3D,挂到 StaticBody3D 下;
  4. 替换实例 —— 把场景里那个模型实例换成生成好的本地场景(可 Ctrl+Z 撤销)。

已经自带碰撞的模型(-col / -colonly 后缀,或导入设置里生成过碰撞)会被原样跳过,绝不重复添加。


安装

  1. 把整个 addons/model_localizer/ 目录复制到你项目的 addons/ 下;
  2. 打开 项目 → 项目设置 → 插件,启用 Model Localizer;
  3. 插件菜单出现在 项目 → 工具 下。

设置项在 项目设置 → model_localizer。


设置

设置 默认 说明
auto_process_scene_instances true 自动处理拖进场景的模型
auto_swap_to_local true 用生成的本地场景替换拖入的实例
auto_import_dropped_files true 把外部拖入的模型文件移进下面的目录
dropped_files_dir res://models/ 外部文件的落地目录
output_dir presets 相对路径 = 模型同目录下的子文件夹;res:// 开头 = 用该绝对目录;留空 = 与模型同目录
output_suffix _local 生成文件名后缀
add_collision true 是否烘焙碰撞
collision_shape Trimesh Convex Hull / Trimesh / Box / Sphere / Capsule / Cylinder
collision_body StaticBody3D StaticBody3D / RigidBody3D / Area3D / CharacterBody3D
force_convex_hull true 仅 Convex Hull 有效:用真实三角面构建凸包
collision_layer / collision_mask 1 碰撞层 / 掩码
skip_dirs addons/, .godot/ 永不扫描的目录

默认布局

按默认设置,以 Chair_PC_04.glb 为例,文件和生成的场景是这样摆放的:

assets/models/
├─ Chair_PC_04.glb              ← 你拖进来的模型
├─ Chair_PC_04_Textures_4.png   ← 它带来的贴图
└─ presets/
   └─ Chair_PC_04_local.tscn    ← 自动生成:静态物体子节点 + 三角网格碰撞

也就是说场景永远生成在「模型所在目录的 presets 子文件夹」里,该目录不存在时插件会自动创建。

形状怎么选

  • Trimesh(默认)—— 逐面精确,不做凸包简化。只能用在静态物体上,顶点多时开销较大。适合地形、建筑,以及任何不需要移动的模型。
  • Convex Hull —— 每个网格一个凸包。性能好,可用于会移动的物体;凹形物体会被"填平"。
  • Box / Sphere / Capsule / Cylinder —— 最省性能。这几种形状会按网格的实际位置自动偏移居中。

生成的场景根节点保持单位变换

生成的 .tscn 根节点变换固定为单位阵,拖放位置记录在场景实例上。这样做有两个原因:

  1. Godot 会对"根节点被变换"的场景弹出警告(节点配置警告!建议不要变换场景的根节点);
  2. 即使你不理这个警告,Godot 里实例自身的变换优先级高于场景内根节点的变换 —— 烘焙进根节点的位置会被静默丢弃。

所以位置信息放在它真正生效的地方:实例节点。序列化时 placement_root_transform 返回给插件,由插件负责把实例摆到正确位置。可以用 tests/root_transform_warning_test.gd 复核。


手动操作

项目 → 工具 菜单:

  • 处理选中模型 —— 对场景里选中的模型实例执行一次转换;
  • 重置记录 —— 清空本次会话的处理记录(想重新处理同一个模型时用);
  • 设置 —— 跳到项目设置页。

行为细节

  • 幂等:同一个模型在同一场景里只处理一次,不会反复生成。
  • 模型重导出自动同步:模型的 .import 变了(重新导入 / 换文件),生成场景会跟着刷新。
  • 动画安全:骨骼、Skeleton3D、AnimationPlayer、动画轨道路径都会保留并校验。实测一个 49 节点、8 个网格、2 段动画、434 条轨道的绑定模型,转换后轨道 0 条失效。
  • 碰撞几何取自网格的绑定姿态几何,按节点累积变换放置。实测每个碰撞体与对应网格的顶点包围盒完全重合(偏差 0.000000)。

关于蒙皮(绑定)模型的限制

对骨骼绑定的模型,碰撞体是按它在静止姿态下的几何生成的,而不是按运行时骨骼变形后的形状。这是静态烘焙的固有性质(游戏里常见的做法是用胶囊/凸包代替,而不是让碰撞跟随骨骼)。

需要知道的一点:某些 FBX 的绑定姿态与静止姿态互相矛盾(导出器没有保持一致)。在这类资产上,骨骼静止姿态给出的位置和渲染出来的位置对不上,没有一个统一变换能同时匹配两者。遇到这种情况,插件的碰撞体会贴合绑定姿态几何;如果你发现碰撞和看到的位置不符,换用 Convex Hull 或 Box 通常更容易得到符合直觉的结果。

需要碰撞跟随骨骼运动时,请把 collision_body 改成 RigidBody3D 或 CharacterBody3D,并自行用骨骼驱动它。

  • 贴图仍共享:生成场景通过 [ext_resource] 引用贴图,所以同一模型的多个实例共享贴图,不会把贴图复制多份。
  • 只在编辑器里运行,不进游戏包。

常见问题

生成场景很大? 网格数据(顶点、索引、UV)内嵌在 .tscn 里,这是"完全本地化"的代价。若模型本身不大而文件异常大,说明网格顶点很多,属于正常。想减小体积可以把 collision_shape 换成 Box,或把 add_collision 关掉后单独处理。

碰撞体不跟着动画动? 烘焙出来的是静态碰撞体。动画驱动的角色请把 collision_body 改成 RigidBody3D,或按需换成 CharacterBody3D 后自己接管移动。

模型已有碰撞,插件又加了一遍? 不会。检测到子树里已有 CollisionShape3D / CollisionPolygon3D 时整体跳过。如果你想重新烘焙,先删掉旧碰撞再执行一次「处理选中模型」(并先「重置记录」)。

拖动后没反应? 检查三件事:插件是否已在项目设置里启用;模型根节点是否直接挂在当前编辑场景根下;auto_process_scene_instances 是否为 true。也可以用「处理选中模型」手动触发。

Invalid array format for surface 警告? FBX 的压缩网格在编辑器里重载时会打印这个。已实测生成的 .tscn 重新加载后顶点数与索引数与原始完全一致,几何无损,可忽略。


测试

tests/ 下是随附的验证脚本(跑在本目录的 demo 工程里)。

# 1) 核心逻辑自测:72 项断言,覆盖路径识别、结构保真、碰撞、动画、序列化往返
godot --headless --path . --script tests/self_test.gd

# 2) 模拟「拖入模型」:先造出带模型实例的场景,再让编辑器加载它
godot --headless --path . --script tests/build_drop_scene.gd
godot --headless --editor --quit --path . --scene res://tests/drop_test.tscn

# 3) 校验生成的场景是否完好(网格表面 / 顶点 / 索引 / 碰撞)
godot --headless --path . --script tests/verify_local.gd -- \
  res://assets/models/Chair_PC_04_local.tscn

# 4) 复核生成的场景根节点没有变换(不触发 Godot 的根节点警告)
godot --headless --path . --script tests/root_transform_warning_test.gd

# 5) 看一眼模型(务必带 opengl3;本机 Vulkan 不可用时默认渲染器会崩)
godot --rendering-driver opengl3 --path . res://tests/capture.tscn -- \
  res://assets/models/Chair_PC_04_local.tscn shot.png

# 6) 把碰撞体以半透明绿色叠加显示
godot --rendering-driver opengl3 --path . res://tests/capture_dbg.tscn -- \
  res://assets/models/Chair_PC_04_local.tscn collision.png

# 7) 物理验证:小球落到碰撞体上(窗口模式)
godot --rendering-driver opengl3 --path . res://tests/physics_test.tscn -- \
  res://assets/models/Chair_PC_04_local.tscn

注意:步骤 5/6/7 依赖真实的场景树。headless --script 模式下 SceneTree.root 本身不在树里,global_transform 不会传播 —— 任何涉及变换传播的检查都会读到 单位阵而给出假结果(包括假通过)。所以这类验证必须走编辑器 --editor, 或带 --rendering-driver opengl3 以窗口模式运行。

实测结果

在 Chair_PC_04.glb(8 670 面)与 GruffaloMouse.fbx(18 126 面、49 节点、骨骼 + 434 条动画轨道)上:

检查项 GLB FBX
节点数(不含碰撞) 2 → 2 49 → 49
网格实例 1 → 1 8 → 8
索引数 8 670 → 8 670 18 126 → 18 126
骨骼 / 动画播放器 — 1 → 1 / 1 → 1
动画轨道失效 0 0 / 434
碰撞体与网格顶点包围盒偏差 0.000000 0.000000(8/8 个网格)
小球落到碰撞体上 PASS 见上方蒙皮限制说明
生成场景重量校验 PASS PASS

工程结构

demo_project/
├─ addons/model_localizer/     ← 插件本体(复制这个目录即可)
│  ├─ plugin.cfg
│  ├─ model_localizer_plugin.gd    编辑器集成(探测 / 生成 / 替换)
│  ├─ model_localizer_core.gd      核心逻辑(纯逻辑,可 headless 自测)
│  ├─ localizer_settings.gd        项目设置
│  └─ README.md
├─ assets/models/              测试模型(GLB + 带骨骼动画的 FBX)
├─ docs/                       截图
└─ tests/                      验证脚本

Model Localizer · 面向 Godot 4.7.2