备注:由于本人最近在学习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。

打赏

发表评论

您的电子邮箱地址不会被公开。