При удалении AndEngine Sprite / Box2D Body происходит сбой моей программы без информации об ошибках / исключениях? - PullRequest
4 голосов
/ 01 сентября 2011

Я занимаюсь скейтбордингом с препятствиями, через которые вам нужно прыгать, используя box2D и AndEngine. Я пытаюсь сделать так, чтобы, когда игрок сталкивался с объектом, объект удалялся и взрыв помещался в старое положение объектов, однако что-то в коде удаления спрайта замораживает мою программу, вызывая ее завершение (даже не Принудительное закрытие сообщения: оно просто закрывается и переходит к моему домашнему экрану), и в logcat не появляется информация об ошибках / исключениях, поэтому я понятия не имею, что ее вызывает! Вот некоторые фрагменты кода -

когда я создаю спрайты / границы, я присоединяю JSONObject к телу, содержащему спрайт и тип спрайта, и присоединяю аналогичный JSONOBject к спрайту с телом и типом:

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

Мой код для построения спрайтов препятствий почти такой же, как и для создания игрока, но я установил скорость для его перемещения:

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

Вот список контактов моего мира физики:

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



        }
    });

вот мой метод 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);

    }
}

и вот метод destroyObtruction, который, кажется, является виновником сбоев (если я закомментирую мои вызовы destroyObstruction, мой код работает нормально, но я не уверен, почему этот метод вызывает сбой ...):

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

1 Ответ

8 голосов
/ 02 сентября 2011

После поиска в Google box2D и удаления спрайта / тела выясняется, что вы не можете удалить спрайт / тело из contactListener, но вы можете установить флаг в теле или спрайте, чтобы удалить его и проверить их. флаги в отдельном методе обновления вне contactListener. Я сделал это, создав единственный метод makeUserData для создания JSONObject со спрайтом / телом / типом и, кроме того, логическим «deleteStatus», который определяет, помечен ли он для удаления:

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

Тогда вместо вызова destroyObstruction () после столкновения я вызываю этот метод, который я создал, чтобы установить для флага удаления в теле значение true:

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

}

Затем в отдельном обработчике обновлений (у меня уже был один в моем методе onLoadScene для обновления партитуры) я добавил вызов другого метода, который я сделал, чтобы перебирать тела в моем физическом мире, ища этот флаг:

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

        }
    });

А вот метод 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);
            }
        }
    }
}

РЕДАКТИРОВАТЬ: Вики AndEngine на box2D довольно хорошо объясняет, насколько расчеты мировой физики хрупки, поэтому вам нужно быть очень осторожным при добавлении / удалении / перемещении тел, поскольку в некоторых местах это может произойти одновременно время как мировой расчет физики, что в конечном итоге приводит к падению программы. Это также обрисовывает в общих чертах решение, которое должно поместить код в 'this.runOnUpdateThread'. Так, например, в моем коде, когда я добавил спрайт препятствий в мой код (они добавляются из CountDownTimer, так что есть вероятность, что они могут быть добавлены одновременно с вычислением мирового шага), я обернул его в поток:

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

    }

    });
}

Я использовал эти темы в большинстве мест, где я делаю код с телами, и я могу подтвердить, что это остановило мои случайные сбои:)

...