Recommended reading: Ghost Collisions on the official Box2D Website
Most of the time, ghost collisions will not be an issue - especially if you're using chain shapes, as recommended in the Box2D manual. However, there may be times where these collisions happen naturally; an example of this would be a gap in the ground your player character walks on, said gap wouldn't be wide enough for him to fall through, but would still generate a collision response - and this can cause issues if you have logic tied to it.
If, for whatever reason, Box2D registers two collisions instead of one, and therefore calls
@Override
public void beginContact(Contact contact) {
Body bodyA = contact.getFixturaA().getBody();
Body bodyB = contact.getFixturaB().getBody();
Object dataA = bodyA.getUserData();
Object dataB = bodyB.getUserData();
if (dataA.equals("Player") && dataB.equals("Ground") || dataA.equals("Ground") && dataB.equals("Player")) {
// player logic to execute when the ground has been touched
}
}
@Override
public void endContact(Contact contact) {
Body bodyA = contact.getFixturaA().getBody();
Body bodyB = contact.getFixturaB().getBody();
Object dataA = bodyA.getUserData();
Object dataB = bodyB.getUserData();
if (dataA.equals("Player") && dataB.equals("Ground") || dataA.equals("Ground") && dataB.equals("Player")) {
// player logic to execute when the ground stopped being touched
}
}
ContactListener.beginContact()
& ContactListener.endContact()
twice, would your logic still be executed in the correct order? What would happen if ContactListener.endContact()
is fired before ContactListener.beginContact()
because of a ghost collision? There's a good chance your logic will break!
The crux of the issue is the fact that multiple collisions are being registered even when they shouldn't - and the best way to fix that is by using chain shapes to build your game's world, as recommended in this excellent article on the official Box2D Website. But if this can't be done for whatever reason, then the following should help:
It's almost identical to the previous block of code, with one big difference - we keep track of the amount of times our player begins contact with the ground by increasing the
byte playerContactsWithGroundCounter = 0;
@Override
public void beginContact(Contact contact) {
Body bodyA = contact.getFixturaA().getBody();
Body bodyB = contact.getFixturaB().getBody();
Object dataA = bodyA.getUserData();
Object dataB = bodyB.getUserData();
if (dataA.equals("Player") && dataB.equals("Ground") || dataA.equals("Ground") && dataB.equals("Player")) {
playerContactsWithGroundCounter++; // increase the counter
}
if (playerContactsWithGroundCounter == 1) {
// player logic to execute when the ground has been touched
}
}
@Override
public void endContact(Contact contact) {
Body bodyA = contact.getFixturaA().getBody();
Body bodyB = contact.getFixturaB().getBody();
Object dataA = bodyA.getUserData();
Object dataB = bodyB.getUserData();
if (dataA.equals("Player") && dataB.equals("Ground") || dataA.equals("Ground") && dataB.equals("Player")) {
playerContactsWithGroundCounter--; // decrease the counter
}
if (playerContactsWithGroundCounter == 0) {
// player logic to execute when the ground stopped being touched
}
}
playerContactsWithGroundCounter
variable, and we do the opposite for when our player ends contact with the ground by decreasing the very same variable. Immediately after that, we check its value to see if our logic can be executed with:
if (playerContactsWithGroundCounter == 1) { // logic }
in ContactListener.beginContact()
if (playerContactsWithGroundCounter == 0) { // logic }
in ContactListener.endContact()
if
blocks, we execute the logic for when the collision begins & ends. The counter is in place to make sure the logic is executed only once for each collision response generated, therefore ignoring ghost collisions!
Regardless, be careful if you do decide to implement this solution, as it's a double-edged sword. There could be times where a collision response is generated while your player is already touching the ground - like in the gap example at the start - and this fix would completely obfuscate said collision, that you may want to handle. So watch out!