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);
}
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:
Then instead of calling destroyObstruction() after collision I call this method i created to set the flag for deletion within the body to 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:
And here is the removeObjectsSetForDestruction method:
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:
I've used these threads in most places I do code with bodies and I can confirm this stopped my random crashes :)