UPL7-8——碰撞检测相关
UPL7-8——碰撞检测相关
合理利用碰撞层矩阵设置
想要减小碰撞检测带来的开销,根本上就是要减少碰撞候选对的产生
所谓的 候选对 就是指,在广义检测 / 粗检测(Broadphase)阶段得到的一组可能相交的物体对
产生的候选对越多,在下一步进行 狭义检测 / 精检测(Narrowphase)的工作量就越大,CPU 压力就越大
因此,我们应该尽量避免无意义的碰撞产生,最有效直接的方式就是利用碰撞层矩阵
让无关层(相互之间不需要发生碰撞检测的层)全部互斥,这样可以大幅减少候选对的产生,从而达到优化性能的目的
利用 Physics 公共类 API 单独忽略物体碰撞
有时可能通过 碰撞层矩阵 不太好进行碰撞忽略,比如处于某两层中的物体,并不是所有都不需要碰撞检测,而只希望个别忽略
举例:
- 角色丢出的手榴弹,不要和自己身上的
Collider撞到 - 近战武器的
Collider 要忽略和角色自身的Collider的碰撞
等等
如果只是想控制个别对象之间的碰撞检测忽略,我们可以利用物理公共类中的忽略碰撞方法
1 | class Physics |
- 参数 1:碰撞体A
- 参数 2:碰撞体B
- 参数 3:是否忽略碰撞检测,传
true 就不会对两个对象进行碰撞检测,传false就会恢复检测
1 | Physics.IgnoreCollision(a, b, false); |
避免全局粗检测(Broadphase)重建
通过之前的学习我们知道,Broadphase(粗检测) 负责快速判断哪些物体 可能相交(生成候选对)
它通常用 空间分区结构(例如动态 BVH、四叉树、八叉树 等),这些结构的核心就是 AABB(Axis-Aligned Bounding Box)
每个 碰撞体(Collider) 都有一个 AABB,它的处理逻辑就是:利用所有物体的 AABB 计算出 候选对(可能相交的对象)
AABB:轴对齐包围盒的简称
原理是用一个长方体,它的六个面分别平行于 X、Y、Z 轴,用来包住某个物体,利用这种规则粗略的表示一个对象的体积范围

而粗检测重建指的就是:当物体的 位置、旋转、缩放 变化很大,或者 静态碰撞体(只挂碰撞器组件的对象) 被移动时
引擎不得不重新构建空间分区结构,以保证 AABB 数据正确,这意味着原来的 Broadphase 树、格子、排序信息会被废弃,必须重新插入或重排节点
这是一个 非常消耗性能的 CPU 操作,尤其是大场景或碰撞器很多时,因此我们要尽量避免这种情况的发生
何时会触发重建:
-
移动静态碰撞体(只挂碰撞器组件的对象)
如果你直接改
transform.position,会导致引擎强制重建整个粗检测(Broadphase) -
瞬移动态碰撞体(挂载刚体和碰撞器组件的对象)
如果你在
Update 里直接改刚体的transform,相当于“瞬移”,也会触发重建 -
缩放改变
改
Collider的尺寸,导致 AABB 改变,节点重插 -
启用/禁用 Collider
会在粗检测(Broadphase)树中添加或移除节点
-
大量物体被
Instantiate 或Destroy每次实例化、销毁碰撞体,粗检测(Broadphase)都要插入或删除节点,大量操作会触发局部甚至全局重建
因此我们应该在开发时尽量控制这些问题带来的开销
降低误配对和过度接触
-
极大或异常薄长的碰撞体会扩大 AABB,导致候选暴涨
必要时拆分为多段更紧的 碰撞体(
Collider) -
物理设置中的 接触偏移 参数不要过大
默认值一般够用,过大会导致接触过早产生,候选与接触点数增多
-
物理设置中的 背面射线检测(Queries Hit Backfaces) 和 射线检测触发器(Queries Hit Triggers)两个选项,不需要可以关闭
可以减少无用检测的开销
等等
合理使用射线检测和范围检测 API
-
用
NonAlloc(非分配)相关 API 替代普通 API在进行大批量射线和范围检测时,应优先使用
NonAlloc 相关API,
如Physics.RaycastNonAlloc,Physics.OverlapBoxNonAlloc,Physics.BoxCastNonAlloc 等
NonAlloc查询和普通查询的差别,本质上是 是否会产生新的数组(内存)分配,直接影响 CPU 性能和内存垃圾回收普通的查询,比如:
1
RaycastHit[] hits = Physics.RaycastAll(ray, 100f);
Physics.RaycastAll会返回一个新的数组对象,每次调用都会分配内存,如果频繁调用,会产生大量垃圾,造成 GC,从而带来 CPU 压力而 NonAlloc 查询,比如:
1
2RaycastHit[] hits = new RaycastHit[10];
int count = Physics.RaycastNonAlloc(ray, hits, 100f);结果会写进你传入的数组
hits里,不会再分配新内存,这种调用方式不会产生垃圾,从而不会造成 GC 压力,也就不会给 CPU 带来压力了 -
调用相关API时,合理利用最大距离、层级过滤、忽略触发器等参数
使用
Physics.Raycast等 API 时,合理利用最大距离、层级过滤、忽略触发器等参数,尽量杜绝全局检测
合理使用布娃娃系统
布娃娃系统是 Unity 自带的功能,可以为指定的骨骼自动添加刚体、碰撞器、关节组件
让角色可以倒地、摔倒、物理模拟,常用于角色死亡时尸体飞出效果
GameObject ——> 3D Object ——> Ragdoll…

-
减少布娃娃系统使用的碰撞器数量
可以在骨盆、胸部、头部和每个肢体使用各 1 个碰撞器
相当于只使用 7 个碰撞器,虽然会牺牲一些布娃娃的真实性
但是可以大大降低消耗成本 -
避免布娃娃之间的碰撞
如果允许布娃娃之间碰撞,性能会呈指数级增长
我们可以利用碰撞层矩阵,让布娃娃处于同一层级后让其不要和自己所在层进行碰撞 -
及时禁用或移除不活跃的布娃娃
当布娃娃完成它的使命后,应该及时禁用或者移除它,从而减少开销
