因为之前的工作需要,所以研究了一下Unity在处理高速状态下的解决方法,首先第一步了解Unity物理碰撞的实现原理
Unity2D物理碰撞的实现原理
Unity2D的物理系统是集成了Box2D引擎
物理系统
Box2D
由于任何一个物体都有可能与其他物体发生碰撞,则包含n个物体的碰撞检测过程中,最坏的情况可能需要进行n(n-1)/2 = O(n^2)次测试,平方时间复杂度通常会降低程序的运行速度。为了减少这种情况的发生,我们把碰撞检测分为两个阶段。粗略检测检测(broad phase)和 精确测试阶段(narrow phase)。
粗略检测
使用 AABBTree 快速筛选可能碰撞的物体
Box2D 的动态树(AABB 树 - 层次包围盒树)
动态树与 AABB 树的关系
- 继承自 AABB 树:Box2D 的动态树继承自 AABB 树。这意味着动态树的每个节点都包含一个 AABB(轴对齐包围盒),并且树的结构是基于 AABB 的合并和分割来组织的。
- 节点类型:动态树中的节点分为叶子节点和中间节点。叶子节点存储用户数据(如物体的 AABB),而中间节点存储其子节点 AABB 的合并区域。
每个节点存储了一个轴对齐包围盒(AABB)。节点分为内部节点和叶子节点:
叶子节点:存储用户数据(如物体的 AABB)
内部节点:存储其子节点 AABB 的合并区域
中间节点的存在
- 中间节点的作用:动态树的每个中间节点的 AABB 是其子节点 AABB 的并集。这种结构使得中间节点的 AABB 足够大,能够完全包含其所有子节点的 AABB。
- 平衡树的结构:中间节点不仅用于存储 AABB 的合并区域,还用于保持树的平衡。通过中间节点,动态树可以高效地支持插入、删除和查询操作。
插入的过程
当A-B C-D E-F相交 GHIJ 不相交的情况下
在实际应用中,动态 AABB 树会根据对象的空间分布动态调整树的结构。如果某些对象的 AABB 相交,它们会被组织在一起,以减少碰撞检测的计算量。如果某些对象的 AABB 不相交,它们会被单独组织,以避免不必要的 AABB 合并
。
AABB
轴对齐包围盒(Axis-Aligned Bounding Box, AABB)是一种用于表示三维空间中物体的简单几何形状。AABB的主要特点是其边界与坐标轴对齐,这使得它在碰撞检测和空间分割等应用中非常高效。
精确检测
了解完了粗略检测,就需要进行精确检测了,Box2D是通过 SAT进行精确检测
SAT(Separating Axis Theorem,分离轴定理)
若两个物体没有发生碰撞,则总会存在一条直线,能将两个物体分离 。于是,我们把这条能够隔开两个物体的线称为 分离轴。
分离轴定理的分离轴,分离的是两个矩形的投影点集合。简而言之,就是指 两个投影点集合的交集是否为空集 。
高速对象被略过的本质(Broad Phase漏检)
根据我们刚刚所知的理论,其实大多数穿透都发生在Broad Phase(粗略检测)的一环,尤其是在极端高速运动的情况下。
正常情况下,如下图所示,通常正确的情况是每次物理帧更新后,小球的包裹盒正好处于人身上的碰撞体,完成碰撞。
但当 对象速度 × Time.fixedDeltaTime > AABB范围 时,碰撞检测失败。
通俗易懂的来说,两帧间的运动路径完全跳出树的检测范围,跨越多个 AABB 节点,导致Broad Phase 无法捕捉其运动轨迹。
解决方案
连续碰撞检测 (CCD)
基于扫掠的 CCD
基于扫掠的 CCD 采用撞击时间 (TOI) 算法,通过扫掠对象的前向轨迹来计算对象的潜在碰撞(采用对象的当前速度)。如果沿对象移动方向有接触,该算法会计算撞击时间并移动对象直至达到该时间。该算法可从该时间开始执行子步骤,即计算 TOI 之后的速度,然后重新扫掠,代价是需要经历更多的 CPU 周期。
按道理说,Sweep-CCD 在理论上可以完美解决Unity高速对象的碰撞漏检问题,但是实际情况也有出入。并且在Unity 官方文档指出,Continuous 和 Continuous Dynamic 模式可以 减少 高速物体穿模的问题,但并未明确说明其速度上限。不过,它提到:
“Continuous Collision Detection is not a guarantee against all missed collisions, especially at extremely high speeds.”
(连续碰撞检测 不能保证 在所有情况下都避免穿透,特别是在 极高速度 下)
许多开发者报告,当物体速度 超过物理引擎的帧步长计算能力(如 speed * Time.fixedDeltaTime > 碰撞体尺寸),Continuous 模式仍可能失效。
综上所述,在部分极端情况下,只使用Continuous是不完备的,那么还有那些其他的方法去解决这个问题呢?
- 降低 Time.fixedDeltaTime(如 0.001s),提高物理更新频率 (在Unity配置中设置)
- 手动射线检测(Raycast),在 Update 中检查两帧间的运动路径(添加射线检测)
我这里推荐在部分情况下,选择拿射线去检测,这里是我的视频和代码仓库
B站:Unity处理高速对象穿模问题
GitHub:Prevent-Passing-Through - 增加碰撞体厚度,避免物体单帧移动距离超过碰撞体范围
这些方法都是可以可以根据自己的项目动态的去改变和实现的,今天就分享到这里如果有需要讨论的可以留言or私信我