AndEngine Sprite / Box2D La eliminación del cuerpo bloquea mi programa sin información de error / excepción?

Estoy haciendo un juego de skate con obstáculos que tienes que saltar usando box2D y AndEngine. Estoy tratando de hacerlo para que cuando el jugador choca con un objeto, el objeto se quita y una explosión se coloca en la posición de los objetos antiguos, sin embargo algo en el código de eliminación de sprite está congelando mi programa haciendo que termine (ni siquiera un Fuerza cerrar el mensaje que acaba de cerrarse y va a mi pantalla de inicio) y no hay información de error / excepción aparece en logcat así que no tengo idea de lo que está causando! Aquí hay algunos fragmentos de código:

Al crear los sprites / límites, adjunto un JSONObject al cuerpo que contiene el sprite y el tipo de sprite que es, y adjunto un JSONOBject similar al sprite con el cuerpo y el tipo:

/** 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; } 

Mi código para la construcción de los sprites de obstrucción es más o menos lo mismo que construir el reproductor, pero establezco una velocidad para que se mueva:

 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); } 

Aquí está el contactListener para mi mundo de la física:

 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); } } }); 

Aquí está mi método playerCollisionHandler:

 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); } } 

Y aquí está el método destroyObtruction que parece ser el culpable de los accidentes (si comento mis llamadas a destroyObstruction mi código funciona bien, pero no estoy seguro de por qué este método está causando el accidente …):

 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); } 

Después de googling sobre box2D y sprite / body removal, resulta que no puedes eliminar un sprite / body del contactoListener, pero lo que puedes hacer es establecer un flag en el cuerpo o sprite para eliminarlo y comprobar estos flags en un Método de actualización separado fuera del contactListener. Hice esto haciendo un solo método 'makeUserData' para crear un JSONObject con el sprite / body / type y adicionalmente un 'deleteStatus' booleano que determina si marcó para borrado:

 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; } 

Entonces en lugar de llamar a destroyObstruction () después de la colisión llamo a este método i creado para establecer la bandera para la eliminación en el cuerpo a true:

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

Luego en un manejador de actualización separado (tuve uno en mi método onLoadScene ya para actualizar la puntuación) He añadido una llamada a otro método que hice para iterar a través de los cuerpos en mi mundo de la física en busca de este indicador:

  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); } } }); 

Y aquí está el método removeObjectsSetForDestruction:

 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: El wiki de AndEngine en box2D explica muy bien cómo el cálculo de física del mundo es frágil por lo que necesita ser muy cuidadoso al agregar / eliminar / moviendo los cuerpos como en algunos lugares podría ocurrir al mismo tiempo que el cálculo de física mundial, que eventualmente Hace que el programa se bloquee. También describe una solución que es colocar el código en 'this.runOnUpdateThread'. Así que por ejemplo en mi código cuando he añadido un sprite de obstrucción en mi código (se añaden desde un CountDownTimer por lo que las posibilidades de que se podrían agregar al mismo tiempo que el cálculo de paso mundial es probable) lo envuelto en un hilo:

 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); } }); } 

He utilizado estos hilos en la mayoría de los lugares que hago código con los cuerpos y puedo confirmar esto detuvo mis accidentes aleatorios 🙂

  • libgdx spritebatch no dibuja basado en el origen de una textura
  • Búsqueda de píxeles de borde de una imagen con envolvente transparente (para detección de colisión)
  • Mover sprite en Andengine
  • Cómo cargar sprite hoja con 5 filas y 5 columnas vista superior en android?
  • No se puede encontrar el ejemplo adecuado para la clase de sprite de OpenGL 2d que no usa GL11Ext para dibujar
  • Android gl abierto más lento que la tela?
  • Animación de sprites en Cocos2d android
  • AndEngine- Dibujo del niño del sprite detrás de su padre
  • Texturas en OpenGL ES 2.0 para Android
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.