2
\$\begingroup\$

I've been creating a game on my own in LIBGDX, BOX2D and there' s a problem I can' t solve. When two World bodies collide, method "beginContact" is called in MyContactListener class that implements ContactListener interface.

How am I supposed to change player's healthpoints when it's fixture collides with other bodies' fixtures. I know I have to do this somehow through MyContactListener class, but I can' t find the way how to call the method I need..

Note: My player's body and fixture are created in "Player" class and there are methods "setRemainingHealth()" and "getRemainingHealth()".

Here's also some code, which might illuminate this situation.

MyContactListener.java

package utils;

import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.ContactImpulse;
import com.badlogic.gdx.physics.box2d.ContactListener;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.Manifold;

public class MyContactListener implements ContactListener {

@Override
public void beginContact(Contact c) {

    Fixture fa = c.getFixtureA();
    Fixture fb = c.getFixtureB();

    if (fa == null || fb == null) return;
    if (fa.getUserData() == null || fb.getUserData() == null) return;

    if (isTunnelMachineCollision(fa, fb)) {
        // call somehow method "player.setRemainingHealthPoints(player.getRemainingHealthPoints-0.01f)" here
    }

}

@Override
public void endContact(Contact c) {

}

@Override
public void postSolve(Contact c, ContactImpulse arg1) {

}

@Override
public void preSolve(Contact arg0, Manifold arg1) {
    // TODO Auto-generated method stub

}

private boolean isTunnelMachineCollision(Fixture a, Fixture b) {
    return (a.getUserData()) == "terrain" && (b.getUserData()) == "machine";
}



}



TestPlayer.java

package testPackage;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.CircleShape;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.World;

public class TestPlayer {

private float remainingHealthPoints = 3;


private CircleShape shape;
private CircleShape defenseBarrier;
private Fixture defenseBarrierFixture;
private Fixture mainBody;
private BodyDef bodydef;
private Body body;

private float x;
private float y;
private float width;


public TestPlayer(World world, float x, float y, float width) {

    this.x = x;
    this.y = y;
    this.width = width;

    bodydef = new BodyDef();
    bodydef.type = BodyType.DynamicBody;
    bodydef.position.set(x, y);
    bodydef.fixedRotation = true;
    body = world.createBody(bodydef);

    shape = new CircleShape();
    shape.setRadius(width);
    mainBody = body.createFixture(shape, 1.0f);
    mainBody.setUserData("machine");
}

public float getRadius() {
    return shape.getRadius();
}

public BodyDef getBodydef() {
    return bodydef;
}

public CircleShape getShape() {
    return shape;
}

public Body getBody() {
    return body;
}

public void activateDefenseBarrier(World world) {

    defenseBarrier = new CircleShape();
    defenseBarrier.setRadius(width*1.5f);

    defenseBarrierFixture = body.createFixture(defenseBarrier, 1.0f);
    defenseBarrierFixture.setUserData("defenseBarrier");
}

public void destroyDefenseBarrier() {
    if (defenseBarrierFixture != null) {
        System.out.println("olemas");
        body.destroyFixture(defenseBarrierFixture);
        System.out.println(defenseBarrierFixture);
    }
    else {
        System.out.println("fixture on null");
    }
}   

public Fixture getDefenseBarrier() {
    return defenseBarrierFixture;
}

public float getRemainingHealthPoints() {
    return remainingHealthPoints;
}

public void setRemainingHealthPoints(float remainingHealthPoints) {
    this.remainingHealthPoints = remainingHealthPoints;
}   


}
\$\endgroup\$
0
\$\begingroup\$

Instead of setting the user data of your Body to a string like "machine", set it to the TestPlayer instance.

mainBody = body.createFixture(shape, 1.0f);
//mainBody.setUserData("machine"); don't do this
mainBody.setUserData(this); do this instead

That way, when you've identified that the colliding bodies are of the types you care about, you have an instance to the object you want to manipulate.

Something like this might work for you;

@Override
public void beginContact(Contact c) {

    Fixture fa = c.getFixtureA();
    Fixture fb = c.getFixtureB();

    if (fa == null || fb == null) return;


    Object udA = fa.getUserData();
    Object udB = fb.getUserData();  

    TestPlayer testPlayer = null;

    if (udA != null && udA instanceof TestPlayer) {
        testPlayer = (TestPlayer)testPlayer;
    }

    if (udB != null && udB instanceof TestPlayer) {
        testPlayer = (TestPlayer)testPlayer;
    }

    if (testPlayer != null) {
        testPlayer.setHitPoints(testPlayer.getHitpoints() - 1); // or something like that
    }
}

As your world gets more entities it might make sense to let the user data of the Body to be some wrapper object that holds both the ID and the instance of an object, for example;

public class UserDataWrapper {
    public final String id;
    public final Object instance;
    public UserDataWrapper(String id, Object instance) {
        this.id = id;
        this.instance = instance;
    }
}


// when creating your bodies in some class like your TestPlayer
body.setUserData(new UserDataWrapper("PLAYER 1", this));    
\$\endgroup\$
  • \$\begingroup\$ I agree with the wrapper and how the user data is set. However I would personally avoid having specific instance checks in the contact listener. It has to be generic and any instance specific code should be avoided. Instead the instance should be called (or maybe a component of the instance) which should deal with the specifics. That will avoid unmaintainable code. \$\endgroup\$ – RdeGier Dec 13 '15 at 10:12
  • \$\begingroup\$ @RdeGier I went for the hit point setting inline in the contact listener for brevity here. If there are several different types of entities that can collide I would probably let the user data be of a type that implements some Collidable interface with a collided (Collidable colludedWith) method. That way the action that happens on collision would be local to the entity colliding. \$\endgroup\$ – bornander Dec 13 '15 at 10:46
  • \$\begingroup\$ Wow, I wonder why I didn't look on my problem from that angle. I truly-truly thank you for helping me with this problem. I can finally get my game working :) \$\endgroup\$ – Carl Dec 13 '15 at 11:50
  • \$\begingroup\$ @Carl Glad I could help. \$\endgroup\$ – bornander Dec 14 '15 at 6:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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