LibGDX Stop body from taking properties of previou

2019-07-31 17:53发布

问题:

VIDEO FOR CLARITY: https://www.youtube.com/watch?v=3Mrro8sjcqo#t=25

I'm making my first LibGDX game, and I'm having trouble destroying and creating bodies. I've been using this snippet (because it seems to be everywhere) to remove bodies:

private void sweepDeadBodies() {
    for (Iterator<Body> iter = tmpBodies.iterator(); iter.hasNext();) {
        Body body = iter.next();
        if (body != null) {
            Entity data = (Entity) body.getUserData(); //Just the bodies data
            if (data.isFlaggedForDelete) {
                iter.remove();
                world.destroyBody(body);
                body.setUserData(null);
                body = null;
            }
        }
    }
}

That works great, just as intended, but shortly after running it, it crashes. I get the whole "AL lib: (EE) alc_cleanup: 1 device not closed" thing. I did some debugging and came to the conclusion that it was crashing when one of my entities fires a projectile, after an object is destroyed. Its a very peculiar problem. The entity that was destroyed was continuously going in a slow circle. After being destroyed, when the next projectile is fired, it will appear as normal, but not move, and take on that same slow circular movement of the entity it destroyed. If the player fires again, it crashes. I'm stumped. Any thoughts?

Here is source code in question

/*Render Loop in screen:*/    

public void render(float delta) {
    Gdx.gl.glClearColor(0, 0, 0, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);



    // Update World, Camera, and State Time
    world.step(TIMESTEP, VELOCITYITERATIONS, POSITIONITERATIONS);
    sweepDeadBodies();
    camera.update();
    stateTime += delta;

    // Get Fire Buttons
    System.out.println(player.stateTime % 2);
    if (aButton.isPressed() && player.stateTime / 0.25 >= 1) {
        player.stateTime = 0f;
        //Projectile in question
        Laser laser = new Laser(world, player.getBody().getPosition()); 
    }

    // Update the player
    player.update(joyStick.getKnobPercentX(), joyStick.getKnobPercentY(),
            delta);
    enemy.update(delta);

    // Order the bodies
    world.getBodies(tmpBodies);
    Iterator<Body> iterator = tmpBodies.iterator();
    int j;
    boolean flag = true; // set flag to true to begin first pass

    while (flag) {
        flag = false; // set flag to false awaiting a possible swap
        for (j = 0; j < tmpBodies.size - 1; j++) {
            if (tmpBodies.get(j).getType() == BodyType.DynamicBody
                && tmpBodies.get(j + 1).getType() == BodyType.StaticBody) {

                tmpBodies.swap(j, j + 1);
                flag = true;

            }
        }
    }

    /*ADDED THIS, FORGOT TO LEAVE WHEN TRIMMING CODE FOR POST*/
    for (Body body : tmpBodies) {

        // Entity
        if (body.getUserData() instanceof Entity) {
            Entity entity = (Entity) body.getUserData();

            if (entity.sprite != null) {
                entity.sprite.setPosition(body.getPosition().x
                        - entity.sprite.getWidth() / 2,
                        body.getPosition().y - entity.sprite.getHeight()
                                / 2);
                entity.sprite.setRotation(body.getAngle()
                        * MathUtils.radiansToDegrees);
                entity.sprite.draw(batch);
            }

            // Damage
            if (entity.health <= 0) {
                // entity.isFlaggedForDelete = true;
                entity.die();
            }
        }
    }

    // Render Box2D World
    debugRenderer.render(world, camera.combined);

    // Render Stage
    stage.draw();

}


/*Laser Class:*/  
public class Laser {

    private Body    body;
    private Fixture fixture;
    private Vector2 velocity    = new Vector2();
    private float   speed       = 4800;
    private World   world;

    public Laser(World world, Vector2 pos) {
        this.world = world;

        // Body Definition
        BodyDef bodyDef = new BodyDef();
        bodyDef.type = BodyType.DynamicBody;
        bodyDef.position.set(pos);
        bodyDef.fixedRotation = true;
        bodyDef.gravityScale = 0;
        bodyDef.linearVelocity.x = 100f;

        // Shape
        PolygonShape shape = new PolygonShape();
        shape.setAsBox(2, 0.25f);

        // Fixture Definition
        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = shape;
        fixtureDef.restitution = 0f;
        fixtureDef.friction = .8f;
        fixtureDef.density = 0f;
        fixtureDef.filter.categoryBits = Entities.CATEGORY_PLAYER_PROJECTILE;
        fixtureDef.filter.maskBits = Entities.MASK_PLAYER_PROJECTILE;

        // Create Body
        body = world.createBody(bodyDef);
        fixture = body.createFixture(fixtureDef);

        // Assign Entity to Body
        Sprite sprite = new Sprite(new Texture("sprites/projectiles/laser.png"));
        sprite.setSize(2, 0.25f);
        Entity entity = new Entity();
        entity.sprite = sprite;
        entity.speed = 100f;
        entity.damage = 20f;
        entity.type = Entities.CATEGORY_PLAYER_PROJECTILE;
        body.setUserData(entity);
        body.setBullet(true);
    }

    public float getRestitution() {
        return fixture.getRestitution();
    }

    public void setRestitution(float restitution) {
        fixture.setRestitution(restitution);
    }

    public Body getBody() {
        return body;
    }

    public Fixture getFixture() {
        return fixture;
    }

}

EDIT: Error thrown on crash:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0000000066bcbd0d, pid=63044, tid=52440
#
# JRE version: Java(TM) SE Runtime Environment (7.0_45-b18) (build 1.7.0_45-b18)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (24.45-b08 mixed mode windows-amd64 compressed oops)
# Problematic frame:
# C  [gdx-box2d64.dll+0xbd0d]
#
# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# C:\Programming\Eclipse Projects\#######\desktop\hs_err_pid63044.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.sun.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#
AL lib: (EE) alc_cleanup: 1 device not closed

Error logged (only stack trace like thing I could find) (TestControls is the screen we are working in)

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  com.badlogic.gdx.physics.box2d.World.jniCreateBody(JIFFFFFFFFZZZZZF)J+0
j  com.badlogic.gdx.physics.box2d.World.createBody(Lcom/badlogic/gdx/physics/box2d/BodyDef;)Lcom/badlogic/gdx/physics/box2d/Body;+80
J  ####.###########.########.screens.levels.TestControls.render(F)V
j  com.badlogic.gdx.Game.render()V+19
j  com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop()V+619
j  com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run()V+27
v  ~StubRoutines::call_stub

World contact listener that sets flag for delete:

    world.setContactListener(new ContactListener() {
        @Override
        public void beginContact(Contact contact) {
        }

        @Override
        public void endContact(Contact contact) {
            if (contact.getFixtureA().getFilterData().categoryBits == Entities.CATEGORY_PLAYER_PROJECTILE
                    && contact.getFixtureB().getFilterData().categoryBits == Entities.CATEGORY_ENEMY) {
                ((Entity) contact.getFixtureA().getBody().getUserData()).isFlaggedForDelete = true;
                ((Entity) contact.getFixtureB().getBody().getUserData())
                        .damage(((Entity) contact.getFixtureA().getBody()
                                .getUserData()).damage);
            }
            if (contact.getFixtureA().getFilterData().categoryBits == Entities.CATEGORY_ENEMY
                    && contact.getFixtureB().getFilterData().categoryBits == Entities.CATEGORY_PLAYER_PROJECTILE) {
                ((Entity) contact.getFixtureB().getBody().getUserData()).isFlaggedForDelete = true;
                ((Entity) contact.getFixtureA().getBody().getUserData())
                        .damage(((Entity) contact.getFixtureB().getBody()
                                .getUserData()).damage);
            }
        }

        @Override
        public void preSolve(Contact contact, Manifold oldManifold) {
        }

        @Override
        public void postSolve(Contact contact, ContactImpulse impulse) {
        }
    });

回答1:

I had exactly the same problem happen to me the last 2 days.

It was a simple bug where I had an ArrayList of Entities which are displayed at the moment. If a collision happened, I set a flag just as you did. Later, I run through my Entity ArrayList and deleted every Entities body which was flagged - but not the Entity itself!

Here's my deletion code. It works for me, I hope it works for you as well.

CopyOnWriteArrayList<Entity> entities = new CopyOnWriteArrayList<Entity>();

public void deleteEntities() {
    for(Entity entity: entities){
        Body body = entity.getBody();
        if (body != null) {
            EntityData data = (EntityData) body.getUserData();
            if (data.isFlaggedForDelete()) {
                final Array<JointEdge> list = body.getJointList();
                //delete all joints attached
                while (list.size > 0) {
                    myWorld.getWorld().destroyJoint(list.get(0).joint);
                }
                //nullify everything, remove the entity from entities and destroy the body
                body.setUserData(null);
                myWorld.getWorld().destroyBody(body);
                entities.remove(entity);
                body = null;                    
            }
        }
    }
}

Additionally make sure you dont dispose() too early! Could be a problem as well.

I hope this was helpful :-)



回答2:

You must put your lasers into an array. They are getting out of scope and getting destroyed, but bodies stay alive. Bodies are recycled by box2d so that may cause issues. Creating them in the render like that is pretty bad as well. You should use pooling for anything that has short life cycle.

Touching disposed body in any way will blow up the game, so its optimal to never dispose them during game play.

Ive recently released libgdx/box2d game that uses pooling extensively. You can check out the code at GitHub.

Also that endContact(), clean it up. Its impossible to read. Something like that maybe?

@Override
public void endContact(Contact contact) {
    Fixture fA = contact.getFixtureA();
    Fixture fB = contact.getFixtureB();
    if (isPlayerProjectile(fA) && isEnemy(fB)) {
        Entity projectile = (Entity) contact.getFixtureA().getBody().getUserData();
        Entity enemy = (Entity) contact.getFixtureB().getBody().getUserData();
        solveProjectileEnemyContact(projectile, enemy);
    } else if (isPlayerProjectile(fB) && isEnemy(fA)) {
        Entity projectile = (Entity) contact.getFixtureB().getBody().getUserData();
        Entity enemy = (Entity) contact.getFixtureA().getBody().getUserData();
        solveProjectileEnemyContact(projectile, enemy);
    }
}

private boolean isPlayerProjectile(Fixture fixture){
    return fixture.getFilterData().categoryBits == Entities.CATEGORY_PLAYER_PROJECTILE;
}

private boolean isEnemy(Fixture fixture){
    return fixture.getFilterData().categoryBits == Entities.CATEGORY_ENEMY;
}

private void solveProjectileEnemyContact(Entity projectile, Entity enemy){
    projectile.isFlaggedForDelete = true;
    enemy.damage(projectile.damage);
}


回答3:

I know this question is a pretty old one, but I wanted to share my experience with this EXACT same error, where the chosen answer didn't help.

So for anyone still experiencing this issue, this is how I solved it:

Instead of creating and deleting my bodies at random times, I gathered all creation and all deletion in just 2 methods create and delete:

    //CREATE ALL
public void createAll(){    
    if(!world.isLocked()){ //KEY FACTOR
        if(createEnemy){
            createEnemy();
        }
        if(createBullet){
            createBullet();     
        }
    }   
}

the delete:

    //get rid of all bodies  (toBeDeleted is an Array<Body>)
public void sweepDeadBodies() {
    if(!world.isLocked()){ // KEY FACTOR
    Array<Body> wB = getBodies();   
       for (Body body : wB) {         
         if(body!=null && toBeDeleted.contains(body, false)) {
                  body.setUserData(null);
                  world.destroyBody(body);
                  toBeDeleted.removeValue(body, false);
         }

       }
    }          
}

where i called them:

    @Override
public void draw() {
    super.draw();

    glyphLayout.setText(scoreFont, score);

    //Do all my deleting and creating after the world.step !!! VERY IMPORTANT!!!
    sweepDeadBodies();
    removeEmtpyActors(); //I use actors as containers for the body, so here i loop though them all and nullify them
    createAll();
    renderer.render(world, camera.combined);

}

finally my createEnemy() :

  private void createEnemy() {
        Enemy enemy= new Enemy(WorldUtils.createEnemy(world));
        ((UserData) enemy.getBody().getUserData()).isFlaggedForDelete = false;
        addActor(enemy);
        addGameActor(enemy); //again this is just for me, you might be using entities or something else
        createEnemy = false;
    }

and my createBullet():

public void createBullet(){
        Bullet bullet = new Bullet(WorldUtils.createBullet(world));
        ((UserData) bullet.getBody().getUserData()).isFlaggedForDelete = false;
        addActor(bullet);
        addGameActor(bullet);
        bullet.fire();  //just adds a linear impulse
        createBullet= false;
    }

All bodies in toBeDeleted are there after I flag them for deletion like so :

((UserData) enemy.getBody().getUserData()).isFlaggedForDelete = true;

And add them like so:

  if(((UserData)body.getUserData()).isFlaggedForDelete){
        toBeDeleted.add(body);
    }

in my update method!

I hope this helps somebody. Essentially my mistake was creating / destroying bodies without checking if the world was locked, which I fixed with a call to !world.isLocked() !