Box2D C++ tutorials - The 'can I jump' question
Last edited: July 14 2013Chinese version -> 中文
Is my character on the ground?
If ever there was a question about Box2D, "how can I tell if my player character is on the ground?" is probably it. I think the reason this comes up so often is firstly because of the callback-style method of getting contact information - the callback method itself can throw off those who are not familiar with it - and also because keeping track of what is touching what is left to the programmer to implement instead of being built-in to the library. In either case, we have covered both of these topics already. The collision callbacks topic dealt with getting the necessary begin and end contact events, and the sensors topic showed an example where we kept track of a 'currently touching' list.
So what's left to talk about? Well, I'm guessing many people will find this page as their first stop when trying to tackle this problem, so I wanted to point out those previous two topics as better places to start for a proper understanding. And I also thought it would be good to make a demonstration of these techniques in the setting that they are most commonly wanted, the platformer game, rather than the battlefield scenario of the previous topics.
We'll look at an example which is quite effective at solving this question, yet not too complex. The player body will have a main fixture to represent the player itself, and another smaller sensor fixture attached underneath it to detect when something is under-foot. This foot sensor will keep track of what other fixtures in the world it is touching, and because we know it is directly beneath the player and not too big, we will just consider any situation where the foot sensor is touching something to be a jumpable situation. There are a couple of small tweaks we can make but that's the general idea.
Because these are concepts we've already covered just put in a different setting, we'll go one step further and instead of simply keeping track of whether the player is standing on something, we'll also keep track of what kind of something(s) are being stood on. The scene will have two types of boxes of different size, and depending on which the player is standing on we can alter his jumpability.
Preventing jumping in the air
Yes you guessed it, we're gonna start with the same scenario as for the Jumping topic. Dig that one up, and in the constructor add a section like this to create some bodies to jump around on in the scene:
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 31 | //add some bodies to jump around on { //body definition b2BodyDef myBodyDef; myBodyDef.position.Set(-5,5); myBodyDef.type = b2_dynamicBody; //shape definition b2PolygonShape polygonShape; polygonShape.SetAsBox(1, 1); //a 2x2 rectangle //fixture definition b2FixtureDef myFixtureDef; myFixtureDef.shape = &polygonShape; myFixtureDef.density = 1; for (int i = 0; i < 5; i++) { b2Fixture* fixture = m_world->CreateBody(&myBodyDef)->CreateFixture(&myFixtureDef); fixture->SetUserData( (void*)1 );//tag square boxes as 1 } //change size polygonShape.SetAsBox(0.5, 1); //a 1x2 rectangle for (int i = 0; i < 5; i++) { b2Fixture* fixture = m_world->CreateBody(&myBodyDef)->CreateFixture(&myFixtureDef); fixture->SetUserData( (void*)2 );//tag smaller rectangular boxes as 2 } } |

Now at the beginning of the constructor, we'll need to make a couple of changes to the 'player' box. Firstly we'll make it a bit taller so we can tell which one it is easier, and we will also add the foot-sensor fixture to it, with a user data tag of 3. You can edit the existing section to do this, but here is the full player body creation for clarity:
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 | //player body { //body definition b2BodyDef myBodyDef; myBodyDef.type = b2_dynamicBody; myBodyDef.fixedRotation = true; //shape definition for main fixture b2PolygonShape polygonShape; polygonShape.SetAsBox(1, 2); //a 2x4 rectangle //fixture definition b2FixtureDef myFixtureDef; myFixtureDef.shape = &polygonShape; myFixtureDef.density = 1; //create dynamic body myBodyDef.position.Set(0, 10); m_body = m_world->CreateBody(&myBodyDef); //add main fixture m_body->CreateFixture(&myFixtureDef); //add foot sensor fixture polygonShape.SetAsBox(0.3, 0.3, b2Vec2(0,-2), 0); myFixtureDef.isSensor = true; b2Fixture* footSensorFixture = m_body->CreateFixture(&myFixtureDef); footSensorFixture->SetUserData( (void*)3 ); } |

-
Firstly, since we know it's under the player, we know a collision with it means the player is standing
on something. If we just used the main body to get collision events, those events could be from collisions
with the walls or ceiling too, so we would have to check the direction before we could say it was a
ground collision. -
Secondly, as the player moves around, especially on slopes the main body tends to bounce slightly on
the ground which causes a large number of begin/end events to occur in quick succession. If we were
using the main body to determine jumpability, it's possible that the user could try to jump just when
the main body is off the ground for a few milliseconds, even though it appears to be on the ground, and
that would just be annoying huh? Using a sensor like this will cause a smoother and more reliable contact
detection because the sensor will stay in contact with the ground during the tiny bounces. -
Thirdly, having the foot sensor and the main body as separate fixtures means they can be given individual
shapes as the situation requires. For example, in this case the foot sensor is much narrower than the main
body meaning you wont be able to jump when you are teetering on the edge of something, but this could
easily be adjusted by changing the size, shape or location of the sensor.
1 2 3 4 5 | //global scope int numFootContacts; //in constructor numFootContacts = 0; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class MyContactListener : public b2ContactListener { void BeginContact(b2Contact* contact) { //check if fixture A was the foot sensor void* fixtureUserData = contact->GetFixtureA()->GetUserData(); if ( (int)fixtureUserData == 3 ) numFootContacts++; //check if fixture B was the foot sensor fixtureUserData = contact->GetFixtureB()->GetUserData(); if ( (int)fixtureUserData == 3 ) numFootContacts++; } void EndContact(b2Contact* contact) { //check if fixture A was the foot sensor void* fixtureUserData = contact->GetFixtureA()->GetUserData(); if ( (int)fixtureUserData == 3 ) numFootContacts--; //check if fixture B was the foot sensor fixtureUserData = contact->GetFixtureB()->GetUserData(); if ( (int)fixtureUserData == 3 ) numFootContacts--; } }; |
Finally, to actually use this information, we need to ignore any attempts to jump when the foot sensor is not touching anything. You could add something like this to the relevant parts of the switch statement in the Keyboard function:
1 | if ( numFootContacts < 1 ) break; |
1 2 3 | //in Step function m_debugDraw.DrawString(5, m_textLine, "Can I jump here? %s", numFootContacts>0?"yes":"no"); m_textLine += 15; |
Preventing jumping again immediately after just jumping
When jumping using ApplyForce or ApplyLinearImpulse, if you hold down the jump key you will find that although it's not as bad as before, the player can still jump really high. This is simply because even though the jump has started and the body is moving upwards, the keypress repeats many times while the body is still close enough to the ground for the sensor to touch, so many forces/impulses get added in that short time span. To counter this, we can just limit the frequency in which jumps are possible, eg. if the player has just jumped, they shouldn't be jumping again for at least another 100ms.
To code this you would typically use a fast timer to compare the times between when you jumped, and the current time. You can find implementations of such timers for various platforms around on the net, and the latest version of Box2D includes one too. Since the version of Box2D we are using, v2.1.2, does not have any timer we'll just make use of the fact that one timestep is 1/60 seconds to suffice as a basic timer. Let's stop the player from making jumps within 1/4 second of each other, which is 15 time steps at 60Hz. This should be enough time for the foot sensor to have moved above the ground.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //class member variable int m_jumpTimeout; //in class constructor m_jumpTimeout = 0; //in Step() m_jumpTimeout--; //in Keyboard routine (only showing the impulse version here as an example) case 'l': if ( numFootContacts < 1 ) break; if ( m_jumpTimeout > 0 ) break; //ok to jump m_body->ApplyLinearImpulse( b2Vec2(0, m_body->GetMass() * 10), m_body->GetWorldCenter() ); m_jumpTimeout = 15; break; |
Preventing jumping depending on ground type
The above method is a basic way to stop the player from jumping when he's already in the air. How about if we wanted to allow jumping only when standing on certain objects? Or maybe the jump should be higher from certain objects, etc. Let's try making jumps possible only when standing on the larger boxes in the scene. To do this we'd like to have a list of what's currently being stood on, to check in the Keyboard function when the user presses the jump key. A list like this can be kept up to date by adding fixtures to it in BeginContact, and removing them in EndContact, just like the radar example in the sensors topic.
Alternatively, you could split the numFootContacts into two variables to keep track of the number of large boxes and the number of small boxes currently stood on separately - but this is quite limited information, and if you introduce a third type of box you'll then need a third integer variable to keep track of the new type, and so on. For this demonstration we'll go with the more useful method of keeping a list of the boxes currently stood on, because it's a better long-term solution, and also because I want to use the stood on boxes a little later.
Add a class member variable to hold the current set of boxes being stood on. You could make this a list of bodies but I will make it hold the original fixtures because remember we can also stand on the static 'ground' body which in a real game would likely have many fixtures and we would probably like to be able to tell them apart.
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 | //global variable (include <set>) set<b2Fixture*> fixturesUnderfoot; //revised implementation of contact listener class MyContactListener : public b2ContactListener { void BeginContact(b2Contact* contact) { //check if fixture A was the foot sensor void* fixtureUserData = contact->GetFixtureA()->GetUserData(); if ( (int)fixtureUserData == 3 ) fixturesUnderfoot.insert(contact->GetFixtureB());//A is foot so B is ground //check if fixture B was the foot sensor fixtureUserData = contact->GetFixtureB()->GetUserData(); if ( (int)fixtureUserData == 3 ) fixturesUnderfoot.insert(contact->GetFixtureA());//B is foot so A is ground } void EndContact(b2Contact* contact) { //check if fixture A was the foot sensor void* fixtureUserData = contact->GetFixtureA()->GetUserData(); if ( (int)fixtureUserData == 3 ) fixturesUnderfoot.erase(contact->GetFixtureB());//A is foot so B is ground //check if fixture B was the foot sensor fixtureUserData = contact->GetFixtureB()->GetUserData(); if ( (int)fixtureUserData == 3 ) fixturesUnderfoot.erase(contact->GetFixtureA());//B is foot so A is ground } }; |
Now in the Keyboard function, instead of merely checking the number of stood on things, we'll check what type they are too. Specifically, we want to prevent jumping when the only thing being stood on is a small box. This means we check the current fixturesUnderfoot list, and if there is a fixture with user data tag of either 1 (a large box) or NULL (a static 'ground' fixture), then the jump should be permitted. Because this jumpability check is getting a bit long now and we want to call it from various places, I'll put this logic in a function of it's own to keep it tidy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //class function bool CanJumpNow() { if ( m_jumpTimeout > 0 ) return false; set<b2Fixture*>::iterator it = fixturesUnderfoot.begin(); set<b2Fixture*>::iterator end = fixturesUnderfoot.end(); while (it != end) { b2Fixture* fixture = *it; int userDataTag = (int)fixture->GetUserData(); if ( userDataTag == 0 || userDataTag == 1 ) //large box or static ground return true; ++it; } return false; } |
Replace the previous checks using numFootContacts with this function, and your ready to go. Try the program and check that you can only jump from the large boxes or the normal static ground.
Adding some reaction to the environment
Well that about covers the basics of preventing jumping in certain situations. As one last little demonstration, let's give the currently stood on boxes a kick downwards when the player jumps. This is one advantage of keeping a list instead of just an integer to keep track of them.
In the Keyboard function when a jump is performed, you could do something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //in Keyboard //kick player body upwards b2Vec2 jumpImpulse(0, m_body->GetMass() * 10); m_body->ApplyLinearImpulse( jumpImpulse, m_body->GetWorldCenter() ); m_jumpTimeout = 15; //kick the underfoot boxes downwards with an equal and opposite impulse b2Vec2 locationOfImpulseInPlayerBodyCoords(0, -2);//middle of the foot sensor b2Vec2 locationOfImpulseInWorldCoords = m_body->GetWorldPoint(locationOfImpulseInPlayerBodyCoords); set<b2Fixture*>::iterator it = fixturesUnderfoot.begin(); set<b2Fixture*>::iterator end = fixturesUnderfoot.end(); while (it != end) { b2Fixture* fixture = *it; fixture->GetBody()->ApplyLinearImpulse( -jumpImpulse, locationOfImpulseInWorldCoords ); ++it; } |