Android AndEngine: 如何正确处理碰撞检测

6

我正在开发一个简单的AndEngine游戏,其中涉及以下几种精灵:

a.) 坦克 b.) 士兵 c.) 炸弹

我在这里有一个类似的问题:Android AndEngine: Simple sprite collision

游戏的样子如下:

enter image description here

然而,在解决了最初的问题后,又出现了另一个问题:

当炸弹(通过鼠标点击,从飞机当前位置生成并垂直向下移动到达目标或地面)击中目标(比如士兵)时,士兵精灵必须分离出来,并留下1秒钟的血液喷溅精灵,以及一枚来自炸弹的爆炸精灵。然而,游戏会强制关闭,并给出一个indexOutOfBoundError错误。我理解这可能是炸弹和目标之间的精灵计数不一致导致了数组越界错误,但logCat没有提供任何帮助。

09-22 11:13:37.585: E/AndroidRuntime(735): FATAL EXCEPTION: UpdateThread
09-22 11:13:37.585: E/AndroidRuntime(735): java.lang.IndexOutOfBoundsException: Invalid  index 5, size is 5
09-22 11:13:37.585: E/AndroidRuntime(735):  at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:251)
09-22 11:13:37.585: E/AndroidRuntime(735):  at java.util.ArrayList.get(ArrayList.java:304)
09-22 11:13:37.585: E/AndroidRuntime(735):  at org.andengine.entity.Entity.onManagedUpdate(Entity.java:1402)
09-22 11:13:37.585: E/AndroidRuntime(735):  at org.andengine.entity.Entity.onUpdate(Entity.java:1167)
09-22 11:13:37.585: E/AndroidRuntime(735):  at org.andengine.entity.Entity.onManagedUpdate(Entity.java:1402)
09-22 11:13:37.585: E/AndroidRuntime(735):  at org.andengine.entity.scene.Scene.onManagedUpdate(Scene.java:284)
09-22 11:13:37.585: E/AndroidRuntime(735):  at org.andengine.entity.Entity.onUpdate(Entity.java:1167)
09-22 11:13:37.585: E/AndroidRuntime(735):  at org.andengine.engine.Engine.onUpdateScene(Engine.java:591)
09-22 11:13:37.585: E/AndroidRuntime(735):  at org.andengine.engine.Engine.onUpdate(Engine.java:586)
09-22 11:13:37.585: E/AndroidRuntime(735):  at org.andengine.engine.Engine.onTickUpdate(Engine.java:548)
09-22 11:13:37.585: E/AndroidRuntime(735):  at org.andengine.engine.Engine$UpdateThread.run(Engine.java:820)

如您所见,logCat对我的代码没有错误提示,但是针对的是AndEngine本身,这很可能不是问题的原因。
我在onCreateScene Update Handler中运行的新代码(参考前面链接中提供的帮助):
protected void checkSoldierCollision() {
    // TODO Auto-generated method stub

    int numBombs = bombGroup.getChildCount();
    int numTroops = troopsGroup.getChildCount();
    final ArrayList<Sprite> toBeDetached = new ArrayList<Sprite>();
    for (int i = 0; i < numBombs; i++) {
        Sprite s = (Sprite) bombGroup.getChildByIndex(i);
        for (int j = 0; j < numTroops; j++) {
            Sprite s2 = (Sprite) troopsGroup.getChildByIndex(j);

            if (s.collidesWith(s2)) {

                /*Sprite splat = createSplat();
                splat.setPosition(s.getX(), s.getY());
                getEngine().getScene().attachChild(splat);*/

                Sprite splode = createExplosion();
                splode.setPosition(s.getX(), s.getY());
                getEngine().getScene().attachChild(splode);

                // WARNING: cannot detach from the list
                 //while looping through the list
                toBeDetached.add(s);
                toBeDetached.add(s2);

            }
        }
    }
    runOnUpdateThread(new Runnable() {
        @Override
        public void run() {
            for (Sprite s : toBeDetached) {
                s.detachSelf();
                Sprite splode = createExplosion();
                splode.setPosition(s.getX(), s.getY());
                getEngine().getScene().attachChild(splode);

            }
            toBeDetached.clear();
        }
    });

}

我注意到只有星号展开符或爆炸才不会出现错误。如果星号展开符和爆炸都在碰撞时同时出现,就会出现错误。

即使炸弹没有击中士兵(但仍然会脱落并在地面撞击时产生爆炸云),也会出现类似的错误:

我的createBomb函数:

    public Sprite createBomb(float x, float y) {
    Sprite bombSprite = new Sprite(x, y, this.mBombTextureRegion,
            getVertexBufferObjectManager());

    MoveYModifier downModBomb = new MoveYModifier(1, 60, FLOOR);

    downModBomb.addModifierListener(new IModifierListener<IEntity>() {

        @Override
        public void onModifierStarted(IModifier<IEntity> pModifier,
                IEntity pItem) {

        }

        @Override
        public void onModifierFinished(IModifier<IEntity> pModifier,
                final IEntity pItem) {
             pItem.detachSelf();
             AnimatedSprite explodeSprite = createExplosion();
             explodeSprite.setPosition(pItem.getX(), FLOOR);
             getEngine().getScene().attachChild(explodeSprite);
        }
    });

    bombSprite.registerEntityModifier(downModBomb); // register action
    return bombSprite;
}

我的onCreateScene炸弹函数和碰撞updateHandler:

    scene.setOnSceneTouchListener(new IOnSceneTouchListener() {

        @Override
        public boolean onSceneTouchEvent(Scene pScene,
                TouchEvent pSceneTouchEvent) {
            if (pSceneTouchEvent.getAction() == TouchEvent.ACTION_UP) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Sprite bomb = createBomb(f16Sprite.getX(),
                                f16Sprite.getY());
                        bombGroup.attachChild(bomb);
                    }
                });

                return true;
            }
            return false;
        }
    });

    // periodic checks
    scene.registerUpdateHandler(new IUpdateHandler() {

        @Override
        public void onUpdate(float pSecondsElapsed) {
            // checkTankCollision();
            // checkSoldierBackCollision();
            checkSoldierCollision();
        }

        @Override
        public void reset() {
        }
    });

我的createExplosion方法:

            public AnimatedSprite createExplosion() {
    AnimatedSprite boomSprite = new AnimatedSprite(0, 0,
            this.mExplodeTextureRegion, getVertexBufferObjectManager());

    DelayModifier delay = new DelayModifier(.3f); // delay in seconds, can
                                                    // take float numbers .5
                                                    // seconds
    delay.addModifierListener(new IModifierListener<IEntity>() {

        @Override
        public void onModifierStarted(IModifier<IEntity> pModifier,
                IEntity pItem) {
            ((AnimatedSprite) pItem).animate(new long[] { 100, 100, 100 }, // durations/frame
                    new int[] { 1, 2, 3 }, // which frames
                    true); // loop
        }

        @Override
        public void onModifierFinished(IModifier<IEntity> pModifier,
                final IEntity pItem) {
            ((AnimatedSprite) pItem).detachSelf();
        }
    });

    boomSprite.registerEntityModifier(delay); // register action
    return boomSprite;
}

我该如何解决这个问题?循环逻辑不是我的强项。我也愿意尝试其他实现方式。
更新:刚刚意识到,即使碰撞的结果是splat或explosion中的任意一个,如果玩家一直轰炸(比如说4-5次),整个游戏将被强制关闭。
-在创建士兵/坦克实例时似乎有一定数量的可允许轰炸次数。我已经关闭了当炸弹击中士兵后首先创建爆炸(所以血液会留在原地而不是同时爆炸和留下)。它运行得不错,但如果超过4-6个炸弹,游戏就会关闭。当新的士兵实例出现(也就是旧的离开屏幕并分离时),玩家可以再使用4-6个炸弹,然后游戏就会强制关闭。
3个回答

4

问题可能是由于您通过调用runOnUiThread将炸弹附加到UI线程,但是在Update线程上检查碰撞。请尝试将炸弹添加到runOnUpdateThread。

通常情况下,您希望保持一致的线程使用方式来操作事物,否则会出现奇怪的bug,这很难调试。

顺便说一句:UI线程非常适合显示Toast消息,请参考以下示例:

runOnUiThread(new Runnable() {
    @Override
    public void run() {
        Toast.makeText(GameActivity.this, "Hello!", Toast.LENGTH_SHORT).show();
    }
});

我已经在我的代码上进行了Toast测试(在鼠标单击事件期间放置了Toast)。看起来每次都会有1个炸弹组的子项,因为它在撞到地面时会分离。我还有一个部队的实例。另外,请让我澄清一下你所建议的内容; 我应该在runOnUpdate线程上生成炸弹吗?但是,分离不应该也发生在那里吗? - Erasmus
是的,您希望生成代码也在Update线程上运行。UI线程可能会调用一个方法,导致Update线程崩溃。考虑以下情况-更新线程对场景中所有子项列表执行某些操作。此操作需要很长时间,并且遍历子项的方法以与您相同的方式获取其数量并将其存储在内存中。假设有七个子节点。在循环执行时,来自UI线程的方法删除了五个子体,Update线程的循环方法就会越界。 - JohnEye
我更新了我的代码,使其能够在UI线程(当s与s2相撞时)生成炸弹,并在runOnUpdate中(在for-each循环内部)生成炸弹。然而,它似乎仍然无法正常工作。正如我所提到的,bombsGroup和troopsGroup的大小仍然为1。 - Erasmus
我承认我不知道为什么会发生这种情况,我会考虑通过使用Sprite.setVisible(boolean visible)来绕过问题,并回收炸弹。 - JohnEye
到目前为止一切都很好。实际上你是对的。这里要学习的课程是关于在runOnUpdateThread中附加/分离的问题。在确认解决方案之前,我正在进行一些测试:D - Erasmus
显示剩余10条评论

1
为了调试这个问题,您需要通过输出(可能是通过日志)来监视实际添加到和troopsGroup的数量,并且这应该与应该添加的数量一致。还可以尝试输出getChildCount()的值以及您的i和j参数。
对于一个猜测,请尝试这个:
for (int i = 0; i < toBeDetached.size(); i++) {

     toBeDetached.get(i).detachSelf();

  }

嗯,只是为了澄清,这段代码片段放在更新线程中是吗?如果是的话,我必须将循环转换为for循环,而不是for-each。 - Erasmus
但是你没有使用for-each循环,for-each就像是for(Sprite s:bombGroup) - Shark

1

在JohnEye先生的帮助下,我已经成功修复了代码中的问题。请看以下片段:

    protected void checkSoldierCollision() {
    // TODO Auto-generated method stub

    int numBombs = bombGroup.getChildCount();
    int numTroops = troopsGroup.getChildCount();
    final ArrayList<Sprite> toBeDetached = new ArrayList<Sprite>();
    for (int i = 0; i < numBombs; i++) {
        Sprite s = (Sprite) bombGroup.getChildByIndex(i);
        for (int j = 0; j < numTroops; j++) {
            Sprite s2 = (Sprite) troopsGroup.getChildByIndex(j);

            if (s.collidesWith(s2)) {

                Sprite splat = createSplat();
                splat.setPosition(s.getX(), s.getY());
                getEngine().getScene().attachChild(splat);

                Sprite splode = createExplosion();
                splode.setPosition(s.getX(), s.getY());
                explodeGroup.attachChild(splode);
                // getEngine().getScene().attachChild(splode);

                // WARNING: cannot detach from the list
                // while looping through the list
                toBeDetached.add(s);
                toBeDetached.add(s2);

            }
        }
    }
    runOnUpdateThread(new Runnable() {
        @Override
        public void run() {
            for (Sprite s : toBeDetached) {
                s.detachSelf();
                Sprite splat = createSplat();
                splat.setPosition(s.getX(), s.getY());
                getEngine().getScene().attachChild(splat);

                Sprite splode = createExplosion();
                splode.setPosition(s.getX(), s.getY());
                explodeGroup.attachChild(splode);
                // getEngine().getScene().attachChild(splode);

            }
            toBeDetached.clear();
        }
    });

}

正如我所提到的,游戏崩溃是因为两个碰撞对象之间计数不一致。当一个精灵在UI线程中生成,但在更新线程中没有生成时,就会出现这种情况,正如JohnEye先生指出的那样。此外,请考虑我的createExplosion、createBomb和createSplat函数:
创建炸弹
    public Sprite createBomb(float x, float y) {
    Sprite bombSprite = new Sprite(x, y, this.mBombTextureRegion,
            getVertexBufferObjectManager());

    MoveYModifier downModBomb = new MoveYModifier(1, 60, FLOOR);

    downModBomb.addModifierListener(new IModifierListener<IEntity>() {

        @Override
        public void onModifierStarted(IModifier<IEntity> pModifier,
                IEntity pItem) {

        }

        @Override
        public void onModifierFinished(IModifier<IEntity> pModifier,
                final IEntity pItem) {
            runOnUpdateThread(new Runnable() {
                @Override
                public void run() {
                    pItem.detachSelf();
                }
            });
            AnimatedSprite explodeSprite = createExplosion();
            explodeSprite.setPosition(pItem.getX(), FLOOR);
            explodeGroup.attachChild(explodeSprite);
            // getEngine().getScene().attachChild(explodeGroup);
        }
    });
    bombSprite.registerEntityModifier(downModBomb);
    return bombSprite;
}

创建爆炸

public AnimatedSprite createExplosion() {
    AnimatedSprite boomSprite = new AnimatedSprite(0, 0,
            this.mExplodeTextureRegion, getVertexBufferObjectManager());

    DelayModifier delay = new DelayModifier(.3f); // delay in seconds, can
                                                    // take float numbers .5
                                                    // seconds
    delay.addModifierListener(new IModifierListener<IEntity>() {

        @Override
        public void onModifierStarted(IModifier<IEntity> pModifier,
                IEntity pItem) {
            ((AnimatedSprite) pItem).animate(new long[] { 100, 100, 100 }, // durations/frame
                    new int[] { 1, 2, 3 }, // which frames
                    true); // loop
        }

        @Override
        public void onModifierFinished(IModifier<IEntity> pModifier,
                final IEntity pItem) {
            runOnUpdateThread(new Runnable() {
                @Override
                public void run() {
                    ((AnimatedSprite) pItem).detachSelf();
                }
            });
        }
    });

    boomSprite.registerEntityModifier(delay); // register action
    return boomSprite;
}

创建 SPLAT

public Sprite createSplat() {
    Sprite splatSprite = new Sprite(0, 0, this.mSplatTextureRegion,
            getVertexBufferObjectManager());

    DelayModifier delay = new DelayModifier(.3f); // delay in seconds, can
                                                    // take float numbers .5
                                                    // seconds
    delay.addModifierListener(new IModifierListener<IEntity>() {

        @Override
        public void onModifierStarted(IModifier<IEntity> pModifier,
                IEntity pItem) {

        }

        @Override
        public void onModifierFinished(IModifier<IEntity> pModifier,
                final IEntity pItem) {
            runOnUpdateThread(new Runnable() {
                @Override
                public void run() {
                    pItem.detachSelf();
                }
            });
        }
    });

    splatSprite.registerEntityModifier(delay); // register action
    return splatSprite;
}

请注意,它们所有的精灵都在runOnUpdate线程中分离/生成。在我之前的运行中,游戏崩溃了,因为我没有在runOnUpdate类中分离/生成所有这些精灵。

你说“一个精灵在UI线程中生成,但不在Update线程中生成。它需要在两个线程中都生成。”这并不完全正确,它只需要在Update线程中生成即可。你可能想要更改一下,以免混淆未来访问者对这个问题的理解。顺便问一下,你是否改变了关于悬赏的想法? - JohnEye
哎呀,完全忘记了,这是给你的 JohnEye! - Erasmus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接