游戏中的“穿透”通常有两种不同的问题:
物体在一次更新中直接越过障碍物,碰撞根本没有被检测到;或者碰撞已经被检测到,但物体仍然陷进了障碍物,甚至被挤到另一侧。
物体高速穿墙
物理引擎通常按固定时间间隔更新,而不是在每一个真实时间点都计算碰撞。
采用离散碰撞检测时,可以将问题理解为:物理引擎检查更新时刻的碰撞形状,却没有充分检查两次更新之间经过的路径。
上一次物理更新 下一次物理更新
● ──────────│墙│────────── ●
检查时没重叠 检查时也没重叠
虽然运动路径经过墙,但两个检查时刻都没有重叠,于是碰撞可能被漏掉。这通常叫 Tunneling,隧穿。
真正需要关注的是相对运动速度。两个物体迎面运动时,即使各自速度不算特别高,相对速度也可能很高。
旋转同样可能造成问题:长杆快速旋转时,杆的末端可以在一个物理步内跨过很远的距离。
一个实用的判断思路是:
当“每个物理步的相对位移”接近或超过“障碍物和运动物体在运动方向上的有效尺寸”时,离散检测漏碰撞的风险会明显增大。
这只是风险判断,不是严格的安全保证。斜向移动、旋转、形状和接触位置都会影响结果。
物体陷进障碍物
这属于另一个问题:碰撞求解不够及时、不够准确,或者运动方式绕过了求解过程。
多数实时物理引擎允许出现少量重叠,然后通过接触约束、速度修正和位置修正,让物体逐渐分离。因此,轻微的嵌入不一定代表检测失效。
严重嵌入通常与这些因素有关:
- 外力或速度太大,接触约束来不及阻止运动。
- 堆叠、关节或多处接触很复杂,求解迭代次数不足。
- 相互接触的动态物体质量差距很大,求解更难稳定。
- 脚本持续强行设置位置,把物体反复推入墙中。
- 物体生成时就与场景重叠。
- 碰撞体尺寸、形状或缩放不合理。
例如,角色已经撞到墙,但脚本每帧仍然执行:
1 | transform.position += direction * speed * Time.deltaTime; |
这段代码直接改变位置,并没有要求物理系统先判断这一步能否移动。即使检测到了碰撞,下一帧脚本仍可能再次把角色推进墙内。
解决方案
| 方案 | 原理 | 适用场景 | 局限或代价 |
|---|---|---|---|
| 连续碰撞检测 CCD | 检查运动过程中的碰撞,或预测接触 | 高速刚体、弹丸 | 增加计算开销,具体模式对旋转等情况支持不同 |
| 缩短物理步长 | 提高物理更新频率,减小每步位移 | 整体物理模拟 | CPU 开销增加,极高速物体仍可能穿透 |
| 增加子步 | 将一次运动拆成多次,每次都检测碰撞 | 快速角色、机关、复杂运动 | 子步越多,开销越大 |
| 射线检测 | 检查当前位置到预计下一位置的线段 | 小子弹、瞬时射击 | 射线没有厚度,不适合直接代表大物体 |
| 形状扫掠 | 用球、胶囊或盒子检查运动路径 | 有体积的弹丸、角色 | 需要处理初始重叠、多次碰撞和旋转 |
| 限制速度或单步位移 | 避免一次跨过障碍物 | 普通角色、箱子、慢速机关 | 可能改变运动效果 |
| 加厚、简化碰撞体 | 增加可检测的接触范围,改善接触稳定性 | 薄墙、复杂场景模型 | 可能出现视觉模型与碰撞边界不一致 |
| 移动前检测并截断位移 | 发现障碍物后,只移动到接触位置附近 | 自定义角色、运动学对象 | 需要实现滑动、台阶和拐角处理 |
| 重叠检测与位置恢复 | 检测已有嵌入,计算分离方向并修正位置 | 生成、传送、平台挤压 | 属于事后修正,处理不好会抖动或弹出 |
| 调整碰撞求解参数 | 提高接触约束的求解精度 | 堆叠下陷、关节拉伸、持续嵌入 | 无法解决根本没检测到的碰撞 |
| 改变对象实现方式 | 用命中查询或逻辑轨迹替代高速实体 | 枪械、飞行弹丸 | 需要自行实现飞行、命中和表现逻辑 |
这里有几个容易混淆的地方。
射线和形状扫掠虽然可以替代引擎内置 CCD,但沿运动路径检测碰撞,本身也属于连续检测的思路。
如果你想找原理上不同于 CCD 的办法,主要是缩短步长、增加离散子步、限制速度、调整碰撞体,以及重叠修正。
增加子步必须包含检测。
例如把一次移动拆成十次,每次都查询碰撞才有意义;只在循环里设置十次位置,物理引擎未必会在中间进行检测。
提高求解迭代次数主要解决“碰到了却挡不稳”。
它适合箱子堆叠、复杂接触等情况,不能解决子弹直接跳过薄墙造成的漏检测。
实际选择可以按对象来定:
- 小子弹:检查前后位置之间的射线;需要飞行时间时,逐步计算轨迹并查询。
- 火球、炮弹等有体积的弹丸:使用球形扫掠,或者适合的 CCD。
- 玩家角色:移动前做胶囊查询,截断位移,再处理沿墙滑动和重叠恢复。
- 动态箱子:检查移动方式和碰撞体,再按需要调整步长、CCD 和求解参数。
- 快速挥剑、旋转刀片:采样旋转轨迹或检测扫掠体积,单纯检查中心移动路径不够。
- 传送后卡进墙里:先验证目标位置,再做必要的重叠恢复。
通常会组合使用。例如角色采用 移动前形状查询+沿墙滑动+重叠恢复;高速弹丸采用 轨迹查询+首次命中处理。