1
\$\begingroup\$

I recently picked up libgdx and I'm obsessed. So far it's been smooth sailing until I've tried to implement an explosion animation based on an event. The game I'm developing is a simple space asteroid shooter, my method currently is checking for a bullet hit on an asteroid, creating a new explosion object, then rendering with that objects coordinates using an iterator. I've read that you have to set runTime to 0 after the animation is finished so the animation will restart. However I'm continually getting only the last frame, and then it disappears because I remove my explosion object based on the animation being finished. So it is being removed on time correctly, however the animation does not start on the right frame. Can anyone give me some insight?

Here is my PlayScreen class which uses a PlayRenderer and a PlayWorld, (PlayRenderer rendering our objects, PlayWorld containing our objects and updating accordingly)

public class PlayScreen implements Screen {

    private Game game;

    private PlayWorld world;
    private PlayRenderer renderer;

    private float runTime;

    public PlayScreen(Game game){
        this.game = game;
        world = new PlayWorld(game);
        renderer = new PlayRenderer(world);
    }

    @Override
    public void show() {

    }

    @Override
    public void render(float delta) {
        runTime += delta;
        renderer.render(runTime);
        world.update(delta);
    }

PlayWorld class which is where I have collision detection contained

public class PlayWorld {

    private Game game;

    private Player player;
    private Array<Bullet> bullets;
    private Array<Explosion> explosions;
    private ScrollBackground bg1, bg2;
    private Crater crater1, crater2, crater3, crater4, crater5;
    private float runTime;

    public static float explosionX, explosionY;
    public static boolean shot;

    public PlayWorld(Game game){
        this.game = game;
        initPlayer();
        initBackground();
        initBullets();
        initExplosions();
        initCraters();
        Gdx.input.setInputProcessor(new PlayHandler(player, bullets));
    }

    private void initPlayer(){
        player = new Player(SSGame.GAME_WIDTH / 2 - 19, 500, 38, 34);
    }

    private void initBackground(){
        bg1 = new ScrollBackground(0, 0, 25);
        bg2 = new ScrollBackground(0, -640, 25);
    }

    private void initBullets(){
        bullets = new Array<Bullet>();
    }

    private void initExplosions(){
        explosions = new Array<Explosion>();
    }

    private void initCraters(){
        crater1 = new Crater(50, -36, 31, 36);
        crater2 = new Crater(100, -50, 31, 36);
        crater3 = new Crater(170, -85, 31, 36);
        crater4 = new Crater(250, -125, 31, 36);
        crater5 = new Crater(75, -200, 31, 36);
    }

    public void update(float delta){
        runTime += delta;
        player.update(delta);
        bg1.update(delta);
        bg2.update(delta);
        updateBullets(delta);
        updateCraters(delta);
        updateCollisions(runTime, delta);
        updatePlayerCollisions();
    }

    private void updateBullets(float delta){
        for(Iterator<Bullet> itr = bullets.iterator(); itr.hasNext();){
            Bullet b = itr.next();
            b.update(delta);
            if(b.getY() < -17){
                itr.remove();
            }
        }
    }

    private void updateCraters(float delta){
        crater1.update(delta);
        crater2.update(delta);
        crater3.update(delta);
        crater4.update(delta);
        crater5.update(delta);
    }

    private void updateCollisions(float runTime, float delta){
        for(Iterator<Bullet> itr = bullets.iterator(); itr.hasNext();){
            Bullet b = itr.next();
            if(b.getY() > 0){
                if(b.getRect().overlaps(crater1.getRect())){
                    Explosion e = new Explosion(crater1.getX(), crater1.getY());
                    explosions.add(e);
                    itr.remove();
                    crater1.reset();
                    Bullet.score++;
                }else if(b.getRect().overlaps(crater2.getRect())){
                    Explosion e = new Explosion(crater2.getX(), crater2.getY());
                    explosions.add(e);
                    itr.remove();
                    crater2.reset();
                    Bullet.score++;
                }else if(b.getRect().overlaps(crater3.getRect())){
                    Explosion e = new Explosion(crater3.getX(), crater3.getY());
                    explosions.add(e);
                    itr.remove();
                    crater3.reset();
                    Bullet.score++;
                }else if(b.getRect().overlaps(crater4.getRect())){
                    Explosion e = new Explosion(crater4.getX(), crater4.getY());
                    explosions.add(e);
                    itr.remove();
                    crater4.reset();
                    Bullet.score++;
                }else if(b.getRect().overlaps(crater5.getRect())) {
                    Explosion e = new Explosion(crater5.getX(), crater5.getY());
                    explosions.add(e);
                    itr.remove();
                    crater5.reset();
                    Bullet.score++;
                }
            }
        }
    }

    private void updatePlayerCollisions(){
        if(player.getRect().overlaps(crater1.getRect())){
            game.setScreen(new GameOverScreen(game));
        }else if(player.getRect().overlaps(crater2.getRect())){
            game.setScreen(new GameOverScreen(game));
        }else if(player.getRect().overlaps(crater3.getRect())){
            game.setScreen(new GameOverScreen(game));
        }else if(player.getRect().overlaps(crater4.getRect())){
            game.setScreen(new GameOverScreen(game));
        }else if(player.getRect().overlaps(crater5.getRect())){
            game.setScreen(new GameOverScreen(game));
        }
    }

    public Player getPlayer(){
        return player;
    }

    public ScrollBackground getBg1(){
        return bg1;
    }

    public ScrollBackground getBg2(){
        return bg2;
    }

    public Array<Bullet> getBullets(){
        return bullets;
    }

    public Crater getCrater1(){
        return crater1;
    }

    public Crater getCrater2(){
        return crater2;
    }

    public Crater getCrater3(){
        return crater3;
    }

    public Crater getCrater4(){
        return crater4;
    }

    public Crater getCrater5(){
        return crater5;
    }

    public Array<Explosion> getExplosions(){
        return explosions;
    }

And finally my PlayRenderer class which is passed runTime, which is = delta, this is where I believe the issue is, however I cannot figure it out, I've inserted a few print statements so someone may be able to put me on the right path

public class PlayRenderer {

    private PlayWorld world;

    //Objects
    private Player player;
    private ScrollBackground bg1, bg2;
    private Array<Bullet> bullets;
    private Array<Explosion> explosions;
    private Crater crater1, crater2, crater3, crater4, crater5;

    //Assets
    private TextureRegion player_texture;
    private TextureRegion background;
    private TextureRegion bullet;
    private TextureRegion crater;
    private Animation explosion;
    private Array<Animation> explosionAnimArray;
    private BitmapFont font;

    private float animTime;

    private OrthographicCamera cam;

    private SpriteBatch batch;

    public PlayRenderer(PlayWorld world){
        this.world = world;
        initEquipment();
        initAssets();
        initObjects();
    }

    private void initEquipment(){
        cam = new OrthographicCamera();
        cam.setToOrtho(true, SSGame.GAME_WIDTH, SSGame.GAME_HEIGHT);
        batch = new SpriteBatch();
        batch.setProjectionMatrix(cam.combined);
    }

    private void initAssets(){
        player_texture = AssetLoader.ship;
        background = AssetLoader.bg;
        bullet = AssetLoader.bullet_basic;
        crater = AssetLoader.crater;
        explosion = AssetLoader.explosion;
        explosionAnimArray = new Array<Animation>();
        font = AssetLoader.font;
    }

    private void initObjects(){
        player = world.getPlayer();
        bg1 = world.getBg1();
        bg2 = world.getBg2();
        bullets = world.getBullets();
        crater1 = world.getCrater1();
        crater2 = world.getCrater2();
        crater3 = world.getCrater3();
        crater4 = world.getCrater4();
        crater5 = world.getCrater5();
        explosions = world.getExplosions();
    }

    public void render(float runTime){
        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        renderBackground();
        renderBullets();
        renderPlayer();
        renderCraters();
        renderExplosions(runTime);
        renderFont();
    }

    private void renderPlayer(){
        batch.begin();
        batch.enableBlending();
        batch.draw(player_texture, player.getX(), player.getY());
        batch.end();
    }

    private void renderBackground(){
        batch.begin();
        batch.draw(background, bg1.getX(), bg1.getY());
        batch.draw(background, bg2.getX(), bg2.getY());
        batch.end();
    }

    private void renderBullets(){
        for(Iterator<Bullet> itr = bullets.iterator(); itr.hasNext();){
            Bullet b = itr.next();
            batch.begin();
            batch.draw(bullet, b.getX(), b.getY());
            batch.end();
        }
    }

    private void renderCraters(){
        batch.begin();
        batch.draw(crater, crater1.getX(), crater1.getY());
        batch.draw(crater, crater2.getX(), crater2.getY());
        batch.draw(crater, crater3.getX(), crater3.getY());
        batch.draw(crater, crater4.getX(), crater4.getY());
        batch.draw(crater, crater5.getX(), crater5.getY());
        batch.end();
    }

    public void renderExplosions(float runTime){
        for(Iterator<Explosion> itr = explosions.iterator(); itr.hasNext();){
            Explosion e = itr.next();
            batch.begin();
            batch.draw(explosion.getKeyFrame(runTime), e.getX(), e.getY());
            System.out.println("" + runTime);
            batch.end();
            if(explosion.isAnimationFinished(runTime)){
                runTime = 0;
                System.out.println("RUNTIME: " + runTime);
                itr.remove();
            }
        }

    }

    private void renderFont(){
        batch.begin();
        font.setColor(Color.WHITE);
        font.draw(batch, "" + Bullet.score, 300, 20);
        batch.end();
    }

}

Here are my print reports for debugging:

MAIN MENU
BEGIN
EXPLOSION
EXPLOSION
1.974246
RUNTIME: 0.0
0.0
1.9911842
RUNTIME: 0.0
EXPLOSION
EXPLOSION
4.626008
RUNTIME: 0.0
0.0
4.6428485
RUNTIME: 0.0
EXPLOSION
7.264123
RUNTIME: 0.0
\$\endgroup\$
0
\$\begingroup\$

The LibGDX Animation class is primarily a container of keyframes (Texture or TextureRegion instances) with some extra logic to pick the correct keyframe given a time and a play mode (normal, loop, ping-pong, etc...)

The important part is that the timer that is used to get a keyframe is not built in to the Animation instance and so there should be a separate timer variable for every running animation. It looks like you might be running with a single animation timer for all your explosions right now.

In your case, since you create a new Explosion instance each time you spawn an explosion, it might be best to put the animation timer float inside the Explosion class and increment all of them by delta time during the update loop.

Something like:

Animation explosionAnim = ...;

// In world.update(delta):
for (Explosion explosion : explosions) {
    explosion.animTimer += delta;
    if (explosionAnim.isAnimFinished(explosion.animTimer) {
        // TODO: cleanup 'finished' explosions here...
    }
}

// In renderExplosions():
for (Explosion explosion : explosions) {
    batch.draw(explosionAnim.getKeyFrame(explosion.animTimer), explosion.getX(), explosion.getY());
}

Even better (if you have lots of them) would be to Pool the Explosion instances so that you aren't allocating new memory all the time when you create and destroy them... This is a bit more complex (but not all that much) so I'll just link to the LibGDX wiki object pooling example code: https://github.com/libgdx/libgdx/wiki/Memory-management

If all else fails, try updating/drawing a single Animation instance in isolation just to make sure the Animation object is set up correctly and that your timer is behaving as expected.

|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ Wow thank you so much, I was convinced nobody would get back to me, this was greatly helpful, and as I'm trying to optimize this game for Android it seems like a brilliant idea to do obj pooling for efficiency so will absolutely be looking into that. I had the completely wrong idea about how runTime and delta worked with animations so it makes total sense that you need a personal timer for each explosion. Thank you so much again! \$\endgroup\$ – s.gang Jul 13 '16 at 12:00
  • \$\begingroup\$ Happy to help! I have a lot of LibGDX projects in my GitHub, but this one in particular has some relevant stuff for your situation (including basic object pooling code): Bulwark - Effects Code \$\endgroup\$ – Lando System Jul 13 '16 at 16:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for?Browse other questions tagged or ask your own question.