Sep 132011
 

To Collide or Not to Collide

When you create simple Box2d objects, they will collide with each other. But what if you do not want an object colliding with all other objects? What if you want certain objects colliding with only certain other objects? How do we achieve this finer grained control?

Say that our game has three game objects: 1. Player, 2. Enemies and 3. Power Ups. Normally, all three objects would collide with each other. But what if what we really want to do is this:

1. Player and enemies can collide
2. Player and power ups can collide
3. Enemies and power ups SHOULD NOT collide

There is something called a groupIndex. Two fixtures with the same positive groupIndex will always collide. And two fixtures with negative groupIndex will never collide. But what if we want an even finer grained control than this?

Category Bits and Mask Bits

There are two properties called categoryBits and maskBits which you can set for a fixture. An integer is 16 bits, so you can have up to 16 different categories. What do I mean by this? See below for 16 possible categories (in binary and also in hex).

Category_01 = 0000 0000 0000 0001 (0x0001)
Category_02 = 0000 0000 0000 0010 (0x0002)
Category_03 = 0000 0000 0000 0100 (0x0004)

Category_15 = 0100 0000 0000 0000 (0x4000)
Category_16 = 1000 0000 0000 0000 (0x8000)

The categoryBits and maskBits can be used together with bitwise operators to control collision between objects.

Example

So let us put the above information to practical use. We have three game objects, so we can define three categories for our game objects.

#define CATEGORY_PLAYER    0x0001     // 0000 0000 0000 0001
#define CATEGORY_ENEMY     0x0002     // 0000 0000 0000 0010
#define CATEGORY_POWERUP   0x0004     // 0000 0000 0000 0100

When we define the fixture for our game objects, we can now set the categoryBits and the maskBits like so. The code below says that the player will collide with enemies and power ups.

b2FixtureDef playerFixture;
... 
playerFixture.filter.categoryBits = CATEGORY_PLAYER;
playerFixture.filter.maskBits = CATEGORY_ENEMY | CATEGORY_POWERUP;

The code below says that the power up only collides with the player (and not enemies)

b2FixtureDef powerupFixture;
... 
powerupFixture.filter.categoryBits = CATEGORY_POWERUP;
powerupFixture.filter.maskBits = CATEGORY_PLAYER;

And finally, the code below says that enemies only collides with the player (and not the power up)

b2FixtureDef enemyFixture;
... 
enemyFixture.filter.categoryBits = CATEGORY_ENEMY;
enemyFixture.filter.maskBits = CATEGORY_PLAYER;

Once you set the categoryBits and maskBits, Box2d uses these and the rule below to determine whether the objects collide.

uint16 catA = fixtureA.filter.categoryBits;
uint16 maskA = fixtureA.filter.maskBits;
uint16 catB = fixtureB.filter.categoryBits;
uint16 maskB = fixtureB.filter.maskBits;
 
if ((catA & maskB) != 0 && (catB & maskA) != 0) {
    // fixtures can collide
}

It is important to remember that if you want two fixtures to collide, the maskBits must include the categoryBits of the other fixture with which you want it to collide. Meaning, in the code below, the the player’s maskBits must include CATEGORY_ENEMY AND the enemy’s maskBits must also include CATEGORY_PLAYER. It is not enough to set it for just one of the fixtures. In other words, just because you set the player’s maskBits to collide with the enemy does not mean that they will collide. If A is set to collide with B, B must also be set to collide with A in order for the two to actually collide.

playerDef.categoryBits = CATEGORY_PLAYER;
playerDef.maskBits = CATEGORY_ENEMY;  // <-- point to the enemy

enemyDef.categoryBits = CATEGORY_ENEMY;
enemyDef.maskBits = CATEGORY_PLAYER;  // <-- point to the player

Conclusion

This should be fairly flexible enough for a great deal of your needs. If you need even more control than this, then I would suggest taking a look at Box2d's contact listener's PreSolve event (b2ContactListener::PreSolve).

Happy coding!

 Posted by at 9:03 am