Box2D C++教程18-碰撞过滤器
本文出自http://www.wenbanana.com稻草人博客,欢迎访问!
备注:由于本人最近在学习box2d引擎,而中文资料中好的文章比较少,我就在google上找了一些英文资料。于是,发现了网上的Box2Dtutorial系列文章,觉得写得挺好的,于是做了一些翻译和大家分享分享。由于这是我第一次翻译技术型文章,翻译不当的地方还请各位多理解。
下面我给出原文网址:http://www.iforce2d.net/b2dtut/user-data,本系列翻译文章仅用于学习交流之用,请勿用于商业用途。欢迎各位转载!
碰撞过滤器
到目前为止我们所创作的场景,所有的fixtures都可以和其他的fixtures发生碰撞。这是默认的行为,但是我们还可以设置“碰撞过滤器”来提供更好的碰撞控制。碰撞过滤器是在创建fixture时设置一些标志。这些标志是:
.categoryBits
.maskBits
.groupIndex
每一个’bits’值都是一个16位整数,所以你可以有16个不同类别的碰撞。There is a little more to it than that though,因为这些值的组合可以决定两个Fixtures是否会碰撞。Group index用来覆盖category/mask来设置一组给定的fixtures。
Category and maskbits
这个categoryBits标志可以认为fixture在说“我是……….”,maskBits就想是在说“我将会和…发生碰撞”。重要的一点是:这些情况下两个fixtures都得满足,这是为了碰撞被许可。
例如,你有两个categories、猫和老鼠。猫可能会说“我是猫,我将会和其他的猫和老鼠碰撞”,但老鼠就不会很愿意和猫碰撞,可能会说到“我是老鼠,我将会和其他的老鼠碰撞”。根据这些规则,一对猫会碰撞,并且一对老鼠也将会碰撞,但是老鼠和猫的组合就不会碰撞(即使猫对此不在意)。特别说明的是,程序是通过一个位和这两个标志来检查的,这点能帮助我们理解下面的代码:
bool collide=
(filterA.maskBits& filterB.categoryBits)!=0&&
(filterA.categoryBits& filterB.maskBits)!=0;
categoryBits默认的值是0x0001,maskBits默认的值是0xFFFF,或者用另外一种说法就是,每一个fixture都会说“我是一个物体,我会和所有其他的物体碰撞”,同时,由于所有的fixtures都有相同的规则,我们发现所有的fixture真的是会和其他的物体都发生碰撞。
让我们做一个实验,通过改变这些标志,看看他们如何被使用。我们需要一个有许多物体的情景,同时我们想看到所有的物体撞在一起但没有飞出屏幕,所以我想用drawing your ownobjects这一话题的场景,这个场景中有很多的圆形物体在围栏内:
到现在,你应该对设置项这样的场景应当很熟悉,我在这里就不在多叙述了。如果你想用自己的场景,关键的一点是这个场景要有很多大小不同并且有颜色的物体。
在这个例子中,我们想为每一个实体设置不同的尺寸和颜色,并把颜色作为持续测试的时间。我们在构造函数中已经有一个半径的参数,因此添加必要的代码来设置颜色参数,并且在渲染时去使用它。我们也会在每一个实体中添加categoryBits和maskBits参数:
//Ball class member variable
b2Color m_color;
//edit Ball constructor
Ball(b2World* world, float radius, b2Color color, uint16 categoryBits, uint16 maskBits) {
m_color = color;
...
myFixtureDef.filter.categoryBits = categoryBits;
myFixtureDef.filter.maskBits = maskBits;
//in Ball::render
glColor3f(m_color.r, m_color.g, m_color.b);
好吧,那我们用尺寸和颜色来做什么呢?一个自上而下有许多不同categories的车辆的战斗场景能很好地解释这一点,这个场景中,只有一部分物体会发生碰撞,例如:地面上的车辆不能和飞行器发生碰撞。让我们创建一个场景,有船、有飞行器,并且每一个实体都有一个友好或敌对状态。我们将使用大小来直观的判断一个实体时船还是飞机,并用颜色来显示它的友好/敌对状态。让我们想场景中添加大量的实体,先让标志为空。
//in FooTest constructor
b2Color red(1,0,0);
b2Color green(0,1,0);
//large and green are friendly ships
for (int i = 0; i < 3; i++)
balls.push_back( new Ball(m_world, 3, green, 0, 0 ) );
//large and red are enemy ships
for (int i = 0; i < 3; i++)
balls.push_back( new Ball(m_world, 3, red, 0, 0 ) );
//small and green are friendly aircraft
for (int i = 0; i < 3; i++)
balls.push_back( new Ball(m_world, 1, green, 0, 0 ) );
//small and red are enemy aircraft
for (int i = 0; i < 3; i++)
balls.push_back( new Ball(m_world, 1, red, 0, 0 ) );
我将会关闭重力来更好地进行观察。你运行的结果应该像下面这样:
很明显所有的物体都没有发生碰撞。这是因为你在fixture的maskBits设置为0造成的,它将不会再碰撞。然而,这不是我们想要的效果。让我们来看看如何得到的不是仅仅简单的“全或无”的控制。我们会在可以发生碰撞的物体间设置如下的规则:
1. 所有的车辆可以和边界碰撞
2. 船和飞行器不碰撞
3. 所有的船可以和其他船碰撞
4. 飞行器会和敌对的飞行器碰撞,而不会和同伴的飞行器碰撞
这看起来相当的复杂,如果我们从每一个category下手,那么就不会这么的复杂了。首先让我们给每一个category定义一些位标志:
enum _entityCategory {
BOUNDARY = 0x0001,
FRIENDLY_SHIP = 0x0002,
ENEMY_SHIP = 0x0004,
FRIENDLY_AIRCRAFT = 0x0008,
ENEMY_AIRCRAFT = 0x0010,
};
由于fixture默认的category是1,这样的安排意味着我们不需要做特别的边界fixture。至于其他的,可以考虑每一种车辆的类型,如“我是….、我和…碰撞”:
实体 |
我是…. (categoryBits) |
我和…碰撞 (maskBits) |
友好的船 |
FRIENDLY_SHIP |
BOUNDARY |FRIENDLY_SHIP | ENEMY_SHIP |
敌对的船 |
ENEMY_SHIP |
BOUNDARY |FRIENDLY_SHIP | ENEMY_SHIP |
友好的飞行器 |
FRIENDLY_AIRCRAFT |
BOUNDARY |ENEMY_AIRCRAFT |
敌对的飞行器 |
ENEMY_AIRCRAFT |
BOUNDARY |FRIENDLY_AIRCRAFT |
所以,我们可以再创建实体时使用上面的数据来设置合适的参数。
//large and green are friendly ships
for (int i = 0; i < 3; i++)
balls.push_back( new Ball(m_world, 3, green, FRIENDLY_SHIP, BOUNDARY | FRIENDLY_SHIP | ENEMY_SHIP ) );
//large and red are enemy ships
for (int i = 0; i < 3; i++)
balls.push_back( new Ball(m_world, 3, red, ENEMY_SHIP, BOUNDARY | FRIENDLY_SHIP | ENEMY_SHIP ) );
//small and green are friendly aircraft
for (int i = 0; i < 3; i++)
balls.push_back( new Ball(m_world, 1, green, FRIENDLY_AIRCRAFT, BOUNDARY | ENEMY_AIRCRAFT ) );
//small and red are enemy aircraft
for (int i = 0; i < 3; i++)
balls.push_back( new Ball(m_world, 1, red, ENEMY_AIRCRAFT, BOUNDARY | FRIENDLY_AIRCRAFT ) );
现在再次运行程序,你会发现上面的规则都被很好地履行了。
使用group indexes
Fixture的groupIndex标志能够用来覆盖上面的category和mask设置。顾名思义,它能够将fixture放在一起,要么它们总是可以碰撞,要么永远不能碰撞。groupIndex作为一个有符号的整数,而不是bitflag。这里是它的工作原理----慢慢地阅读,一开始会有些难理解。当检查两个fixture来看看它们是否应该碰撞:
.如果它们中的一个groupIndex是0,使用category/mask规则同上
.如果两个的groupIndex都是非零但不相同,使用category/mask规则同上
.如果两个的groupIndex相同且都为正,碰撞
.如果两个的groupIndex相同且为负,不碰撞
groupIndex的默认值为0,所以目前为止它什么也没有参与。让我们做一个简单的例子,覆盖上面的category/mask设置。我们会将边界墙和一辆车放在同一组内,并且将group值设为负值。如果你仔细留意了上面的解释,你会发现这将让一辆车偷偷摸摸地逃离边界围栏。
你可以再Ball类的构造函数中添加一个groupIndex参数,或者用一个快捷的方法:
//in FooTest constructor, before creating walls
myFixtureDef.filter.groupIndex = -1;//negative, will cause no collision
//(hacky!) in global scope
bool addedGroupIndex = false;
//(hacky!) in Ball constructor, before creating fixture
if ( !addedGroupIndex )
myFixtureDef.filter.groupIndex = -1;//negative, same as boundary wall groupIndex
addedGroupIndex = true;//only add one vehicle to the special group
如果我们将groupIndex设置为正值,这个汽车总是会和墙碰撞。在这种情况下,所有的汽车都和墙碰撞了,所以这没有太大的区别,这就是我们为什么将它设置为负,因此我们就可以看到要发生的事。在其他的情况下,这可能会相反,例如船和飞行器永远都不会发生碰撞,你可能会说一部分的飞行器会和船碰撞。如低空飞行的水上飞机,或许…
需要更多的控制
如果你需要更加具体地控制什么和什么可以碰撞,你可以在世界里设置一个碰撞过滤回调以便Box2D在需要检查两个fixtures是否应该碰撞,而不是使用上面的规则给出两个fixtures并让你去决定。回调的使用方法和debug draw以及碰撞回调的使用一样,通过继承b2ContactFilter,实现线面的函数,让引擎通过调用世界的SetContactFilter函数回去信息。
bool b2ContactFilter::ShouldCollide(b2Fixture* fixtureA, b2Fixture* fixtureB);
在运行时改变碰撞过滤器
有时候你可能想根据游戏中具体的事件来改变碰撞过滤器。你可以在fixture中创建新的b2Filter来改变他们的categoryBits、maskBits和groupIndex。通常你只想改变他们中的一个,所以这很方便获取现有的过滤器,改变你想要修改的数值,并把它放回去。如果你有一个指向fixture的引用,这回变得更容易:
//get the existing filter
b2Filter filter = fixture->GetFilterData();
//change whatever you need to, eg.
filter.categoryBits = ...;
filter.maskBits = ...;
filter.groupIndex = ...;
//and set it back
fixture->SetFilterData(filter);
如果你只有只想物体的引用,你需要遍历物体的Fixture来找到你想要的fixture。