Box2D C++ tutorials - Collision callbacks
Last edited: July 14 2013Chinese version -> 中文
Collision callbacks
When bodies move around in the physics scene and bounce off each other, Box2D will handle all the necessary collision detection and response so we don't need to worry about that. But the whole point of making a physics game is that occasionally something should happen in the game as a result of some of the bodies hitting each other eg. when the player touches a monster he should die, or a ball bouncing on the ground should make a sound etc. We need a way to get this information back from the physics engine.
In the drawing your own objects topic, we held a reference to the body in our game entity, and queried it every frame to get the current location of the body to draw it. This was a reasonable thing to do because we will be rendering the body every frame, and the location/rotation are likely to be changing every frame too. But collisions are a bit different - they don't occur every frame, they usually happen much less frequently. So asking the physics engine every time step if a body had collided with something would be like the child in the back seat on a car trip continually saying "are we there yet...?". Almost all the time the answer is no, nothing new is accomplished and if you consider that we would have to do this for every entity in the world, it's inefficient too. It would be far better for the driver to say "just keep quiet and I'll let you know when something happens".
In Box2D we do this with a callback function. This is a function that we write to take some action when two entities collide. We don't know yet what the two entities will be, so they must be passed to the function as a parameter when the collision actually happens. Then we give this function to Box2D as if to say "when a collision occurs, call back to this function".
Callback timing
Collisions between entities in the world will be detected during the b2World::Step() function that we call every time step. As we saw in the topic on worlds, the b2World::Step function advances the simulation, moving all the bodies around and so on. As soon as a collision is detected, the program flow will be given to your callback function to do something, and then goes back to b2World::Step to continue with more processing. It's important to note that since your callback is being called right in the middle of the stepping process, you shouldn't do anything to change the scene right away - there may be more collisions occuring in the same time step. Setting up a collision callback function is done in the same way as customizing the debug draw. We make a subclass of the b2ContactListener class, which has a bunch of virtual functions that can be overridden. These are the functions we'll use:
1 2 3 4 5 6 | //b2ContactListener // Called when two fixtures begin to touch virtual void BeginContact(b2Contact* contact); // Called when two fixtures cease to touch virtual void EndContact(b2Contact* contact); |
Callback information
Once we've set up a subclass for b2ContactListener, we will know when a pair of entites has collided, and do something with them. But hang on... there are no bodies supplied to BeginContact/EndContact, so how can we tell what entities collided? The contact parameter contains all the information we need. The main thing we want to know is what two fixtures were involved in the collision, and we can get these from the contact with these functions:
1 2 3 4 5 6 | //b2Contact // Get the first fixture in this contact b2Fixture* GetFixtureA(); // Get the second fixture in this contact b2Fixture* GetFixtureB(); |
Example
For this topic's demonstration, let's use the BeginContact/EndContact collision callbacks to change the color of a ball when it hits something. Start with the scene we had in the drawing your own objects topic, just after we set the smiley face to draw in the correct position. At this point there was only one ball in the scene.
Add a boolean member variable to the Ball class to track whether it is currently hitting something, and functions to call when the physics engine tells us about contacts changing. In the render function, we will set the color depending on the current contact state.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //Ball class member variable bool m_contacting; //in Ball class constructor m_contacting = false; //Ball class functions void startContact() { m_contacting = true; } void endContact() { m_contacting = false; } //in Ball::render if ( m_contacting ) glColor3f(1,0,0);//red else glColor3f(1,1,1);//white |
1 2 | //in Ball constructor body->SetUserData( this ); //set this Ball object in the body's user data |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | class MyContactListener : public b2ContactListener { void BeginContact(b2Contact* contact) { //check if fixture A was a ball void* bodyUserData = contact->GetFixtureA()->GetBody()->GetUserData(); if ( bodyUserData ) static_cast<Ball*>( bodyUserData )->startContact(); //check if fixture B was a ball bodyUserData = contact->GetFixtureB()->GetBody()->GetUserData(); if ( bodyUserData ) static_cast<Ball*>( bodyUserData )->startContact(); } void EndContact(b2Contact* contact) { //check if fixture A was a ball void* bodyUserData = contact->GetFixtureA()->GetBody()->GetUserData(); if ( bodyUserData ) static_cast<Ball*>( bodyUserData )->endContact(); //check if fixture B was a ball bodyUserData = contact->GetFixtureB()->GetBody()->GetUserData(); if ( bodyUserData ) static_cast<Ball*>( bodyUserData )->endContact(); } }; |
Now to tell the Box2D world to use these functions for collisions, we use the SetContactListener function. This function takes a pointer to a b2ContactListener object, so we'll need to have an instance of the class to point to. This is easily done by just declaring a variable of your new class at global scope.
1 2 3 4 5 | //at global scope MyContactListener myContactListenerInstance; //in FooTest constructor m_world->SetContactListener(&myContactListenerInstance); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //replace old boolean variable m_contacting in Ball class int m_numContacts; //in Ball constructor m_numContacts = 0; //new implementation for contact state change void startContact() { m_numContacts++; } void endContact() { m_numContacts--; } //altered rendering code if ( m_numContacts > 0 ) glColor3f(1,0,0);//red else glColor3f(1,1,1);//white |
Add a boolean variable to the Ball class to track whether it is currently 'it', and set up a function at global scope that takes two balls and switches their 'it' status.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //in Ball class bool m_imIt; //in Ball constructor m_imIt = false; //in Ball::render if ( m_imIt ) glColor3f(1,0,0);//red else glColor3f(1,1,1);//white //in global scope void handleContact( Ball* b1, Ball* b2 ) { bool temp = b1->m_imIt; b1->m_imIt = b2->m_imIt; b2->m_imIt = temp; } |
1 2 3 4 5 6 7 8 | void BeginContact(b2Contact* contact) { //check if both fixtures were balls void* bodyAUserData = contact->GetFixtureA()->GetBody()->GetUserData(); void* bodyBUserData = contact->GetFixtureB()->GetBody()->GetUserData(); if ( bodyAUserData && bodyBUserData ) handleContact( static_cast<Ball*>( bodyAUserData ), static_cast<Ball*>( bodyBUserData ) ); } |
1 2 | //at end of FooTest constructor balls[0]->m_imIt = true; |
Real scenarios
This was a pretty basic demonstration, but the idea can be expanded on to handle more interesting logic in your game, for example when a player touches a monster, etc. In a more complex scene with different kinds of entities colliding, you would want to set something in the fixture user data from which you can tell what kind of entity it represents. One possibility is instead of directly putting the Ball pointer in the user data, you could set an object of a generic parent class, of which Ball and every other entity are subclasses. This parent class would have a virtual function to return what type of entity it is, something like this:
1 2 3 4 5 6 7 8 9 | class Entity { virtual int getEntityType() = 0; }; class Ball : public Entity { int getEntityType() { return ET_BALL; } } |
Another way might be to use dynamic casting. To be honest I haven't really come up with a method that I like much, because somewhere along the way they all seem to end up in bloated routines with a lot of if/else checks to get the right case. If anyone has a nice clean way to handle this, let us know!
Update: As Marcos points out in the comments, C++ also has the 'typeid' method for determining what class a pointer is. See also http://en.wikipedia.org/wiki/Typeid.