一个物体可以具有零个或多个形状。具有多个形状的物体有时称为复合体。
形状包含以下内容:
这些将在以下章节中详细描述。
形状通过初始化形状定义和形状原型来创建。这些将传递给每种形状类型特定的创建函数。
b2ShapeDef shapeDef = b2DefaultShapeDef();
shapeDef.density = 10.0f;
shapeDef.friction = 0.7f;
b2Polygon box = b2MakeBox(0.5f, 1.0f);
b2ShapeId myShapeId = b2CreatePolygonShape(myBodyId, &shapeDef, &box);
这段代码创建了一个多边形并将其附加到物体上。你不需要存储形状ID,因为在父物体销毁时形状会自动销毁。但是,如果你计划稍后更改形状的属性,可能需要存储形状ID。
你可以在一个物体上创建多个形状。它们都可以为物体的质量做出贡献。这些形状之间不会相互碰撞,并且可以重叠。
你可以销毁物体上的形状。你可以通过这种方式模拟可破碎的物体。否则,你可以让物体销毁时自动销毁附加的形状。
b2DestroyShape(myShapeId);
材料属性如密度、摩擦力和恢复系数与形状关联,而不是与物体关联。由于你可以将多个形状附加到一个物体上,这允许更多的设置。例如,你可以制造一个后部较重的汽车。
形状的密度用于计算父物体的质量属性。密度可以是零或正值。通常应为所有形状使用相似的密度,这将提高堆叠的稳定性。
当你设置密度时,物体的质量不会立即调整。你必须调用 b2Body_ApplyMassFromShapes()
才能让这一变化生效。一般情况下,应在 b2ShapeDef
中设定形状密度,并避免在以后修改,因为这可能代价昂贵,特别是在复合体上。
b2Shape_SetDensity(myShapeId, 5.0f);
b2Body_ApplyMassFromShapes(myBodyId);
摩擦力用于使物体相互滑动时更加真实。Box2D支持静摩擦和动摩擦,但使用相同的参数。Box2D尝试准确模拟摩擦力,其强度与法向力成比例。这称为库仑摩擦。摩擦力参数通常设置在0到1之间,但可以是任何非负值。摩擦力为0表示没有摩擦,而值为1表示摩擦力很强。当两个形状之间计算摩擦力时,Box2D必须结合两个父形状的摩擦力参数。这通过几何平均数来完成:
float mixedFriction = sqrtf(b2Shape_GetFriction(shapeIdA) * b2Shape_GetFriction(shapeIdB));
如果一个形状的摩擦力为零,则混合摩擦力也为零。
恢复系数用于使物体弹跳。恢复系数的值通常设置在0到1之间。考虑将球掉在桌子上。值为零表示球不会弹跳,这称为非弹性碰撞。值为一表示球的速度将完全反射,这称为完全弹性碰撞。恢复系数的结合使用如下公式:
float mixedRestitution = b2MaxFloat(b2Shape_GetRestitution(shapeIdA), b2Shape_GetRestitution(shapeIdB));
恢复系数以这种方式组合,这样你可以有一个弹性球而不必让地面也弹性。
当一个形状产生多个接触时,恢复系数是近似模拟的。这是因为Box2D使用顺序求解器。当碰撞速度很小时,Box2D还会使用非弹性碰撞。这是为了防止抖动。请参见 b2WorldDef::restitutionThreshold
。
碰撞过滤使你可以有效地防止形状之间的碰撞。例如,假设你创建了一个骑自行车的角色。你希望自行车与地形碰撞,角色也与地形碰撞,但不希望角色与自行车碰撞(因为它们必须重叠)。Box2D通过使用类别、掩码和组支持这种碰撞过滤。
Box2D支持32个碰撞类别。对于每个形状,你可以指定其所属的类别。你还可以指定此形状可以与哪些类别碰撞。例如,你可以指定在多人游戏中玩家之间不发生碰撞。建议识别出所有应该碰撞的情况,而不是识别不应该碰撞的情况。这样你就不会遇到使用双重否定的情况。你可以使用掩码位指定可以碰撞的对象。例如:
enum MyCategories
{
PLAYER = 0x00000002,
MONSTER = 0x00000004,
};
b2ShapeDef playerShapeDef = b2DefaultShapeDef();
b2ShapeDef monsterShapeDef = b2DefaultShapeDef();
playerShapeDef.filter.categoryBits = PLAYER;
monsterShapeDef.filter.categoryBits = MONSTER;
// 玩家与怪物碰撞,但不与其他玩家碰撞
playerShapeDef.filter.maskBits = MONSTER;
// 怪物与玩家和其他怪物碰撞
monsterShapeDef.filter.maskBits = PLAYER | MONSTER;
碰撞发生的规则如下:
uint32_t catA = shapeA.filter.categoryBits;
uint32_t maskA = shapeA.filter.maskBits;
uint32_t catB = shapeB.filter.categoryBits;
uint32_t maskB = shapeB.filter.maskBits;
if ((catA & maskB) != 0 && (catB & maskA) != 0)
{
// 形状可以碰撞
}
另一种过滤功能是碰撞组。碰撞组允许你指定组索引。具有相同组索引的所有形状要么总是碰撞(正索引),要么永不碰撞(负索引)。组索引通常用于某种相关的事物,如自行车的各个部分。在以下示例中,shape1
和 shape2
总是碰撞,而 shape3
和 shape4
从不碰撞。
shape1Def.filter.groupIndex = 2;
shape2Def.filter.groupIndex = 2;
shape3Def.filter.groupIndex = -8;
shape4Def.filter.groupIndex = -8;
不同组索引的形状之间的碰撞按类别和掩码位进行过滤。如果两个形状具有相同的非零组索引,则这将覆盖类别和掩码。碰撞组的优先级高于类别和掩码。
请注意,Box2D中自动发生的其他碰撞过滤。以下是列表:
有时你可能需要在形状已创建后更改碰撞过滤。你可以使用 b2Shape_GetFilter()
和 b2Shape_SetFilter()
在现有形状上获取和设置 b2Filter
结构。更改过滤是昂贵的操作,因为它会导致接触点被销毁。
链形状提供了一种有效的方式将许多线段连接在一起,以构建静态游戏世界。链形状自动消除幽灵碰撞,并提供单面碰撞。
如果你不关心幽灵碰撞,你可以创建一堆双面段形状。性能是相似的。
使用链形状的最简单方法是创建环路。只需提供一个顶点数组。
b2Vec2 points[4] = {
{1.7f, 0.0f},
{1.0f, 0.25f},
{0.0f, 0.0f},
{-1.7f, 0.4f}};
b2ChainDef chainDef = b2DefaultChainDef();
chainDef.points = points;
chainDef.count = 4;
b2ChainId myChainId = b2CreateChain(myBodyId, &chainDef);
// 稍后...
b2DestroyChain(myChainId);
// 为安全起见,将ID置空
myChainId = b2_nullChainId;
段的法线方向取决于绕行顺序。逆
时针绕行顺序将法线定向为向外,而顺时针绕行顺序将法线定向为向内。
你可能有一个滚动的游戏世界,并希望将几条链连接在一起。你可以使用幽灵顶点连接链。要做到这一点,必须使每条链的前三个或后三个点重叠。详情请参见样本 ChainLink
。
链形状的自相交是不支持的。它可能有效,也可能无效。防止幽灵碰撞的代码假设链没有自相交。此外,非常接近的顶点可能会导致问题。确保所有点之间的距离大约超过一厘米。
每条链中的每个段都作为一个 b2SmoothSegment
形状在物体上创建。如果你有一个平滑段形状的形状ID,你可以获得其所属的链ID。如果该形状不是平滑段,则返回 b2_nullChainId
。
b2ChainId chainId = b2Shape_GetParentChain(myShapeId);
你不能直接创建平滑段形状。
有时游戏逻辑需要知道两个形状何时重叠,但不应该产生碰撞响应。这可以通过使用传感器来完成。传感器是一种检测重叠但不产生响应的形状。
你可以将任何形状标记为传感器。传感器可以是静态、运动或动态的。请记住,一个物体可以具有多个形状,并且可以混合使用传感器和实心形状。此外,传感器仅在至少一个物体是动态的情况下形成接触,因此你不会获得运动与运动、运动与静态或静态与静态之间的传感器重叠检测。最后,传感器不会检测其他传感器的重叠。
传感器重叠检测是通过事件实现的,事件将在下文中描述。