1

So I've been playing around with box2d on iOS (thought this question isn't really limited to iOS), and I've got a small demo going where a player character jumps when the user taps the screen via ApplyLinearImpulse.

For the most part, it works. Except if I tap while the player is in midair, ApplyLinearImpulse is called again, and the player "jumps" again, but in midair. Now, it'd be pretty neat to do that in real life, but you can't, and I'd prefer my player not be able to do that either.

So I've been trying to come up with a decent way to prevent jumping while a player is already jumping, and I'm not sure where to go from here - my best thought is to try something like this:

  • Attach a body that is just a sensor to the bottom of my player with a fixed joint.
  • When the player jumps, disable jumping until the aforementioned sensor detects a collision (i.e., when the player "lands" on something, which may or may not be the ground).
  • Once a collision has been detected, re-enable jumping.

I'm not sure how much I like this idea, for one, I'm not sure if attaching a sensor to the bottom of my player is the "smartest" way of detecting collisions on the bottom of my player sprite. I haven't given this a try yet, I wanted to instead get SO's input on it and see if anyone could provide a better alternative. Any ideas?

Edit: mafutrct had good a suggestion: try some sort of downward hit test, using a ray straight downward from my player's body, and see if that ray intersects another object within a short distance (i.e., right below my player) - is that type of ray-casting hit test possible with box2d?

1

3 Answers 3

5

My vote goes to the 'sensor at the player's feet' method. You could make the sensor fixture an edge if you really want a ray type check, but generally I think it's more useful to be able to specify an area, especially if your character will be able to stand on more than one thing at once, and if you are interested in keeping an up-to-date list of what's being stood on. If you are creating the player's body in a graphical editor then it will also be more intuitive to place and look at the sensor instead of having it all running programmatically with raycasts. I believe the foot sensor method is also less cpu cost overall, and less work than implementing raycast checks yourself. I wrote a detailed tutorial about it, I hope it's useful: http://www.iforce2d.net/b2dtut/jumpability

1
  • I used your tutorial (although I found the link elsewhere) and reckon that this approach seems solid and has worked for me.
    – DonnaLea
    Commented Aug 15, 2011 at 4:05
3

You need to check if the feet touch the ground at the moment of jumping.

Depending on your model, measure the distance between your feet and the ground. Either apply a vector from the model center and check for a vertex hit, or (more sophisticated) check for the first vertical hit of a polygon describing your feet area with the ground.

Simply checking for any collision fails since you may jump against a wall, which still counts as midair (unless you play Unreal Tournament, in which it is a wall jump).

I am not familiar with your software though, so this is just an idea - would be great if it is actually helpful. Good luck :)

5
  • Hmm, the problem with checking against only the ground is that the player might very well land on something that is above the ground, i.e., a box that is sitting on the ground. I'm not sure that will be sufficient.
    – Rob
    Commented Apr 18, 2011 at 13:17
  • 1
    Yes, you need to perform a hit test downwards. If the distance to the hit is small, you're touching the ground. If it is big, you're still in the air.
    – mafu
    Commented Apr 18, 2011 at 13:33
  • 1
    The question, as described in the answer above, would be how to perform the hit test - do you want to test only a single point, usually a ray straight downward from the center of your model, or do you want to use a more sophisticated test (i.e. a polygon)? I assume the former suffices, as you can still extend and improve it later if necessary.
    – mafu
    Commented Apr 18, 2011 at 13:36
  • Yes - I think that makes much more sense. Do you know if box2d supports some sort of hit test, like what you're describing? That sounds like exactly what I need.
    – Rob
    Commented Apr 18, 2011 at 13:39
  • 1
    I'm sorry, but I don't know that. I hope you can find a way (possibly google?) - or maybe create another question on SO :)
    – mafu
    Commented Apr 18, 2011 at 13:49
2

The way to solve this problem is to create a class that implements ContactListener and then when you create the World, you .setContactListener() to your class. Now your class will be notified of all collisions. From there you create a flag for if the player is in contact with any ground objects.

Assume that you've set the shapes' userData to the relevant GameObjects (shapeDef.userData = this; in GameObject constructor). Assume you have an enum that defines the types of GameObject in your game, then you have a contact listener along these lines:

import org.jbox2d.dynamics.ContactListener;
import org.jbox2d.dynamics.contacts.ContactPoint;
import org.jbox2d.dynamics.contacts.ContactResult;
import android.util.Log;

import GameObject.GAME_OBJECT_TYPE;

public class CollisionChecker implements ContactListener {

    private boolean groundCollision = false;

    @Override
    public void add(ContactPoint arg0) {
        // Called when a contact point is added. This includes the geometry and the forces.
    }

    @Override
    public void persist(ContactPoint arg0) {
        // Called when a contact point persists. This includes the geometry and the forces.
    }

    @Override
    public void remove(ContactPoint arg0) {
        // Called when a contact point is removed. This includes the last computed geometry and forces.
    }

    @Override
    public void result(ContactResult arg0) {
        // This is called once the collision has been resolved 

        GAME_OBJECT_TYPE a = ((GameObject) arg0.shape1.getUserData()).getType();
        GAME_OBJECT_TYPE b = ((GameObject) arg0.shape2.getUserData()).getType();
        //check for ground
        groundCollision = check(a, b, GAME_OBJECT_TYPE.ground);
    }

    private boolean check(GAME_OBJECT_TYPE a, GAME_OBJECT_TYPE b, GAME_OBJECT_TYPE test){
        if (a == test && b == GAME_OBJECT_TYPE.player){
            return true;
        }
        else if (a == GAME_OBJECT_TYPE.player && b == test){
            return true;
        }
        return false;
    }

    public boolean isGroundCollision(){
        return groundCollision;
    }

    public void step() {
        /*
        if (groundCollision)
            Log.d("COLLSN", "We have a collision with ground");*/
        //after logic, reset the state variables
        reset();
    }

    private void reset(){
        groundCollision = false;
    }
}

Now you create a jump boolean in your player. When he jumps, set it to true. He can no longer jump when the flag is true. Test every frame for a collision with ground with 'collisionChecker.isGroundCollision()'. When a collision occurs, set player.jump = false again.

There is a problem here in that if you have vertical ground, the player will be able to walljump. If the player hits his head against ground he will also be able to jump more. To solve these issues you check the 'arg0.position' to find out where the point of contact was in relation to the player object. If the contact is not underneath the player, then he's hitting his head or hitting a wall.

2
  • I see two problems here. Firstly, Box2D fixtures can be touching more than one other fixture at a time, so storing a boolean is not sufficient - you'll need to increment and decrement a count of how many are currently touching. Also, the remove() should be used to decrement the count - in the code above it seems like groundCollision would never be set back to false.
    – iforce2d
    Commented Jun 17, 2011 at 2:48
  • This solution is working and you don't have to count collisions. Before calling 'world.step()' assume 'groundCollision' to be false. The 'ContactListener' has to check if the player collides on this loop. I do this in the 'preSolve' method to set 'groundCollision' to true. Now you can disable jumping for this loop and start over again. Commented Dec 29, 2013 at 14:56

Not the answer you're looking for? Browse other questions tagged or ask your own question.