Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android AndEngine: handling Collission Detection properly

I am working on a simple AndEngine game that involves the ff. sprites

a.) tanks b.) soldiers c.) bombs

I have a similar question located here: Android AndEngine: Simple sprite collision

What the game looks like:

enter image description here

However upon fixing the initial problem, another problem arose:

When the bomb (spawns where the plane is currently at and goes down vertically until it reaches a target or floor, by means of mouse click) hits a target, say a soldier, the soldier sprite must detach and leave behind it a blood splatter sprite for 1 second, as well as an explosion sprite from the bomb. However, the game force closes giving an indexOutOfBoundError. I understand that this might probably be a discrepancy of sprite count between the bomb and its target causing an out of bound array error, but logCat isn't helping at all.

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)

As seen here, logCat isn't giving an error on my code, but on the AndEngine itself, and that's most probably not the case.

My new code that runs in the onCreateScene Update Handler (as per help with the linked previous problem above):

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

}

What I noticed that it can only be either a splat or an explosion for it not to have an error. If both splat and explosion were to populate the scene upon collision, an error occurs.

Also, even if the bombs dont hit the soldiers (but still detaching and replaced with an explosion cloud when hitting the floor) it also gives a similar error:

My createBomb function:

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

My onCreateScene bomb function and collision 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() {
        }
    });

My createExplosion method:

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

How do I remedy this? Looping logic isn't entirely my strong point. I am also open on how to implement this alternatively.

UPDATE: Just realized that even if the result of the collision is either splat or explosion, it doesn't matter, if the player keeps spamming bombs (like say 4-5 times) the entire game force closes.

-It seems that there is an allowable number of bombing whenever an instance of a solder/tank is created. I have turned off the create explosion first whenever a bomb hits a soldier (so a bloodsplat would just remain in its place instead of both). It works ok, but exceed 4-6 bombs and the game closes. When a new soldier instance spawns (meaning when the old ones go off screen and detaches) the player is then given 4-6 bombs before the game force closes.

like image 418
Erasmus Avatar asked Sep 22 '12 03:09

Erasmus


3 Answers

The problem might be caused by the fact that you attach the bombs on the UI thread by calling runOnUiThread but you check the collisions on the Update Thread. Try adding the bombs inside runOnUpdateThread.

Generally, you want to keep consistent about what thread you use to manipulate things, otherwise strange bugs will occur which are a pain to debug.

On a side note: The UI thread is great for showing Toasts, see this example:

runOnUiThread(new Runnable() {
    @Override
    public void run() {
        Toast.makeText(GameActivity.this, "Hello!", Toast.LENGTH_SHORT).show();
    }
});
like image 176
JohnEye Avatar answered Nov 14 '22 21:11

JohnEye


In order to debug this, you need to monitor by output (through Log perhaps), how many were ACTUALLY added to bombGroup and troopsGroup, and this should be consistent with how many SHOULD have been added. Try also to output the value of getChildCount() and your i and j parameters.

For a wild guess, try this:

for (int i = 0; i < toBeDetached.size(); i++) {

     toBeDetached.get(i).detachSelf();

  }
like image 43
Magic Avatar answered Nov 14 '22 23:11

Magic


Ok with the help of Mr JohnEye, I've been able to fix the problem in my code. Consider the following snippet:

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

}

As I've mentioned, the game crashes because of a discrepancy in count between the 2 colliding objects. This situation happens when, as Mr. JohnEye points out, a sprite is spawned in the UI thread, but not in the Update thread as well. Furthermore, consider my createExplosion, createBomb and createSplat functions:

CREATE BOMB

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

CREATE EXPLOSION

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

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

Notice that all of them detach/generate other sprites in the runOnUpdate thread. The game crashes in my earlier run-throughs because I did not detach/generate all these sprites on the runOnUpdate class.

like image 41
Erasmus Avatar answered Nov 14 '22 22:11

Erasmus