cocos2dx 多边形碰撞

2015年01月30日 09:54 0 点赞 0 评论 更新于 2025-11-21 13:56

今天我们要探讨的并非 cocos2dx 多边形碰撞,而是碰撞过滤。下面将详细介绍碰撞过滤的相关知识。

1. Chipmunk 对碰撞过滤的处理

碰撞过滤,简单来说,就是筛选出会发生碰撞的刚体,过滤掉不会发生碰撞的刚体,以便在后续回调中处理碰撞。例如在《AngryBird》中,小鸟和箱子碰撞后,会有小鸟羽毛飞散、死亡,箱子爆破等处理。

很多人对 Box2D 更为熟悉,为了更好地理解碰撞过滤,我们先看看 Box2D 是如何实现碰撞过滤的,再过渡到 Chipmunk。

1.1 Box2D 碰撞过滤实现机制

在 Box2D 中,通过标志位和掩码的设计来实现碰撞过滤。其中有两个标志位和一个组别索引,分别是:

  • categoryBits:类别标志位
  • maskBits:掩码标志位
  • groupIndex:组别索引

这三个属性在碰撞过滤机制中起着关键作用。

过滤规则

  • 如果两个形状材质的组别索引相同且为 0,使用类别和掩码计算规则来确定是否碰撞。
  • 如果两个形状材质的组别索引相同且为正数,则直接确定为碰撞。
  • 如果两个形状材质的组别索引相同且为负数,则直接确定为不碰撞。
  • 如果两个形状材质的组别索引不同,使用类别和掩码计算规则来确定是否碰撞。

额外规则

  • 静态刚体的形状永远不会与其他静态刚体的形状发生碰撞。
  • 同一刚体上的形状永远不会发生碰撞。
  • 可以选择性地启用或者禁止被关节约束的刚体形状之间的碰撞。

需要注意的是,组别索引的过滤筛选优先级高于类别和掩码标志位过滤筛选。

以下是一些示例代码及分析:

player1ShapeDef.filter.groupIndex = 1;
player2ShapeDef.filter.groupIndex = 1;
player3ShapeDef.filter.groupIndex = 2;
player4ShapeDef.filter.groupIndex = -3;
player5ShapeDef.filter.groupIndex = -3;
player6ShapeDef.filter.groupIndex = 0;
player7ShapeDef.filter.groupIndex = 0;

根据上述规则可知:

  • player1 与 player2 会碰撞。
  • player4 与 player5 不会碰撞。
  • player1 与 player3、player3 与 player4、player5 与 player7 等组别索引不同的形状材质,需进一步根据类别和掩码计算来确定是否碰撞。
  • player6 与 player7 组别索引相同为 0,同样要进一步根据类别和掩码计算来确定是否碰撞。

类别标志位与掩码标志位的计算

Box2D 支持 16 个类别,对于任何一种形状材质都可以设定类别标志位。通常用一个 16 进制数来表示,共 16 位。例如 0x0004,展开为 0x0000 0000 0000 0100

以下是一个具体例子:

playerShapeDef.filter.categoryBits = 0x0001;
playerShapeDef.filter.maskBits = 0x0002;
monsterShapeDef.filter.categoryBits = 0x0002;
monsterShapeDef.filter.maskBits = 0x0001;

计算规则如下:

  1. 让材质形状 A 的类别标志位与材质形状 B 的掩码标志位进行“按位与”运算,得到结果 r1。
  2. 让材质形状 B 的类别标志位与材质形状 A 的掩码标志位进行“按位与”运算,得到结果 r2。
  3. r1 与 r2 进行“逻辑与”运算,如果结果为 true,则形状材质 A 与形状材质 B 碰撞;如果为 false,则不碰撞。

根据上述规则得出结论:player 与 player 之间不会碰撞,monster 与 monster 之间也不会碰撞,但 player 与 monster 之间会发生碰撞。

1.2 Chipmunk2D 碰撞过滤实现

在 Chipmunk 中,一个 shape 具有 grouplayer 属性。下面来看 cpSpaceStep.c 中的一个检测函数 queryReject(查询否定拒绝):

static inline cpBool queryReject(cpShape *a, cpShape *b) {
return (
// BBoxes must overlap
!cpBBIntersects(a->bb, b->bb)
// Don't collide shapes attached to the same body.
|| a->body == b->body
// Don't collide objects in the same non-zero group
|| (a->group && a->group == b->group)
// Don't collide objects that don't share at least on layer.
|| !(a->layers & b->layers)
// Don't collide infinite mass objects
|| (a->body->m == INFINITY && b->body->m == INFINITY)
);
}

根据上述否定情况,总结出过滤规则如下:

  • 形状 a 与形状 b 的轴对齐包围盒如果没有发生碰撞,则不可能碰撞。
  • 如果形状 a 和形状 b 同属于同一个刚体,则不会碰撞。
  • 如果形状 a 和形状 b 在相同的非 0 组,则不会碰撞;同在 0 组,或者不相等则考虑碰撞。
  • 如果形状 a 的层和形状 b 的层的按位与运算为 0,即意味着不在一个“位面”上,则不会碰撞。
  • 如果形状 a 和 b 从属的刚体的质量无限大,则不可能碰撞。

Cocos2dX 对物理引擎进行了封装,碰撞过滤的实现与上述方式有所不同,其封装的碰撞过滤更接近 Box2D 碰撞过滤的思路。

CCPhysicsShape/CCPhysicsBody 类的重要属性

  • categoryBitmask(类别掩码):该掩码定义了刚体形状属于的类别。Chipmunk 支持 32 种类别。通过对刚体或刚体形状设定 categoryBitmaskcontactTestBitmask,将两者按位与运算,便可以指定游戏中的哪些刚体之间可以有相互作用,并在相互作用后进行后续的通知(该通知直接影响到 preSolve、postSolve、seperate 等回调是否被调用)。默认值为 0xFFFFFFFF。需要注意的是,相互作用并不等于就会产生碰撞反应,如传感器(sensor)就是一例。
  • contactTestBitmask(接触测试掩码):该掩码定义了哪些类别的刚体可以与本刚体(或刚体形状)产生相互作用。在物理空间中,每个刚体的类别掩码(categoryBitmask)会和其他刚体的接触测试掩码(contactTestBitmask)进行按位与运算,如果结果为非 0 值,便会产生一个 PhysicsContact 对象,并作为参数传入到 physics world 的代理方法内。为了性能考虑,我们只会设定我们关注的相互作用的掩码。默认值为 0x00000000
  • collisionBitmask(碰撞掩码):该掩码定义了哪些类别的刚体可以与本刚体(或刚体形状)发生碰撞。当刚体彼此接触的时候,可能会发生碰撞反应。此时该刚体的碰撞掩码(collisionBitmask)会与另外一个刚体的类别(categoryBitmask)进行按位与运算,如果结果为非 0 值,该刚体就会受到碰撞影响。每个刚体都可以选择是否要受到碰撞影响。例如,可以通过设定碰撞掩码来避免碰撞计算带来的刚体速度的改变。默认值为 0xFFFFFFFF

另外需要注意的是,封装后的 CCPhysicsShape 和 CCPhysicsBody 的 group 属性和 Chipmunk2D 的 group 对过滤规则的影响不同。在 Cocos2DX 封装之下的 group,采取了和 Box2D 一样的 group 过滤规则,即:

  • 如果两个形状材质的组别索引相同为正数,则直接确定为碰撞。
  • 如果两个形状材质的组别索引相同为负数,则直接确定为不碰撞。

组别索引的过滤筛选要比掩码过滤筛选具有更高的优先级。之前有人以为这是官方的一个 bug,并提过一个 Issue 给官方团队,见 https://github.com/cocos2d/cocos2d-x/pull/6148。官方解释是对物理引擎的封装要隐藏掉具体使用哪个引擎的细节,更关心友好的 API、性能和功能性,同时也要对有 SpriteKit 开发经验的开发者更友好。

1.3 简单示例与思考

关于在 Cocos2DX v3.x 里面如何理解 Chipmunk2D 的碰撞过滤,可以参考一个简单的 demo。思考以下问题:为什么 ball1 与 ball2 不碰撞,box1 与 ball1、ball2 不碰撞,box2 与 ball1、ball2 碰撞?改变它们的 group 会怎样?对它们的一些掩码重新赋值会怎样?朋友们可以尝试设定不同的掩码来观察,以便更好地理解其中的规则。

欢迎朋友们关注这个基础概念 demo 的项目,在学习过程中的测试 demo 可以提交个 pull request 过来,一起丰富这个项目。