6

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.

Community
  • 1
  • 1
Erasmus
  • 427
  • 2
  • 10
  • 23

3 Answers3

4

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();
    }
});
JohnEye
  • 6,436
  • 4
  • 41
  • 67
  • I have done the Toast test on my code (placed the toasts during the mouse click event). It appears that I always have 1 child of the Bomb Group everytime, since it detaches whenever it hits the floor. I also have 1 instance of the troop. Also, let me clarify what you're suggesting; that I spawn the bomb on my runOnUpdate Thread? But isn;t that were the detaching should happen as well? – Erasmus Sep 24 '12 at 12:27
  • Yes, you want to have the spawning code running on the Update Thread as well. The UI Thread might call a method that would break the Update Thread. Consider the following situation - the update thread does something on a list of all the children on the scene. This operation takes a lot of time and the method that goes through the children obtains their number in the same way as you do and stores it in the memory. Let's say that there are seven children. While the loop does its thing, a method from the UI Thread removes five bodies and bang, the Update Thread's loop method goes out of bounds. – JohnEye Sep 24 '12 at 12:55
  • I updated my code to be able to spawn the bombs during the UI thread (when s collides with s2), as well as, spawn the bombs in the runOnUpdate (inside the for-each loop). It still doesn;t seem to work though. As I have mentioned, the size of both bombsGroup and troopsGroup remain 1 – Erasmus Sep 24 '12 at 13:19
  • I just noticed that you use postRunnable and not runOnUpdateThread when detaching Sprites, how about if you change the line `getEngine().getScene().postRunnable(new Runnable() {` to `runOnUpdateThread(new Runnable() {` – JohnEye Sep 24 '12 at 13:27
  • Updated it to runOnUpdateThread. It still has an index out of bounds error when the player exceeds a certain number of bombs (spams). – Erasmus Sep 24 '12 at 13:34
  • Are you detaching Sprites somewhere else outside of the code you posted? I think it must be caused by detaching Sprites outside of Update Thread. – JohnEye Sep 24 '12 at 13:49
  • Oh, I overlooked the onModifierFinished method, try putting the code that detaches pItem in runOnUpdateThread as well. – JohnEye Sep 24 '12 at 13:50
  • Updated my post and added createExplosion. Indeed I do detach sprites outside of the UI (I create functions to spawn sprites such as bombs and explosions then detach them after some time or event). Though, I'm a little confused about what you said regarding pItem. pItem is a local variable of the createBomb/Explosion functions. I can't reference them in the update, and I can already remove the bombs in the runOnUpdate too (detaching a child of the bombsGroup) – Erasmus Sep 24 '12 at 14:00
  • pItem is final, so you can reference it inside Runnable. Try this code: http://pastebin.com/e0hz202r – JohnEye Sep 24 '12 at 14:18
  • Hm. Well now it's working fine. the player can now spam the bombs, however, the bombs remain in place, suspended in midair, as per your code in that link. I added bombSprite.registerEntryModifier in order for the Y Movement modifier to take effect. The bombs now drop, but again, outOfBoundsError. I decided to toast out the # of bombs and troops. Since the bombs do not detach w/o the modifier, it doesnt crash, however, when the modifier is there, the bomb detaches upon impact, causing the code to crash. The toast shows there is an X to 1 ratio of bombs to troops when the mod is not registered – Erasmus Sep 24 '12 at 14:35
  • I admit I have no idea why that happens, I would consider hacking around the problem by using Sprite.setVisible(boolean visible) instead and recycling the bombs. – JohnEye Sep 24 '12 at 14:48
  • I think I solved it mr JohnEye, without using the hacks. I'm still doing some error checking. Will update my code if it works – Erasmus Sep 24 '12 at 15:05
  • Great, I am trembling with anticipation waiting to see the solution ;-) – JohnEye Sep 24 '12 at 15:22
  • Well so far so good. Actually you were right. The lesson to be learned here is about attaching/detaching in the runOnUpdateThread. I'm doing some tests before confirming the solution :D – Erasmus Sep 24 '12 at 15:33
  • Posted my answer. I'll award your bounty in 11 hours. Thanks again JohnEye! – Erasmus Sep 24 '12 at 15:51
1

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

  }
Mun0n
  • 4,438
  • 4
  • 28
  • 46
Magic
  • 11
  • 1
  • Hm, just to clarify, this snippet is put into the Update Thread yes? If so, I have to convert the loops into for loops instead of for-each – Erasmus Sep 24 '12 at 12:00
  • But you aren't using for-each loops, for-each is like for(Sprite s : bombGroup) – Shark Sep 26 '12 at 16:07
1

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.

Erasmus
  • 427
  • 2
  • 10
  • 23
  • You say that `a sprite is spawned in the UI thread, but not in the Update thread as well. It needs to be generated on both`. That is not really correct, it has to be done only in the Update Thread. You may want to change that so as not to confuse future visitors to this question. BTW, did you change your mind about the bounty? – JohnEye Sep 29 '12 at 15:28
  • Oops, totally forgot about that, here you go JohnEye! – Erasmus Sep 30 '12 at 14:40