Search code examples
javalibgdx

Libgdx iterating speeding up animation?


GitHub: https://github.com/alex578344/RocketDefender.git

In my render loop I iterate through a series of rockets and when i click on them a explosion animation is shown. The problem is the animation is too quick and how however i change the float value for the run time of the Animation it seems to still show at the same really quick speed.

  textureAtlas = new TextureAtlas(Gdx.files.internal("ExplosionAnimation.atlas"));
        explosionA = new Animation(10f, textureAtlas.getRegions());

 public void render(float runTime){

        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        cam.update();

        batch.setProjectionMatrix(cam.combined);

        batch.begin();
        //BATCH START


        batch.draw(AssetLoader.background, 0, 0 , GWidth,GHeight);

        for(Rectangle rocket : rockets) {
            batch.draw(AssetLoader.rocket ,rocket.x, rocket.y,
                    rocket.width, rocket.height);
        }

        if (Gdx.input.justTouched()){

            Vector3 v = new Vector3(Gdx.input.getX(), Gdx.input.getY(), 0);
            v = cam.unproject(v);

            tap_X = (int) v.x;
            tap_Y = (int) v.y;

            Gdx.app.log("MyTag", String.valueOf(tap_X));
            Gdx.app.log("MyTag", String.valueOf(tap_Y));



        }

        Iterator<Rectangle> iter = rockets.iterator();
        while(iter.hasNext()) {

            Rectangle rocket = iter.next();
            rocket.y -= 140 * Gdx.graphics.getDeltaTime();
            if(rocket.y + 32 < 0) iter.remove();

            if (rocket.x - 5 < tap_X && tap_X < rocket.x + rocket.width + 5) {

                if (tap_Y > rocket.y - 5 && tap_Y < rocket.y + rocket.height + 5) {
                    batch.draw(AssetLoader.explosionA.getKeyFrame(runTime, false), rocket.x, rocket.y,
                            rocket.width, rocket.height);
                    iter.remove();
                }
            }
        }

        //BATCH END
        batch.end();


        if(TimeUtils.nanoTime() - lastDropTime > 1000000000) spawnRocket();

    }

Solution

  • The problem is that you're rendering the explosion for only one frame, and then immediately removing that corresponding Rectangle from the game. Right here:

                if (tap_Y > rocket.y - 5 && tap_Y < rocket.y + rocket.height + 5) {
                    batch.draw(AssetLoader.explosionA.getKeyFrame(runTime, false), rocket.x, rocket.y,
                            rocket.width, rocket.height);
                    iter.remove();
                }
    

    Also note that you cannot simply use runTime for all your explosion animations, because then they will all be in sync with each other, and if it's not a looping animation, then a few seconds into the game they will all simply show the last frame of the animation forever.

    There are a number of different approaches you could take for fixing this. Here's one:

    Instead of using Rectangle, use a subclass of Rectangle like this that lets you track this individual rocket's animation time and whether it has exploded yet:

    public class Rocket extends Rectangle {
        public boolean exploded = false;
        public float explosionAge = 0;
    
        public Rocket(float x, float y, float width, float height) {
            super(x, y, width, height);
        }
    
        public Rocket(Rectangle rect) {
            super(rect);
        }
    }
    

    So you would be using this class instead of Rectangle for all your Rockets. Change your rockets array to be of type <Rocket>.

    Then in your loop, instead of drawing and removing a rocket when it's tapped, just flip it's exploded boolean on. And then for exploded rockets, you can increment their age, draw their animation, and remove them if the animation is complete. (I'm assuming a non-looping animation here.)

    Also, remove the loop where you draw all the rockets. We'll move the drawing step into the bottom loop, so it can decide whether to draw the plain rocket or an explosion:

    float deltaTime = Gdx.graphics.getDeltaTime();
    
    Iterator<Rocket> iter = rockets.iterator();
    while(iter.hasNext()) {
    
        Rocket rocket = iter.next();
        rocket.y -= 140 * deltaTime;
    
        if (!rocket.exploded){
    
            if(rocket.y + 32 < 0) {
                iter.remove();
                continue;
            }
    
            if (rocket.x - 5 < tap_X && tap_X < rocket.x + rocket.width + 5) {
    
                if (tap_Y > rocket.y - 5 && tap_Y < rocket.y + rocket.height + 5) {
                    rocket.exploded = true;
                    rocket.explosionAge = -deltaTime; //This is so the first frame will start from 0, since deltaTime is added before it's drawn below
                }
            }
        }
    
        if (rocket.exploded){
            rocket.explosionAge += deltaTime;
            if (rocket.explosionAge > AssetLoader.explosionA.getAnimationDuration()){
                iter.remove();
                continue;
            }
    
            batch.draw(AssetLoader.explosionA.getKeyFrame(rocket.explostionAge, false), 
                rocket.x, rocket.y, rocket.width, rocket.height);
        } else {
            batch.draw(AssetLoader.rocket, 
                rocket.x, rocket.y, rocket.width, rocket.height);
        }
    }
    

    This above example is for minimal changes to your current code. But actually I would advise against conflating game world updates and rendering. It makes it harder to debug and find your way around the code if you need to change some behavior. I would instead have an update method that updates the positions of the rockets and their explosion states, and a separate draw method that simply draws them all.