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.
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.
In the next lines, you will find the content of the Box2DCollisionReactionSample.java
file broken down into nine steps:
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.ChainShape
class with the following vertices: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;
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.
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);
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(); }
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.
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.
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(); } } }
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.
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();
3.141.192.120