So can I jump or not? Determining if the ground is under your feet in Box2D

I now have a near-total grasp why the developers of the Box2D physics engine “caution” its use in platformer games. It’s really a poor match for a whole host of common interaction patterns, the most basic one being one of the most difficult (and important) to get right: is the character standing on solid ground?

The basic tutorial to answer this question doesn’t work properly, since counting collisions and separations with the character’s feet isn’t accurate in all cases, such as when bodies are created or destroyed the same simulation step as they touch the character’s body. When the count gets off, you’ll either have a character who can’t jump (broken game) or a character who can air-jump endlessly (sequence-broken game). The only thing to do is to verify, by examining the contacts touching the character’s body, whether there’s solid ground under their feet or not.

Except this doesn’t work properly in all cases, either, if you ever resize the polygon of the character’s body. Does your character need to duck, or to crawl? Then you’ll either need to a) resize or recreate his body when this happens, or b) use prism joints and multiple fixtures to achieve the same effect. The second option sounded like a rabbit hole to me, but I was still running into cases where I was getting non-physical behavior after a resize event, which you can see happening at around the 45-second mark below.

I’ve been coding this game, off and on, for over a year now, and I just this week finally figured out how to subdue this buggy behavior. I’m not going to kid myself that my fix won’t cause other bugs further down the line, but it at least addresses all the ones I know about. It consists of two changes:

  1. Don’t rely on Box2D to tell you about collision and separation events to determine whether the character is standing on solid ground. Instead, check manually every frame, using contact with the ground as a primary source and ray casts to make sure. This is the only reliable method I have found to answer the “can I jump” question when bodies are being created, destroyed, and resized.
  2. When resizing a body (e.g. to duck), Box2D can exhibit non-physical behavior, like bouncing it away from a surface it was already in contact with. To resolve this problem, ignore any updates from existing contact edges for one frame after the resize.

The latter change looks like this:

/*
 * Resizing the body sometimes results in our slight 
 * overlap with the floor, resulting in Box2D detecting 
 * a collision and bouncing the body upwards. To get 
 * around this, we just disable any existing contact 
 * edges with terrain for the next timestep.
 */
ContactEdge edge = _body.ContactList;
while ( edge != null ) {
    if ( edge.Contact.FixtureA.GetUserData().IsTerrain 
        || edge.Contact.FixtureB.GetUserData().IsTerrain ) {
        edge.Contact.Enabled = false;
    }
    edge = edge.Next;
}

Hopefully someone find these tips useful. For what it’s worth, if I were starting the project again today, I wouldn’t use a physics engine — my game doesn’t need realistic physics. What it does need is a collision engine, which Box2D does a decent job at. Unfortunately, it’s really hard to just take that one piece without grabbing more, and even that one piece is unreliable when bodies are frequently created, destroyed, or resized. Since these things happen pretty often in many platformers, I can’t really recommend Box2D (or maybe just Farseer) for this use case.

One thought on “So can I jump or not? Determining if the ground is under your feet in Box2D

  1. That’s a neat looking game – I like how the slimes go around the walls 🙂

    For the record, the original C++ version does not have the problem that you mention here: “counting collisions and separations with the character’s feet isn’t accurate in all cases”. However, there is an odd quirk in that destroying fixtures causes the EndContact to fire immediately, whereas BeginContact does not fire until the first Step call after creating a new fixture. This can make it awkward to keep track of a lot of fast changing states.

    After resizing a fixture, you can call b2Fixture::Refilter to mark it to have its contacts updated in the next Step. This is necessary if the fixture AABB is now touching something it was not before (or vice-versa) and the body it is attached to is not moving fast enough to cause the AABB to update naturally.

    However, there is no ‘fix’ for the non-physical behavior that occurs when resizing fixtures, because this is not something that rigid bodies were ever intended to do, and in the real world things do not instantaneously change sizes either. The only way you could reduce this effect is by changing the size gradually.

Leave a Reply