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!

You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.

Comments

  1. On November 02, 2009 Joe says:

    Great tutorial! I was really confused with all sorts of documentation out there for Box2D (different versions, out of date examples/tutorials), that it really discouraged me. I was looking for a good tutorial on Box2D ContactListeners, and this blog post nailed it! Thanks!

  2. On November 02, 2009 XyrisKenn says:

    Thank you Joe, I appreciate your compliment, and am glad this was helpful to you :)

  3. On November 30, 2009 Eric says:

    Great tutorial, very helpful and informative.

    My only suggestion for making this better is to have a downloadable xcode example so that it could be easily walked thru and modified to aid in learning.

    Great job though, please keep these tutorials coming!

  4. On November 30, 2009 XyrisKenn says:

    Thanks Eric, I appreciate your words. Good idea on downloadable code.