Wednesday, November 18, 2015

java - How do I use a JBox2D ContactListener to check if my character is standing on the ground?


I want to know when my character is touching a wall or floor. I've had no luck searching for information on this. I saw a few things about ContactListeners and getContactList(), but I don't understand how to use them.


How can I do this?



Answer



I recently implemented a similar detection in a platformer using Box2D, to know if my player body was touching the ground (to allow the player to jump—or not). There are many ways to do it, but here is my way.


Give your character body two fixtures; a solid one and a sensor. (If you're unfamiliar with the concepts, read the relevant manual chapter.) Arrange them like this:


positioning of solid body and sensor


Sensor


From the official manual:




Sometimes game logic needs to know when two fixtures overlap yet there should be no collision response. This is done by using sensors. A sensor is a fixture that detects collision but does not produce a response.



Here, the sensor (in blue) will be used to detect if something is "under the feet" of the body and will lead to no collision response (it's a sensor). Collision responses are handled by the solid fixture (or main fixture; in red).


To manipulate fixtures through contact listeners, you need a way to know if a given fixture is actually the relevant one. A simple way to do this setting the fixture sensor's user data (you can set user data on both fixtures and bodies). Here, a simple integer should do the job:


feetSensor.setUserData(FEET_SENSOR_ID);

Later on in the listener, to check if a fixture is the sensor, you simply do:


if ((Integer) feetSensor.getUserData() == FEET_SENSOR_ID) {
// This is the feet sensor
}


Contact Listener


Since the sensor is at the bottom of the body, if there is at least one contact between it and any other fixture, you can safely assume that the body is "on the ground".


A contact listener is used to listen to contacts occurring in the world. Each time two fixtures contact each other (touch), the beginContact method is called with a Contact instance as parameter, containing data about the contact. From this Contact instance, you can use getFixtureA() and getFixtureB() methods to get the two fixtures involved in the contact:


Fixture fA = contact.getFixtureA()
Fixture fB = contact.getFixtureB()

Once you have the two fixtures, you need to check if this contact is relevant. In our scenario, a contact is relevant if one of the two fixtures is the feet sensor. This method returns whichever (or neither) of its arguments is the feet sensor:


private Fixture getFeetFixture(Fixture f1, Fixture f2) {
if (f1.getUserData() != null && (Integer) f1.getUserData() == FEET_SENSOR_ID) {

return f1;
} else if (f2.getUserData() != null && (Integer) f2.getUserData() == FEET_SENSOR_ID) {
return f2;
}
return null;
}

Then, each time you encounter a relevant contact in the beginContact method, you can, for example, increment a static counter variable to keep track of how many fixtures contact the feet sensor.


Finally, you still need to handle the case where two fixtures are no longer in contact. Use the endContact method just like you used beginContact. This method also receives a Contact instance as parameter. After checking whether the contact involves the feet sensor as before and finding it to be so, you decrement the static counter variable (reducing the number of fixtures the sensor is in contact with, by one).


You can then check if the counter variable is greater than zero to know whether the player character is on the ground.



Here's a complete implementation of such a contact listener:


public class FeetContactListener implements ContactListener {
private static int nbFootContacts = 0;

private Fixture getFeetFixture(Fixture f1, Fixture f2) {
if (f1.getUserData() != null && (Integer) f1.getUserData() == FEET_SENSOR_ID) {
return f1;
} else if (f2.getUserData() != null && (Integer) f2.getUserData() == FEET_SENSOR_ID) {
return f2;
}

return null;
}

@Override
public void beginContact(Contact contact) {
Fixture feetFixture;

if ((feetFixture = getFeetFixture(contact.getFixtureA(),
contact.getFixtureB())) != null) {
nbFootContacts++;

if (nbFootContacts > 0) {
//touching the ground
}
}

@Override
public void endContact(Contact contact) {
Fixture feetFixture;

if ((feetFixture = getFeetFixture(contact.getFixtureA(),

contact.getFixtureB())) != null) {

nbFootContacts--;
if (nbFootContacts <= 0) {
//not touching the ground
}
}
}

The last step is to register the contact listener with the physics World.



world.setContactListener(new FeetContactListener());

I find this way quite flexible, since you can add as many fixtures as you want (and hence, as many sensors as you want). Add a sensor for parts of the body that need specific collision handling.


Resources



No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...