碰撞过滤
在一个复杂的游戏中,并非所有物体都需要与所有其他物体发生碰撞。一个子弹应该能穿过发射它的角色,但不应该穿过敌人;一个团队的玩家之间不应该相互阻挡;一个幽灵可以穿墙,但可以捡起道具。如果让物理引擎对所有这些物体都进行一视同仁的碰撞检测和响应,不仅会造成不必要的性能浪费,更无法实现这些游戏设计所要求的特殊交互规则。**碰撞过滤(Collision Filtering)**就是这样一套机制,它允许开发者精确地定义“谁能与谁碰撞”。
原理
碰撞过滤本身不是一个物理原理,而是一个纯粹的逻辑规则系统,它在宽阶段(Broad Phase)——就开始工作。当宽阶段检测到两个物体的包围盒(AABB)重叠时,它不会立即将这对“嫌疑犯”传递给窄阶段,而是会先进行一次快速的逻辑检查:“根据过滤规则,这两个物体被允许相互碰撞吗?” 如果答案是否定的,那么这对物体就会被直接丢弃,即使它们的几何形状可能真的重叠了。
实现碰撞过滤最常见和最强大的方法是基于类别/掩码系统(Category/Mask System)。一个物理世界中的物体定了两个32位的整数:
- 类别(Category Bits):一个位掩码,用于声明“我是谁”。每个比特位代表一个类别。一个物体可以同时属于多个类别。例如:
PLAYER = 0x0001;
ENEMY = 0x0002;
BULLET = 0x0004
WORLD = 0x0008
- 掩码(Mask Bits):一个位掩码,用于声明“我想和谁碰撞”。每个比特位对应一个类别。
过滤规则
当物体 A 和物体 B 的包围盒重叠时,引擎会执行以下双向检查:
if ((A.CategoryBits & B.MaskBits) != 0 && (B.CategoryBits & A.MaskBits) != 0)
// 允许碰撞,进入下一个阶段碰撞检测
else
// 不碰撞
只有当这个条件为真时,A和B才被允许进入下一阶段的碰撞检测。这个双向检查意味着,碰撞的意愿必须是“相互的”。
例子
玩家(PLAYER):
// 0x0001
categoryBits = PLAYER;
// 0x0002 | 0x0008 = 0x000A 和敌人以及世界环境碰撞
maskBits = ENEMY | WORLD;
敌人(ENEMY):
// 0x0002
categoryBits = ENEMY;
// 0x0001 | 0x0004 | 0x0008 = 0x000D 和玩家、子弹、世界环境碰撞
maskBits = PLAYER | BULLET | WORLD;
子弹(BULLET):
// 0x0004
categoryBits = BULLET;
// 0x0002 | 0x0008 = 0x000A 和敌人、世界环境碰撞
maskBits = ENEMY | WORLD
当玩家与子弹相遇时:
PLAYER.categoryBits & BULLET.maskBits -> 0x0001 & 0x000A -> 0 (不满足)
BULLET.categoryBits & PLAYER.maskBits -> 0x0004 & 0x000A -> 0 (不满足)
结果:不发生碰撞。
当敌人与子弹相遇时:
ENEMY.categoryBits & BULLET.maskBits -> 0x0002 & 0x000A -> 0x0002 (满足)
BULLET.categoryBits & ENEMY.maskBits -> 0x0004 & 0x000D -> 0x0004 (满足)
结果:发生碰撞。