Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AndEngine Sprite/Box2D Body removal crashes my program with no error/exception information?

I am making a skateboarding game with obstacles you have to jump over using box2D and AndEngine. I am trying to make it so that when the player collides with an object, the object is removed and an explosion is placed at the objects old position, however something in the sprite removal code is freezing my program causing it to end (not even a force close message it just closes itself and goes to my home screen) and no error/exception information appears in logcat so I have no idea what is causing it! Here are some code snippets-

when I create the sprites/boundaries I attach a JSONObject to the body containing the sprite and the type of sprite it is, and attach a similar JSONOBject to the sprite with the body and type:

/** method to construct our player (takes an x and y position)*/
private void constructPlayer(final float pX, final float pY) {


    final Body body;


    /* construct the sprite of our player and set the animation */
    this.player = new AnimatedSprite(pX, pY, this.mSkaterTextureRegion);
    long[] frameDurations = {100, 100};
    player.animate(frameDurations, 4, 5, true);


    body = PhysicsFactory.createBoxBody(this.mPhysicsWorld, player, BodyType.DynamicBody, PLAYER_FIXTURE_DEF);
    this.mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(player, body, true, false));
    body.setUserData(makeUserDataForBody(PLAYER_TYPE,player));
    player.setUserData(makeUserDataForSprite(PLAYER_TYPE,body));
    this.mScene.registerTouchArea(player);

    //attach our player to the scene
    this.mScene.attachChild(player);
}

private JSONObject makeUserDataForBody(int type, Object sprite)
{
    JSONObject myObject = new JSONObject();

    try {
        myObject.put("type", type);
        myObject.put("sprite", sprite);
    } catch (JSONException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return myObject;
}
private JSONObject makeUserDataForSprite(int type, Body body)
{
    JSONObject myObject = new JSONObject();

    try {
        myObject.put("body", body);
        myObject.put("type", type);
    } catch (JSONException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return myObject;
}

My code for the constructing the obstruction sprites is pretty much the same as constructing the player but I set a velocity for it to move:

private void addObstruction(final float pX, final float pY) {


    final Body body;
    final Sprite myObstruction;

    myObstruction = new Sprite(pX, pY, this.mCrateTextureRegion);

    body = PhysicsFactory.createBoxBody(this.mPhysicsWorld, myObstruction, BodyType.DynamicBody, OBJECT_FIXTURE_DEF);

    this.mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(myObstruction, body, true, true));

    body.setUserData(makeUserDataForBody(OBSTRUCTION_TYPE,myObstruction));
    myObstruction.setUserData(makeUserDataForSprite(OBSTRUCTION_TYPE,body));

    body.setLinearVelocity(-150f, 0);
    //attach our Obstruction to the scene

    this.mScene.attachChild(myObstruction);
}

Here is the contactListener for my physics world:

this.mPhysicsWorld.setContactListener(new ContactListener() {

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

        }

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

        @Override
        public void endContact(Contact contact) {
            // TODO Auto-generated method stub

                Body obj1Data = contact.getFixtureA().getBody();
                Body obj2Data = contact.getFixtureB().getBody();

                JSONObject obj1UserData;
                JSONObject obj2UserData;
                int obj1Type = 0;
                int obj2Type = 0;
                if(obj1Data.getUserData()!=null)
                {
                    obj1UserData =(JSONObject) obj1Data.getUserData();
                    try {
                    obj1Type = obj1UserData.getInt("type");
                    }catch (JSONException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                if(obj2Data.getUserData()!=null)
                {
                    obj2UserData=(JSONObject) obj2Data.getUserData();
                    try {
                        obj2Type = obj2UserData.getInt("type");
                    } catch (JSONException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                switch (obj1Type)
                {
                    case PLAYER_TYPE:
                    break;
                    case GRINDRAIL_TYPE:
                    if(isGrinding)
                        {
                            endGrind();
                            if(!isJumping)
                            fall(player);
                        }
                    break;
                    case GROUND_TYPE:
                    break;
                    case OBSTRUCTION_TYPE:
                    break;

                    case WALL_TYPE:
                        break;


                }

                switch (obj2Type)
                {
                    case PLAYER_TYPE:
                    break;
                    case GRINDRAIL_TYPE:
                    if(isGrinding)
                        {
                            endGrind();
                            if(!isJumping)
                            fall(player);
                        }
                    break;
                    case GROUND_TYPE:
                    break;
                    case OBSTRUCTION_TYPE:
                    break;

                    case WALL_TYPE:
                        break;
                }


        }

        @Override
        public void beginContact(Contact contact) {

                Body obj1Data = contact.getFixtureA().getBody();
                Body obj2Data = contact.getFixtureB().getBody();


                JSONObject obj1UserData;
                JSONObject obj2UserData;
                int obj1Type = 0;
                int obj2Type = 0;
                if(obj1Data.getUserData()!=null)
                {
                    obj1UserData =(JSONObject) obj1Data.getUserData();
                    try {
                    obj1Type = obj1UserData.getInt("type");
                    }catch (JSONException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                if(obj2Data.getUserData()!=null)
                {
                    obj2UserData=(JSONObject) obj2Data.getUserData();
                    try {
                        obj2Type = obj2UserData.getInt("type");
                    } catch (JSONException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }


                //deal with things colliding with the player
                if(obj1Type==PLAYER_TYPE)
                {
                    playerCollisionHandler(obj2Data);
                }
                else if(obj2Type==PLAYER_TYPE)
                {
                    playerCollisionHandler(obj1Data);
                }



        }
    });

here is my playerCollisionHandler method:

private void playerCollisionHandler(Body secondBody)
{
    JSONObject secondBodyData = null;
    if(secondBody.getUserData()!=null)
    {
        secondBodyData=(JSONObject) secondBody.getUserData();
    }


    JSONObject userdata = (JSONObject) player.getUserData();
    Body playerBody = null;
    try {
        playerBody = (Body) userdata.get("body");
    } catch (JSONException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    int objType = 0;
    try {
        if(secondBodyData!=null)
        objType = secondBodyData.getInt("type");
    } catch (JSONException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    if(objType == GROUND_TYPE)
    {

        if(playerBody.getLinearVelocity().y<0)
        {
            /* If the sprites y velocity is negative the sprite is jumping,
             * don't reset the values!!!*/
        }
        else
        {
            if((isJumping)||(isFalling))
            {
                //play landing sound
                AndEngineTestActivity.this.mLandSound.play();
                isJumping = false;
                isFalling = false;
                //player.setPosition(player.getX(), GROUND_LEVEL-player.getHeight());
                //animate landing
                player.animate(createFrameDurations(LAND_ANIM_FRAMES.length), LAND_ANIM_FRAMES, 0);
            }
            if(!rollSoundIsPlaying)
            {
                playRollSound();
            }
        }
    }
    else if(objType == GRINDRAIL_TYPE)
    {
        Sprite grindRail=null;
        try {
            grindRail = (Sprite) secondBodyData.get("sprite");
        } catch (JSONException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        /*create a rectangle at the upper bound of the grind rail to test collision*/
        grindRailUpperBound = new Rectangle(grindRail.getX(), grindRail.getY(), mGrindRailTextureRegion.getWidth(), COLLISION_BOUNDS_PIXEL_ACCURACY);
        playerLowerBound = new Rectangle(player.getX(), player.getY()+player.getHeight(), player.getWidth(), COLLISION_BOUNDS_PIXEL_ACCURACY);
        grindRailUpperBound.setColor(1.0f, 0f, 0f,1f);
        playerLowerBound.setColor(1.0f, 1.0f, 0f,1f);
        mScene.attachChild(playerLowerBound);
        mScene.attachChild(grindRailUpperBound);
        if(grindRailUpperBound.collidesWith(playerLowerBound))
        {
            if(!isGrinding)
            {
                mScene.detachChild(grindRailUpperBound);
                mScene.detachChild(playerLowerBound);
                grindPlayer(player);
            }
        }

        if(!isGrinding)
        {
            /* if it reaches this point and the custom rectangle bounds did not collide
             * it means the player has collided with the grind rail another way, so we hurt the player*/
            playerHitByObject();
            destroyObstruction(secondBody);
        }
    }
    else if(objType == OBSTRUCTION_TYPE)
    {
        playerHitByObject();
        destroyObstruction(secondBody);

    }
}

and here is the destroyObtruction method which seems to be the culprit of the crashes (if i comment out my calls to destroyObstruction my code runs fine, but I'm not sure why this method is causing the crash...):

private void destroyObstruction(Body obstructionBody)
{
    obstructionBody.setActive(false);


    try{
        JSONObject secondBodyData = null;
        if(obstructionBody.getUserData()!=null)
        {
            secondBodyData=(JSONObject) obstructionBody.getUserData();
        }

        explodeObstruction(((IEntity) secondBodyData.get("sprite")).getX(),((IEntity) secondBodyData.get("sprite")).getY());

        final PhysicsConnector obstructionPhysicsConnector = this.mPhysicsWorld.getPhysicsConnectorManager().findPhysicsConnectorByShape((IShape) secondBodyData.get("sprite"));
        this.mPhysicsWorld.unregisterPhysicsConnector(obstructionPhysicsConnector);
        this.mPhysicsWorld.destroyBody(obstructionPhysicsConnector.getBody());

        //this.mPhysicsWorld.destroyBody(obstructionBody);
        this.mScene.detachChild((IEntity) secondBodyData.get("sprite"));

    }catch(Exception e)
    {
        Log.d(TAG, "Exception:"+e);
    }
    catch(Error e)
    {
        Log.d(TAG, "Error:"+e);
    }
}

private void explodeObstruction(float pX, float pY)
{
    PointParticleEmitter obstructionExplosion = new PointParticleEmitter(pX, pY);
    ParticleSystem ExplosionParticleSystem = new ParticleSystem(obstructionExplosion, 45, 60, 60, this.mCrateParticleTextureRegion);


    ExplosionParticleSystem.addParticleInitializer(new AlphaInitializer(1f));
    ExplosionParticleSystem.setBlendFunction(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
    ExplosionParticleSystem.addParticleInitializer(new VelocityInitializer(-175, 175, -175, 175));
    ExplosionParticleSystem.addParticleInitializer(new RotationInitializer(0.0f, 360.0f));
    ExplosionParticleSystem.addParticleInitializer(new RotationInitializer(0f, -20f));

    ExplosionParticleSystem.addParticleModifier(new ScaleModifier(1.0f, 0.5f, 0, MAX_PARTICLE_LIFE/2));

    ExplosionParticleSystem.addParticleModifier(new AlphaModifier(1, 0.35f, 0, MAX_PARTICLE_LIFE));

    ExplosionParticleSystem.addParticleModifier(new ExpireModifier(MAX_PARTICLE_LIFE, MAX_PARTICLE_LIFE));


    this.mScene.attachChild(ExplosionParticleSystem);
}
like image 300
AndroidNoob Avatar asked Sep 01 '11 10:09

AndroidNoob


1 Answers

After googling about box2D and sprite/body removal it turns out you can't remove a sprite/body from the contactListener, but what you can do is set a flag in either the body or sprite to delete it and check for these flags in a seperate update method outside the contactListener. I did this by making a single 'makeUserData' method to create a JSONObject with the sprite/body/type and additionally a boolean 'deleteStatus' that determines if it flagged for deletion:

private JSONObject makeUserData(int type, Body body, Object sprite)
{
    JSONObject myObject = new JSONObject();

    try {
        myObject.put("type", type);
        myObject.put("sprite", sprite);
        myObject.put("body", body);
        myObject.put("deleteStatus", false);
    } catch (JSONException e) {
        // TODO Auto-generated catch block
        Log.d(TAG,"Exception creating user data:"+e);
    }
    return myObject;
}

Then instead of calling destroyObstruction() after collision I call this method i created to set the flag for deletion within the body to true:

private void setForDestruction(Body myBody) throws JSONException
{
    if(myBody.getUserData()!=null)
    {
        ((JSONObject)myBody.getUserData()).put("deleteStatus", true);
    }

}

Then in a seperate update handler (I had one in my onLoadScene method already to update the score) I added a call to another method I made to iterate through the bodies in my physics world looking for this flag:

 this.mScene.registerUpdateHandler(new IUpdateHandler() {
        @Override
        public void reset() { }

        @Override
        public void onUpdate(final float pSecondsElapsed) {
            //update the players score
            updateScore();

            //update the text on the screen
            playerScoreText.setText( "Score: "+PLAYER_SCORE);               
            playerLivesText.setText("Lives:"+PLAYER_LIVES);

            //remove any sprites flagged for deletion
            try{
                removeObjectsSetForDestruction();
            }catch(Exception e)
            {
                Log.d(TAG,"Exception removing objects from update:"+e);
            }
            catch(Error e)
            {
                Log.d(TAG,"Error removing objects from update:"+e);
            }

        }
    });

And here is the removeObjectsSetForDestruction method:

private void removeObjectsSetForDestruction()
{
    if(this.mPhysicsWorld!=null)
    {
        Iterator<Body> allMyBodies = this.mPhysicsWorld.getBodies();//gets all the bodies in my physics world
        boolean isDelete = false;
        JSONObject currentBodyData;
        while(allMyBodies.hasNext())
        {
             try {
//this code is in a try/catch bracket because some of my bodies don't have the extra data attached
                 currentBodyData = (JSONObject)allMyBodies.next().getUserData();//gets the next JSONOBject from the body
                 if(currentBodyData!=null)
                 {
                     isDelete = (Boolean) currentBodyData.get("deleteStatus");
                    if(isDelete)
                    {
                        destroyObstruction((Body) currentBodyData.get("body"));
                    }
                 }
            } catch (JSONException e) {
                // TODO Auto-generated catch block
                Log.d(TAG,"Error getting world bodies data:"+e);
            }
        }
    }
}

EDIT: The AndEngine wiki on box2D explains pretty well how the world physics calculation is fragile so you need to be really careful when adding/deleting/moving bodies as in some places it could happen at the same time as the world physics calculation, which eventually causes the program to crash. It also outlines a solution which is to place the code into 'this.runOnUpdateThread'. So for example in my code when I added an obstruction sprite in my code (they are added from a CountDownTimer so the chances that they could be added at the same time as world step calculation is likely) I wrapped it in a thread:

private void addObstruction(final float pX, final float pY) {

    runOnUpdateThread(new Runnable() {



    @Override
    public void run() {
        final Body body;
        final Sprite myObstruction;

        myObstruction = new Sprite(pX, pY-mCrateTextureRegion.getHeight(), mCrateTextureRegion);

        body = PhysicsFactory.createBoxBody(mPhysicsWorld, myObstruction, BodyType.DynamicBody, OBJECT_FIXTURE_DEF);
        //body.setLinearDamping(10);
        //body.setAngularDamping(10);
        mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(myObstruction, body, true, true));

        body.setUserData(makeUserData(OBSTRUCTION_TYPE,body,myObstruction));
        myObstruction.setUserData(makeUserData(OBSTRUCTION_TYPE,body,myObstruction));

        myObstruction.registerUpdateHandler(new IUpdateHandler() {

            @Override
            public void reset() {

            }

            @Override
            public void onUpdate(float pSecondsElapsed) {
                    runOnUpdateThread(new Runnable() {

                    @Override
                    public void run() {
                        final Vector2 velocity = Vector2Pool.obtain(-10f, 0f);
                        body.setLinearVelocity(velocity);
                        Vector2Pool.recycle(velocity);

                    }
                });

            }
        });
        //attach our Obstruction to the scene
        mScene.attachChild(myObstruction);

    }

    });
}

I've used these threads in most places I do code with bodies and I can confirm this stopped my random crashes :)

like image 86
AndroidNoob Avatar answered Sep 25 '22 13:09

AndroidNoob