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:
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.
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();
}
});
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();
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With