Reacting to collisions

Box2D takes collision response to an awesome level of realism where bodies act exactly as expected in most cases. However, there are a lot of situations within a game that demand custom collision dealing, such as receiving damage, controlling a ball, or simply pushing enemies.

After reading this recipe, you will easily be able to manage those special circumstances that will surely appear in your game.

Getting ready

The sample code will implement a balloon-breaker mini-game where a shuriken will follow your cursor looking for fragile balloons to destroy before they leave the scene dimensions.

You can find the code within the Box2DCollisionReactionSample.java file. Remember to import sample projects.

How to do it…


In the next lines, you will find the content of the Box2DCollisionReactionSample.java file broken down into nine steps:

  1. Initialize Viewport, SpriteBatch, World, Box2DDebugRenderer, and a groundBody for our scene. Feel free to go back to the first recipe of this chapter, Introduction to Box2D, if you need to refresh your memory.
  2. Create a big shuriken kinematic body using the ChainShape class with the following vertices:
    How to do it…

  3. Once the body is created, it must follow the cursor. You can take two separate paths to carry out this mission depending on the required degree of precision:
    • High precision: In this, we set the shuriken body type to the kinematic body and update its position manually.
    • Low precision: In this, we use a MouseJoint, updating its target position to the cursor location if necessary and applying a force to reach that point. The shuriken must be implemented through a dynamic body in this case.

    The first path is more precise because the position of the body is manually set to a certain point while the second one relies on a force to push the body to the target point.

    As we have not used a kinematic body yet, we will take the high precision approach. So make sure you set the proper body type in FixtureDef:

    shurikenBodyDef.type = BodyType.KinematicBody;
  4. We have to update the shuriken body position within the overridden mouseMoved(…) function:
    public boolean mouseMoved(int screenX, int screenY) {
      // Translate into world coordinates
      viewport.getCamera().unproject(point.set(screenX, screenY, 0));
      pointerBody.setTransform(point.x, point.y, pointerBody.getAngle() + 5 * MathUtils.degreesToRadians);
    
      return true;
    }

    Since we have to specify an angle, let's add some visual interaction with a small rotation.

  5. Up until now, we have only defined one of the two main actors of this example. Balloons will be instantiated within the screen bounds with the createBalloon() function, which will basically create a balloon body that contains a fixture with a precise shape created with the physics editor mentioned in the Introducing more complex shapes recipe. Each of these bodies will carry a flag, false by default, to indicate whether it should be deleted as soon as possible. This is due to the need to delete physics elements outside of the simulation process as you should not delete objects that the World instance is working with. Otherwise, your game might crash.

    The physics properties for balloons will be:

    float balloonDensity = 0.0099999f;
    float balloonFriction = 0.90f; 
    float balloonRestitution = 0.0f;

    To simplify this, we will store the flag under the UserData field:

    balloonBody.setUserData(false);
  6. Every second, we will instantiate a new balloon within the render() function thanks to the time variable, which is originally set to zero:
    float delta = Gdx.graphics.getDeltaTime();
    if((time += delta) >= 1f) { //Every second
      time-=1f;
      createBalloon();
    }
  7. Right after this piece of code, we will apply a buoyancy force to each and every balloon to make them look more realistic:
    for(Body balloon : balloons) // Keep balloons flying
      balloon.applyForceToCenter(buoyancyForce, true);

    In order to calculate buoyancyForce (yes, air is physically a fluid), we need to obtain some other variables first because the resulting formula is:

    Vector2 buoyancyForce = new Vector2(0f, displacedMass * 9.8f);

    The calculation of displacedMass needs the area, but for simplicity's sake, we will consider the balloon as an ellipse (A = PI * semi-major axis * semi-minor axis):

    private static final float BALLOON_WIDTH = 0.5f;
    private static final float BALLOON_HEIGHT = 0.664f;
    private static final float BALLOON_AREA = MathUtils.PI * BALLOON_WIDTH * 0.5f * BALLOON_HEIGHT * 0.5f;

    We will set the airDensity value to 0.01 and consequently, the displacedMass value:

    float displacedMass = BALLOON_AREA * airDensity;

    As theoretical explanation about physics is not the goal of this book, I strongly recommend you to go to http://www.iforce2d.net/b2dtut/buoyancy if you are interested in a deeper explanation.

  8. At this point, we have a scene where the shuriken pushes balloons when colliding. As we want to manually deal with those collisions, our sample class will implement the ContactListener interface. We must also inform to the World object of this:
    world.setContactListener(this);

    This will force us to define some functions. For now, we will focus on BeginContact(…) because we need to know exactly when two fixtures start touching. The rest of the functions will be covered in the How it works… section. Take into account that we must consider both contact points when checking the two fixtures:

    public void beginContact(Contact contact) {
      Fixture fixtureA = contact.getFixtureA();
      Fixture fixtureB = contact.getFixtureB();
    
      Body bodyA = fixtureA.getBody();
      Body bodyB = fixtureB.getBody();
    
      if(bodyA == pointerBody && bodyB != groundBody)
        bodyB.setUserData(true);
      else if(bodyB == pointerBody && bodyA != groundBody)
        bodyA.setUserData(true);
    }

    What we do here is consult which fixtures have taken part in the contact and if one of them is the shuriken and the other is not the ground, it means that the second is a balloon. All we have to do is mark it as pending to be deleted at the end of the current step of the simulation.

  9. The last line of the render() function should be responsible for cleaning the house by calling freeBalloons(). It will just loop over the balloons container and remove those that are set as broken (false flag) or have simply flown away from the screen limits:
    void freeBalloons() {
      Iterator<Body> i = balloons.iterator();
      while (i.hasNext()) {
        Body balloon = i.next();
        boolean broken = (Boolean) balloon.getUserData();
    
        if(((balloon.getPosition().y - BALLOON_HEIGHT*0.5f) > SCENE_HEIGHT) || // Top limit/*Bottom limit*/ ||/*Right limit */ ||/*Left limit  */ ||broken) {
          world.destroyBody(balloon);
          i.remove();
        }
      }
    }

How it works…

Classes that implement the ContactListener interface will be able to listen for overlapping fixtures at every stage of the collision anatomy through these functions:

  • void beginContact (Contact contact): This function is called once when two fixtures overlap and cannot be called again over the same ones until endContact(…) has finished. You cannot disable the contact here.
  • void endContact (Contact contact): This function is called when two fixtures are no longer touching.
  • void preSolve (Contact contact, Manifold oldManifold): This function gives you the opportunity to manually deal with the contact between two bodies instead of delegating to Box2D, which will just calculate the collision response according to their physics properties. You can disable the contact here.
  • void postSolve (Contact contact, ContactImpulse impulse): This function allows us to get the characteristics of the collision response once it is done.

Whenever the system detects a contact within a simulation step, BeginContact is called, and from now on, PreSolve and PostSolve will be called respectively on each simulation step until EndContact is executed.

You can find a deeper explanation together with some examples at http://www.iforce2d.net/b2dtut/collision-anatomy.

There's more…

The Contact object stores the necessary information to know what fixtures are colliding. But it can also disable the Box2D future response by:

contact.setEnabled(false);

This is a temporary flag because it will be reset in the next timestep. This is the reason why it only works in presolve as it gets called as long as the fixtures are colliding.

You can also modify friction and restitution properties for the contact by calling these functions:

contact.setFriction(0.1f);
contact.setRestitution(0.3f);

The Manifold object stores information about the contact point in local coordinates. Use WorldManifold to get it in the world coordinates. You can retrieve such information with:

manifold.getPoints();

It might be useful to know whether a bullet has impacted the chest, the head, or the leg so you can act as a consequence.

Finally, you can query a world object for all the contact points with:

world.getContactList();

See also

  • The Sensors and collision filtering recipe
  • The Querying the world recipe
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.192.120