开场
上周在调一个角色穿模的 bug 时,我盯着 Profiler 看了半个小时——场景里明明只有一个 MeshRenderer,但物理调试视图告诉我有 12 个碰撞体在相互挤压。问题出在哪?原来美术同学把一个带MeshCollider的子物体 Prefab 嵌套了三层,而我们在Start()里只Instantiate了一次根节点,没意识到 Unity 已经为整个层级悄悄建了一整套物理表示。
这就是引擎开发的典型陷阱:场景树和物理世界是两个独立的子系统,各自维护自己的空间数据结构、更新节奏和查询接口。大多数时候它们步调一致,但一旦节奏错了——比如在物理步之间改了transform.localScale、或者销毁了带约束的中间节点——你就会看到角色飞天、子弹穿墙这种诡异的 bug。今天我们就来拆解这两个"平行宇宙"为什么分开、又是怎么通信的。
一、核心概念:两套系统各自干什么
1.1 场景树(Scene Graph):为渲染服务
场景树是Transform层级构成的父子结构。渲染模块基于它收集所有MeshRenderer/SkinnedMeshRenderer,做视锥剔除、遮挡剔除和 LOD 选择。它关心的核心问题是:这一帧哪些东西需要画、按什么顺序画。
// 场景树的典型操作:遍历层级收集 Renderer foreach