物理引擎每帧会在固定时间步长内执行碰撞检测,这个过程分为宽相和窄相两个阶段。宽相负责快速筛选可能相交的碰撞体对,窄相则对候选对进行精确的几何相交计算。当场景中大量使用网格碰撞体时,窄相阶段需要对三角形面片进行遍历,计算量随顶点数线性甚至超线性增长。一个包含数万三角面的静态场景模型,如果直接挂载网格碰撞体并开启凸包选项,虽然能参与动态碰撞,但凸包生成结果往往无法匹配原始形状;如果不开启凸包选项,动态刚体之间的网格碰撞体碰撞又受到引擎限制甚至直接不检测。这种不必要的精度是性能杀手。

相比之下,盒碰撞体、球碰撞体、胶囊碰撞体使用解析几何进行相交测试,计算复杂度是常数级别。一个盒碰撞体的相交判断只需要比较几个轴对齐区间,而网格碰撞体可能涉及数十次三角形与三角形测试。因此,优化碰撞体的第一步不是调整物理设置,而是重新审视场景中每个碰撞体是否过度精细。
此外,碰撞对的数量也会被层级设置放大。默认情况下,许多引擎中所有层之间都会进行碰撞检测,即使一个子弹永远不会与 UI 元素产生物理交互,物理引擎仍会为它们建立宽相候选对。这种无效检测在深场景中可能占用大量 CPU 时间。
用基础碰撞体组合替代复杂网格
很多复杂的视觉模型并不需要完全匹配的碰撞形状。玩家在高速移动或战斗时,对碰撞精度的需求往往远低于视觉精度。对于一把外形复杂的武器,可以用一个细长的盒碰撞体近似剑身,用一个球碰撞体近似护手,再把它们放在同一个游戏对象下组合。这样既保留了攻击判定范围,又避免了网格碰撞体的面片扫描。
Unity 允许在同一个游戏对象或父物体下挂载多个基础碰撞体。只要这些碰撞体没有刚体,它们会作为静态碰撞体参与检测,多个碰撞体之间不会互相产生碰撞。对于动态物体,可以把多个基础碰撞体放在子物体上,并给父物体绑定刚体,形成复合碰撞体。这样物理引擎会分别处理每个简单形状,总体开销仍然低于单个高精度网格碰撞体。
using UnityEngine;
public class CompositeColliderBuilder : MonoBehaviour
{
void BuildWeaponCollider()
{
// 剑身:细长盒碰撞体
GameObject blade = new GameObject("BladeCollider");
blade.transform.SetParent(transform, false);
blade.transform.localPosition = new Vector3(0, 0, 0.5f);
blade.transform.localRotation = Quaternion.identity;
BoxCollider bladeBox = blade.AddComponent<BoxCollider>();
bladeBox.size = new Vector3(0.1f, 0.05f, 1.0f);
// 护手:球碰撞体
GameObject guard = new GameObject("GuardCollider");
guard.transform.SetParent(transform, false);
guard.transform.localPosition = Vector3.zero;
SphereCollider guardSphere = guard.AddComponent<SphereCollider>();
guardSphere.radius = 0.12f;
}
}
上述代码通过脚本动态创建子物体并挂载基础碰撞体。如果父物体带有刚体,子碰撞体会自动组合成复合碰撞体,参与统一的物理模拟。需要注意,不要在子物体上再挂刚体,否则它们会成为独立的刚体,产生不必要的连接开销。
对于静态场景物体,更推荐的做法是在建模阶段或导入设置中生成简化碰撞体。可以在导入模型时禁用网格碰撞体的读写属性,同时启用自动生成碰撞体选项,让引擎根据网格自动生成近似碰撞体。如果自动生成效果不理想,可以在美术工具中手动制作代理网格,再通过导入设置将其指定为碰撞网格。
碰撞层级与矩阵过滤无效检测
碰撞层级是 Unity 提供的粗粒度过滤机制。打开项目设置下的物理面板,可以看到层级碰撞矩阵。矩阵中的每一个勾选表示两个层之间是否进行碰撞检测。默认所有层都勾选,这意味着任何两个碰撞体都可能产生交互。通过取消无关层的勾选,可以从宽相阶段直接排除大量碰撞对,物理引擎甚至不会为这些层生成候选对。
例如一个项目中定义了玩家、敌人、地面、子弹、UI 五个层。子弹不需要与 UI 碰撞,UI 也不必与地面碰撞,那么可以在矩阵中取消子弹与 UI、UI 与地面的勾选。调整后,每帧物理更新会跳过这些组合,宽相阶段的计算量立即下降。这种方式没有运行时开销,是性价比最高的优化手段之一。
如果碰撞矩阵无法覆盖所有需求,比如只希望某个特定碰撞体忽略某个碰撞体,可以使用 Physics.IgnoreCollision 方法。以下代码在角色释放无敌技能时,让角色碰撞体与所有敌人碰撞体互相忽略。
using UnityEngine;
public class IgnoreCollisionExample : MonoBehaviour
{
public Collider playerCollider;
public Collider[] enemyColliders;
public void EnableInvincible(float duration)
{
foreach (Collider enemy in enemyColliders)
{
Physics.IgnoreCollision(playerCollider, enemy, true);
}
Invoke("DisableInvincible", duration);
}
void DisableInvincible()
{
foreach (Collider enemy in enemyColliders)
{
Physics.IgnoreCollision(playerCollider, enemy, false);
}
}
}
需要区分的是,层级碰撞矩阵是全局规则,对所有同层碰撞体生效;而 IgnoreCollision 是实例级规则,只影响两个具体碰撞体。两者可以叠加使用。一般建议先通过层矩阵完成大部分过滤,再用代码处理少量特殊交互,这样既保持逻辑清晰,又避免在代码中维护复杂的碰撞关系。
实际优化流程与常见误区
一个常见的优化误区是盲目把所有网格碰撞体替换成盒碰撞体,结果角色掉进地面或穿过墙体。碰撞体简化必须以保证玩法可接受为前提。优化的正确流程是先通过性能分析器找到物理峰值来源,确认是窄相检测耗时还是碰撞对数量过多,再决定是简化单个碰撞体还是调整层级过滤。
对于必须保留高精度碰撞的场景,可以考虑使用复合碰撞体加凸包组合,或者使用更高效的凸包碰撞体代替凹网格。凸包碰撞体虽然不能表达凹面,但计算效率远高于凹网格,并且支持动态刚体。静态环境中的凹面结构可以用多个凸包拼合,或者用基础碰撞体处理。
另一个容易被忽略的点是刚体的插值和碰撞检测模式。如果刚体速度很快,可能需要设置碰撞检测为连续动态或连续推测,但这会增加额外开销。优化时应先减少碰撞体数量和复杂度,再考虑连续碰撞检测,否则高精度模式会放大性能问题。
最后给出一个综合案例:某 3D 动作游戏的敌方单位原本使用高精度网格碰撞体作为受击框,每帧物理耗时约 3.8ms。优化时将受击框改为头部球碰撞体、躯干胶囊碰撞体和四肢盒碰撞体组合,并在碰撞矩阵中取消敌方单位与敌方单位之间的碰撞,同时将子弹层与场景装饰层取消交互。优化后敌方单位相关物理耗时降至 2.1ms,整体固定更新耗时下降约 45%,而玩家手感没有明显变化。
碰撞体性能优化没有银弹,关键是持续量化评估。先测量,再优化,最后验证玩法稳定性,才能在不破坏游戏体验的前提下获得稳定的物理性能。