Search code examples
javaandroidandroid-studiolibgdxbox2d

Object is not fully destroyed upon collision using Box2D with Libgdx on Android studio. Sprite can't go through destroyed object completely


I am developing a Super Mario Bros game in Android Studio using Libgdx and so far I have not encountered any errors and the project runs fine. However, when I use category and mask filters from Box2D to "destroy" an object upon collision with a sprite (when Mario jumps and his head hits the brick which results in the brick breaking), although the brick gets "destroyed", Mario can only jump through what used to be the brick, until his head reaches the upper layer of the brick (the top side of the object's square outline). Here are some screenshots: Screenshot before collision with brick

Screenshot after collision with brick As you can see, Mario cannot jump higher than the upper faint green line which denotes the top part of the destroyed brick.The collision detection is working fine because the I wrote some code for the log to display "Brick: Collision" if there is a collision detected with the brick, and this comes out positive the first time he collides with the brick - as it should do. Thus my question is raised, why does he not jump higher than the top part of the destroyed brick when he should be able to jump freely as if an object isn't there at all. Currently, he is bounced off the top part of the square so he can't go higher. Here is the relevant code:

Main class:

public static final short DEFAULT_BIT = 1;
public static final short MARIO_BIT = 2;
public static final short BRICK_BIT = 4;
public static final short COIN_BIT = 8;
public static final short DESTROYED_BIT = 16;

InteractiveTileObject class:

public abstract void onHeadHit();
public void setCategoryFilter(short filterBit){
    Filter filter = new Filter();
    filter.categoryBits = filterBit;
    fixture.setFilterData(filter);
}
public TiledMapTileLayer.Cell getCell(){
    TiledMapTileLayer layer = (TiledMapTileLayer) map.getLayers().get(1);
    return layer.getCell((int)(body.getPosition().x * SuperMarioBros.PPM / 16),
            (int)(body.getPosition().y * SuperMarioBros.PPM / 16));

Mario sprite class:

FixtureDef fdef = new FixtureDef();
    CircleShape shape = new CircleShape();
    shape.setRadius(6 / SuperMarioBros.PPM);
    fdef.filter.categoryBits = SuperMarioBros.MARIO_BIT;
    fdef.filter.maskBits = SuperMarioBros.DEFAULT_BIT | SuperMarioBros.COIN_BIT | SuperMarioBros.BRICK_BIT ;

    fdef.shape = shape;
    b2body.createFixture(fdef);

    FixtureDef fdef2 = new FixtureDef();
    EdgeShape feet = new EdgeShape();
    feet.set(new Vector2(-2 / SuperMarioBros.PPM, -6 / SuperMarioBros.PPM), new Vector2(2 / SuperMarioBros.PPM, -6 / SuperMarioBros.PPM));
    fdef2.shape = feet;
    b2body.createFixture(fdef2);

    EdgeShape head = new EdgeShape();
    head.set(new Vector2(-2/SuperMarioBros.PPM, 6/SuperMarioBros.PPM), new Vector2(2/SuperMarioBros.PPM, 6/SuperMarioBros.PPM));
    fdef.shape = head;
    fdef.isSensor = true;

    b2body.createFixture(fdef).setUserData("head");

Brick sprite class:

public class Brick extends InteractiveTileObject {
public Brick(World world, TiledMap map, Rectangle bounds) {
    super(world, map, bounds);
    fixture.setUserData(this);
    setCategoryFilter(SuperMarioBros.BRICK_BIT);
}

@Override
public void onHeadHit() {
    Gdx.app.log("Brick", "Collision");
    setCategoryFilter(SuperMarioBros.DESTROYED_BIT);
    getCell().setTile(null);
    }
}

Please help I have been struggling with this issue for quite a while now and have not found a solution anywhere online


Solution

  • I solved this issue by applying the filter.maskBits and filter.categoryBits filters to fdef2 which is Mario's feet. I literally copy pasted:

    fdef.filter.categoryBits = SuperMarioBros.MARIO_BIT; fdef.filter.maskBits = SuperMarioBros.DEFAULT_BIT | SuperMarioBros.COIN_BIT | SuperMarioBros.BRICK_BIT ;

    and renamed fdef to fdef2 so my code now looks like this:

    FixtureDef fdef = new FixtureDef();
        CircleShape shape = new CircleShape();
        shape.setRadius(6 / SuperMarioBros.PPM);
        fdef.filter.categoryBits = SuperMarioBros.MARIO_BIT;
        fdef.filter.maskBits = SuperMarioBros.DEFAULT_BIT | SuperMarioBros.COIN_BIT | SuperMarioBros.BRICK_BIT ;
    
        fdef.shape = shape;
        b2body.createFixture(fdef);
    
        FixtureDef fdef2 = new FixtureDef();
        EdgeShape feet = new EdgeShape();
        feet.set(new Vector2(-2 / SuperMarioBros.PPM, -6 / SuperMarioBros.PPM), new Vector2(2 / SuperMarioBros.PPM, -6 / SuperMarioBros.PPM));
        fdef2.filter.categoryBits = SuperMarioBros.MARIO_BIT;
        fdef2.filter.maskBits = SuperMarioBros.DEFAULT_BIT | SuperMarioBros.COIN_BIT | SuperMarioBros.BRICK_BIT ;
        fdef2.shape = feet;
        b2body.createFixture(fdef2);
    
        EdgeShape head = new EdgeShape();
        head.set(new Vector2(-2/SuperMarioBros.PPM, 6/SuperMarioBros.PPM), new Vector2(2/SuperMarioBros.PPM, 6/SuperMarioBros.PPM));
        fdef.shape = head;
        fdef.isSensor = true;
    
        b2body.createFixture(fdef).setUserData("head");
    

    and this seems to work although I'm not too sure why? I think it's because now his feet can pass through the bottom of the square as opposed to his head not being able to pass through the top. I believe the root cause of the problem was that his feet detected an object when the head didn't so Mario couldn't get his feet to go through the square.

    ---EDIT--- I realized I don't even need a fixture for his feet so deleting all code relating to fdef2 works accordingly and produces the result it should.