Tutorials

Check the Filter

Box2D allows filtering of collision contacts into groups. In my case I require a boundary at center-screen to limit game player movement, but need a third piece to cross the center barrier without being stopped. Box2D bodies in a group tagged ‘-1’ will not have their vectors changed when a collision is detected.

Without making changes to a playerPiece class, I can add this grouping information to a ‘ball’ class to give it free movement across the board.

This example is gathered from the Box2D documentation and the testbed files in Cocos2D, and assumes you have a b2World and b2ContactListener already initialized.

Firstly, in the main GamePlay.mm file, a b2ContactFilter class needs to be added:

#pragma mark ContactFilter

class myContactFilter : public b2ContactFilter
{
	// There are more callbacks than this one, see manual
	bool ShouldCollide(b2Fixture* fixtureA, b2Fixture* fixtureB);
};

// add a method to test the collision data for filter settings
bool myContactFilter::ShouldCollide(b2Fixture* fixtureA, b2Fixture* fixtureB)
{
	b2Filter filter1 = fixtureA->GetFilterData();
	b2Filter filter2 = fixtureB->GetFilterData();

	// test to let pass or calculate collision results
	if(filter1.groupIndex == filter2.groupIndex) {
		return false;
	} else {
		return true;
	}
}

..then add the ContactFilter to the GamePlay.mm init function:


-(id) init
{
	if( (self=[super init])) {
        [...] // add b2World and groundbox

// start b2World
world = new b2World(gravity, doSleep);

// init a ContactFilter class
world->SetContactFilter(new myContactFilter);

       }
} 

Next in the main GamePlay.mm class, we add the Box2D edge line at screen center:

-(id) init
{
	if( (self=[super init])) {
        [...] // add b2World and groundbox
        
        // start b2World
        world = new b2World(gravity, doSleep);

       // init a ContactFilter class
        world->SetContactFilter(new myContactFilter);

                // center bar
		b2PolygonShape polygon;
		polygon.SetAsEdge(b2Vec2(0,(screenSize.height / 2) / PTM_RATIO),
b2Vec2(320/PTM_RATIO,(screenSize.height / 2) / PTM_RATIO)); 
// calculates horizontal and vertical screen centers
		
		b2FixtureDef boxShapeDef;
		boxShapeDef.shape = &polygon;
		boxShapeDef.density = 0.0f; 
//zero density makes the object 'static', behaves like an obstacle

// add the contact filter group with an index of -1. 
//Category and mask bits are other ways to match collision groups
of colliding and non-colliding Box2D bodies.
		boxShapeDef.filter.groupIndex = -1;
		//boxShapeDef.filter.categoryBits = 0x0004;
		//boxShapeDef.filter.maskBits = 0xFFFF;
		
		b2BodyDef boxBodyDef;
		
		b2Body* body3 = world->CreateBody(&boxBodyDef);
		body3->CreateFixture(&boxShapeDef);
}
return self;
}

Lastly in my external Ball.mm class, I add similar filter information to its init function:

// initialize the Box2D body in Ball.mm external class:
- (id)initWithPos:(CGPoint)points {
	if(self = [super init]) {	
                [self setBodyDef:new b2BodyDef];
		[self bodyDef]->position.Set(points.x/PTM_RATIO, points.y/PTM_RATIO); 
                //PTM_RATIO converts Box2D units(m) into Cocos2D units(px)
		[self bodyDef]->linearDamping = 0.2f;
		
		[self setShapeDef:new b2CircleShape];
		[self shapeDef]->m_radius = 22.0f/PTM_RATIO;
		
		[self setFixtureDef:new b2FixtureDef];
		[self fixtureDef]->shape = shapeDef;
		[self fixtureDef]->friction = 0.06f;
		[self fixtureDef]->density = 0.8f;
		[self fixtureDef]->restitution = 0.4f;//bounciness
		
// filter for passing middle boundary
		self.fixtureDef->filter.groupIndex = -1;
		self.fixtureDef->filter.categoryBits = 0x0004;
		self.fixtureDef->filter.maskBits = 0xFFFF;
       }
return self;
}

Now if the Box2D world is built correctly, bodies without the group index of -1 will be blocked while the Ball body passes through without stopping.

The Manifold Ways to Listen

Box2D is quickly evolving. It seems within a range of four months the physics engine’s contact listener has been radically changed (and improved).

C++, Obj-C and Box2d challenged me as a newcomer to synthesize user tutorials and outdated documentation with up-to-date examples. With the help of a few Cocos2D forum participants, time and plain bullish determination to understand, I’ve been successful and would like to share this info with other newbies.

ContactListener

A contact listener isn’t essential to watch Box2d physics simulations in action. You can set the shape’s body definitions and go. But the contact listener is essential for game logic, because it provides location of contact and pointers to the colliding physics bodies.

With that information you can also access any user data embedded in the physics body object. Often a cocos2d node is embedded, making it easy to update the onscreen graphic image during the simulation. Since Box2d is actually a mathmatical process without graphics, the visible shapes which aid debugging are usually made invisible for the actual application release.

The two principle methods of the b2ContactListener are beginContact and endContact. These are most useful for common game logic.
The listener also provides a preSolve and postSolve function. Those could be useful for altering properties of physics bodies before or after each step – making them sensors, changing collision filtering settings, and so on.

Manifolds

Embedded throughout the b2Contact classes are references to a manifold. This is simply a holder for the two objects being evaluated for contact. There’s a local-coordinate manifold and a world-coordinates manifold.
If you view the Box2d Testbed’s ‘presolve’ method, you’ll see that ‘manifold’ and ‘oldManifold’ are used to retrieve position and vector normals from colliding physics fixtures.

Older versions of Box2d examples might have used Add, Remove, and Persist functions for contacts. Manifolds simplify the code needed to collect and evaluate these contact points.

Example b2ContactListener code

My example is based on the Box2d testbed, included with the cocos2d 0.8.1 release.

In my main Game.mm class (Box2d is C++ so XCode requires the extra ‘m’), I’ve declared an actual class file:
class MyContactListener : public b2ContactListener
{ ...

A limit to the number of contacts is then stated. This is important because contacts are reported each frame. So two bodies colliding may signal many contacts in a very short time.

class MyContactListener : public b2ContactListener
{  int32 m_pointCount;
ContactPoint m_points[k_maxContactPoints];
...

Next, public methods are declared. Game logic and other game piece classes may need access to these methods.

class MyContactListener : public b2ContactListener
    {
        int32 m_pointCount;
        ContactPoint m_points[k_maxContactPoints];
        public:
        void BeginContact(b2Contact* contact)
            {
            }
    };

Note the use of C++ syntax in the methods declarations ( e.g. void rather than Obj-C -(void) ).

The b2ContactListener compares the fixtures of physics bodies, calling these fixtureA and fixtureB. We need to state these as struct in the file. Scroll to the top of the Game.mm file, outside of the @implementation and type:

const int32 k_maxContactPoints = 2048;
struct ContactPoint
    {
        b2Fixture* fixtureA;
        b2Fixture* fixtureB;
        b2Vec2 normal;
        b2Vec2 position;
        b2PointState state;
    };

These provide the constant value for k_maxContactPoints (2048) and state the fixture and physics vector variables, used in the contactListener.

Return to the BeginContact method and type:

public:
void BeginContact(b2Contact* contact)
    {
        b2Fixture* fixtureA = contact->GetFixtureA();
        b2Fixture* fixtureB = contact->GetFixtureB();
        ...

Now we’ve created local variables for the two colliding physics bodies. This will be updated every time step, so the values of fixtureA and fixtureB might change rapidly. Box2d fixtures contain the essential values for calculation, e.g. density (affecting mass), friction and rectitude (elasticity).

Any game logic you require is now entered here; methods to change color, call an animation loop, trigger a sound effect, alter the appearance or value of one or both of the colliding bodies, etc.

Hint: customize the XCode command bar to easily view your project classes and methods: View/Customize Toolbar then add Class Browser. The Box2D source is very well commented so it takes only a moment to find and understand these functions while coding.

To test our listener, we’ll add one of the b2Contact methods.
Let’s use the ‘isSolid’ function. This tests a contact to see if it’s real, and not a sensor, disabled, or no longer in contact. We’ll use it to trigger a console message:

public:
void BeginContact(b2Contact* contact)
    {
        b2Fixture* fixtureA = contact->GetFixtureA();
        b2Fixture* fixtureB = contact->GetFixtureB();
        if (contact->IsSolid()) {
            NSLog(@"Contact is solid");
        }
    }

Since there are possibly many contacts per second, it’s wise to add your own check to ignore subsequent contacts for a period of time, perhaps one second. This avoids triggering your game logic too rapidly.

Next add your EndContact function. We’ll write a simple message to the console.

void EndContact(b2Contact* contact)
    {
        NSLog(@"end contact");
    }

Complete class

#pragma mark Contact Listener

class MyContactListener : public b2ContactListener
{
    int32 m_pointCount;
    ContactPoint m_points[k_maxContactPoints];

    public:
    void BeginContact(b2Contact* contact)
        {
            b2Fixture* fixtureA = contact->GetFixtureA();
            b2Fixture* fixtureB = contact->GetFixtureB();
            if (contact->IsSolid()) {
               NSLog(@"Contact is solid");
             }
        }

    void EndContact(b2Contact* contact)
        {
            NSLog(@"end contact");
        }

    void PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
        {
           const b2Manifold* manifold = contact->GetManifold();
        }

    void PostSolve(b2Contact* contact)
        {
            const b2ContactImpulse* impulse;
        }
};

Hint:’#pragma mark’ is simply a bookmark to that part of the page, which then appears in a dropdown menu above the code window in XCode.

Lastly, after creating your Box2d physics ‘world’, you need to add the contact listener to the world:
world->SetContactListener(new MyContactListener);

Remember, Box2d requires an environment to be initialized and a groundBox added in order to work.

Good luck!