AnimatedSprite.java

Any community contributions to libgdx go here! Some may get included in the core API when permission is granted.

Re: AnimatedSprite.java

Postby rage » Fri Aug 17, 2012 12:02 am

cyble wrote:I studied Wavesonic's implementation and the example from the tests branch and I think it would be cool to have a generic animatedsprite class that should work with textureatlas, file handles, inmemory textures and have all benefits from our known sprite class.

So here it is (including different playback and looping types (reversed, pingpong, random)).


Thanks a bunch cyble, I made a few tweaks because there was one bug and I needed a feature.

    Everytime you call update() it resets the x, y coordinates of the sprite, which was super annoying having to set the x, y each time after update.
    Added a geter isPlaying() so that I can find out if the animation has run it's course.

Hope that helps someone.

Code: Select all
package com.your.package;

import java.util.ArrayList;
import java.util.List;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;

/*
 * Example 1: Create an animated sprite from texture atlas
 *
 * Create the sprite (in create() method):
 *     atlas = new TextureAtlas(Gdx.files.internal("data/sprites.txt"), Gdx.files.internal("data/"));
 *     mySprite = new AnimatedSprite(atlas, "mySprite");
 *
 *     mySprite.setAnimationRate(60);
 *     mySprite.loop(true);
 *     mySprite.play();
 *
 * Render the sprite (in render() method):
 *     mySprite.update(Gdx.graphics.getDeltaTime());
 *     mySprite.draw(batch);
 *
 * Example 2: Create an animated sprite from file and play reversed and after that pingpong-loop :)
 *
 * Create the sprite (in create() method):
 *     mySprite = new AnimatedSprite(Gdx.files.internal("data/walkanim.png"), 64, 64);
 *
 *     mySprite.setAnimationRate(8);
 *      mySprite.loop(true, AnimatedSprite_LoopType.PINGPONG);
 *      mySprite.play(AnimatedSprite_PlayType.REVERSED);
 *
 * Render the sprite (in render() method):
 *     mySprite.update(Gdx.graphics.getDeltaTime());
 *     mySprite.draw(batch);
 *
 *
 * Example 3: Find out if animation is still running
 * mySprite.update(Gdx.graphics.getDeltaTime());
 * if(mySprite.isPlaying()){
 *      //draw your sprite
 *      mySprite.draw(batch);
 * }else{
 *      //do something like remove your object from the stage
 *      //or omit and do no sprite is drawn
 * }
 *
 */
public class AnimatedSprite extends Sprite
{
    protected List<TextureRegion> frameRegions;
    protected int currentFrame;

    protected float frameRate;
    protected float totalDeltaTime;
    protected boolean isPlaying;
    protected boolean isLooping;
    protected boolean isReversed;

    protected AnimatedSprite_PlayType playType;
    protected AnimatedSprite_LoopType loopType;

    public enum AnimatedSprite_PlayType {NORMAL, REVERSED, RANDOM}
    public enum AnimatedSprite_LoopType {NORMAL, PINGPONG}

    public final boolean looping()
    {
        return isLooping;
    }

    public final boolean isPlaying(){
        return isPlaying;
    }

    /*
    * Initialize
    */
    private void Init()
    {
        frameRate = 1.0f;
        isPlaying = false;
        isLooping = true;
        isReversed = false;

        currentFrame = 0;

        frameRegions = new ArrayList<TextureRegion>();
    }

    // Initialize our textureregion (too bad the super constructor must be the first call in overloaded constructors in java,
    // because it's the same code as in the super constructor. Maybe badlogic can create a protected init in the sprite class ?)
    private void CreateSprite(TextureRegion region)
    {
        setRegion(region);
        setColor(1, 1, 1, 1);
        setSize(Math.abs(region.getRegionWidth()), Math.abs(region.getRegionHeight()));
        setOrigin(getWidth() / 2, getHeight() / 2);

        // Set our playhead at the correct position
        stop();
    }


    /*
     * Create an animated sprite, based on texture-atlas content.
     * @param textureAtlas Reference to the TextureAtlas instance
     * @param regionName Name of region to be used
    */
    public AnimatedSprite(final TextureAtlas textureAtlas, final String regionName)
    {
        Init();

        List<AtlasRegion> atlasFrameRegions = textureAtlas.findRegions(regionName);
        for(AtlasRegion rg : atlasFrameRegions)
            frameRegions.add((TextureRegion)rg);

        // Initialize our first frame
        CreateSprite(frameRegions.get(0));
    }


    /*
    * Create an animated sprite, based on a filehandle
    * @param file File to load
    * @param tileWidth Width of each frame tile
    * @param tileHeight Height of each frame tile
    */
    public AnimatedSprite(final FileHandle file, final int tileWidth, final int tileHeight)
    {
        Texture texture = new Texture(file);
        CreateAnimatedSpriteFromTexture(texture, tileWidth, tileHeight);
    }


    /*
    * Create an animated sprite, based on an existing texture instance
    * @param texture Reference to the texture instance
    * @param tileWidth Width of each frame tile
    * @param tileHeight Height of each frame tile
    */
    public AnimatedSprite(final Texture texture, final int tileWidth, final int tileHeight)
    {
        CreateAnimatedSpriteFromTexture(texture, tileWidth, tileHeight);
    }


    /* Wrapper for our texture based constructors.
    * @param texture Reference to the texture instance
    * @param tileWidth Width of each frame tile
    * @param tileHeight Height of each frame tile
    */
    private void CreateAnimatedSpriteFromTexture(final Texture texture, final int tileWidth, final int tileHeight)
    {
        Init();

        // Split the texture into tiles (from upper left corner to bottom right corner)
        TextureRegion[][] tiles = TextureRegion.split(texture, tileWidth, tileHeight);

        // Now unwrap the two dimensional array into a single array with all the tiles in the correct animation order.
        for(int row = 0; row < tiles.length; row++) {
            for(int col = 0; col < tiles[row].length; col++) {
                frameRegions.add(tiles[row][col]);
            }
        }

        // Initialize our first frame
        CreateSprite(frameRegions.get(0));
    }


    /*
    * Set at what rate the animated sprite should be playing.
    * @param fps Framerate in frames per second
    */
    public void setAnimationRate(final int fps)
    {
        frameRate = 1.0f / (float)fps;
    }


    /*
    * Loop the animation
    * @param isLooping If true: the animation plays infinite
    */
    public void loop(boolean isLooping)
    {
        loop(isLooping, AnimatedSprite_LoopType.NORMAL);
    }

    /*
    * Loop the animation (use special looping abilities)
    * @param isLooping If true: the animation plays infinite
    * @param loopType NORMAL: last frame ? start at frame 0. PINGPONG: last frame ? reverse animation.
    */
    public void loop(boolean isLooping, AnimatedSprite_LoopType loopType)
    {
        this.loopType = loopType;
        this.isLooping = isLooping;
    }


    /*
    * Stop (and therefore rewind and play)
    *
    */
    public void restart()
    {
        stop();
        play();
    }


    /*
    * Start or resume animation
    */
    public void play()
    {
        play(AnimatedSprite_PlayType.NORMAL);
    }


    /*
    * Start or resume animation (use special playing abilities)
    * @param playType NORMAL: start at first frame. REVERSED: start at last frame. RANDOM: pick a random frame.
    */
    public void play(AnimatedSprite_PlayType playType)
    {
        this.playType = playType;
        isReversed = this.playType == AnimatedSprite_PlayType.REVERSED;
        isPlaying = true;
        totalDeltaTime = 0;
    }

    /*
    * Pauses animation
    */
    public void pause()
    {
        isPlaying = false;
        totalDeltaTime = 0;
    }


    /*
    * Stops and rewinds animation.
    */
    public void stop()
    {
        pause();
        currentFrame = getFirstFrameNumber();
    }


    /* Gets the index of the last frame.
    * @return Returns 0 or last frame index.
    */
    private int getFirstFrameNumber()
    {
        return (playType == AnimatedSprite_PlayType.REVERSED ? frameRegions.size()-1 : 0);
    }


    /* Updates and increments the animation playhead to the next frame
    * @param deltaTime Frame duration in seconds.
    */
    public void update(final float deltaTime)
    {
        if( isPlaying )
        {
            totalDeltaTime += deltaTime;
            if (totalDeltaTime > frameRate)
            {
                totalDeltaTime = 0;

                if (this.playType == AnimatedSprite_PlayType.RANDOM)
                {
                    currentFrame = (int)Math.round(Math.random() * (frameRegions.size()-1));
                }
                else
                {
                    currentFrame += (isReversed ? -1 : 1);

                    // Back to first
                    if (currentFrame >= frameRegions.size() || currentFrame < 0)
                    {
                        // If not looping, stop
                        if(!isLooping)
                        {
                            stop();
                        }
                        else
                        {
                            switch (loopType)
                            {
                                case PINGPONG:
                                    isReversed = !isReversed;
                                    if (isReversed)
                                    {
                                        currentFrame = frameRegions.size() - 2;
                                        if (currentFrame < 0)
                                            currentFrame = 0;
                                    }
                                    else
                                        currentFrame = 0;

                                    break;
                                case NORMAL:
                                default:
                                    currentFrame = getFirstFrameNumber();
                                    break;
                            }

                        }
                    }
                }

                TextureRegion region = frameRegions.get(currentFrame);
                // set the region
                setRegion(region);

                // Give an AtlasRegion some special attention (boundaries may be trimmed)
                if (region instanceof AtlasRegion)
                {
                    // Set the region and make sure the sprite is set at the original cell size
                    AtlasRegion atlasRegion = (AtlasRegion)region;
                    if (atlasRegion.rotate) rotate90(true);
                    setOrigin(atlasRegion.originalWidth / 2, atlasRegion.originalHeight / 2);
                    super.setBounds(this.getX(), this.getY(), Math.abs(atlasRegion.getRegionWidth()), Math.abs(atlasRegion.getRegionHeight()));
                }

                setColor(1, 1, 1, 1);
            }
        }
    }
}
rage
 
Posts: 1
Joined: Thu Aug 16, 2012 11:47 pm

Re: AnimatedSprite.java

Postby Sako » Mon Jan 21, 2013 9:11 am

Hello,

I just wanted to share my custom implementation of an animated Actor. It is heavily based on com.badlogic.gdx.scenes.scene2d.ui.Image but holds an Animation reference instead of a Drawable.

It's a starting implementation, I might fix stuff in the future, but works for now.

WARNING: will work VERY wacky if you pack the animation assets using libgdx's TexturePacker with "stripWhitespace" or "rotation" parameters active, because TextureRegion drawing does not rotate/pad them back, unlike Sprite does. To solve this, you can cast the TextureRegion to a Sprite inside the draw() method. I might fix this myself in the future, I'm making a very animation-heavy game and I have to check the memory impact of casting each region to a Sprite.

Code: Select all
package com.pimentoso.xxx.scene2d;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Widget;
import com.badlogic.gdx.scenes.scene2d.utils.Align;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.utils.Scaling;

/**
 * Animated scene2d actor. Similar to {@link Image} but uses {@link Animation} instead of a {@link Drawable}.
 * @author Pimentoso
 */
public class AnimatedImage extends Widget {

   private Scaling scaling;
   private int align = Align.center;
   private float imageX, imageY, imageWidth, imageHeight;
   
   private Animation animation;
   private TextureRegion region;
   public int state;
   public float stateTime;
   
   public AnimatedImage() {
      this((Animation) null);
   }
   
   public AnimatedImage(Animation animation) {
      this(animation, Scaling.stretch, Align.center);
   }
   
   public AnimatedImage(Animation animation, Scaling scaling, int align) {
      setAnimation(animation);
      this.scaling = scaling;
      this.align = align;
      setWidth(getPrefWidth());
      setHeight(getPrefHeight());
   }
   
   @Override
   public void draw(SpriteBatch batch, float parentAlpha) {
      validate();

      Color color = getColor();
      batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);

      float x = getX();
      float y = getY();
      float scaleX = getScaleX();
      float scaleY = getScaleY();

      if (animation != null) {
         region = animation.getKeyFrame(stateTime);
         float rotation = getRotation();
         if (scaleX == 1 && scaleY == 1 && rotation == 0)
            batch.draw(region, x + imageX, y + imageY, imageWidth, imageHeight);
         else {
            batch.draw(region, x + imageX, y + imageY, getOriginX() - imageX, getOriginY() - imageY, imageWidth, imageHeight,
               scaleX, scaleY, rotation);
         }
      }
   }
   
   @Override
   public void layout() {
      float regionWidth, regionHeight;
      if (animation != null) {
         regionWidth = animation.getKeyFrame(0).getRegionWidth();
         regionHeight = animation.getKeyFrame(0).getRegionHeight();
      } else
         return;

      float width = getWidth();
      float height = getHeight();

      Vector2 size = scaling.apply(regionWidth, regionHeight, width, height);
      imageWidth = size.x;
      imageHeight = size.y;

      if ((align & Align.left) != 0)
         imageX = 0;
      else if ((align & Align.right) != 0)
         imageX = (int)(width - imageWidth);
      else
         imageX = (int)(width / 2 - imageWidth / 2);

      if ((align & Align.top) != 0)
         imageY = (int)(height - imageHeight);
      else if ((align & Align.bottom) != 0)
         imageY = 0;
      else
         imageY = (int)(height / 2 - imageHeight / 2);
   }

   @Override
   public void act(float delta) {
      super.act(delta);
      stateTime += delta;
   }
   
   public void setState(int state) {
      this.state = state;
      stateTime = 0.0f;
   }
   
   public void setPlayMode (int playMode) {
      animation.setPlayMode(playMode);
   }

   public Animation getAnimation() {
      return animation;
   }
   
   public void setAnimation(Animation animation) {
      if (animation != null) {
         if (this.animation == animation) return;
         invalidateHierarchy();
      } else {
         if (getPrefWidth() != 0 || getPrefHeight() != 0) invalidateHierarchy();
      }
      this.animation = animation;
   }

   public void setScaling (Scaling scaling) {
      if (scaling == null) throw new IllegalArgumentException("scaling cannot be null.");
      this.scaling = scaling;
   }

   public void setAlign (int align) {
      this.align = align;
   }

   public float getMinWidth () {
      return 0;
   }

   public float getMinHeight () {
      return 0;
   }

   public float getPrefWidth () {
      if (animation != null) return animation.getKeyFrame(0).getRegionWidth();
      return 0;
   }

   public float getPrefHeight () {
      if (animation != null) return animation.getKeyFrame(0).getRegionHeight();
      return 0;
   }

   public float getImageX () {
      return imageX;
   }

   public float getImageY () {
      return imageY;
   }

   public float getImageWidth () {
      return imageWidth;
   }

   public float getImageHeight () {
      return imageHeight;
   }
}
Sako
 
Posts: 136
Joined: Sat Sep 10, 2011 11:39 am

Re: AnimatedSprite.java

Postby dermetfan » Mon Jun 10, 2013 3:33 pm

I wrote my own AnimatedSprite using com.badlogic.gdx.graphics.g2d.Animation.

Find it on Bitbucket or here (may be outdated):

It is now part of my mini libdgx-utils library.

Code: Select all
package net.dermetfan.libgdx.graphics;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Graphics;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

/**
 * An {@link AnimatedSprite} holds an {@link Animation} and sets the {@link Texture} of its super type {@link Sprite} to the correct one according to the information in the {@link Animation}.
 * Usage:
 * <pre>Animation animation = new Animation(1 / 3f, new TextureRegion(frame1), new TextureRegion(frame2), new TextureRegion(frame3));
animation.setPlayMode(Animation.LOOP);
animatedSprite = new AnimatedSprite(animation);</pre>
 * Draw it using any of the {@link Sprite Sprite's} draw methods:
<pre>animatedSprite.draw(batch);</pre>
 *
 * @author dermetfan
 */
public class AnimatedSprite extends Sprite {

   /** the {@link Animation} to display */
   private Animation animation;

   /** the current time of the {@link Animation} */
   private float time;

   /** if the animation is playing */
   private boolean playing = true;

   /** if the animation should be updated every time it's drawn */
   private boolean autoUpdate = true;

   /** if the size of the previous frame should be kept by the following frames */
   private boolean keepSize;

   /** if a frame should be centered in its previous one's center if it's smaller */
   private boolean centerFrames;

   /**
    * creates a new {@link AnimatedSprite} with the given {@link Animation}
    * @param animation the {@link #animation} to use
    */
   public AnimatedSprite(Animation animation) {
      this(animation, false);
   }

   /**
    * creates a new {@link AnimatedSprite} with the given {@link Animation}
    * @param animation the {@link #animation} to use
    * @param keepSize the {@link #keepSize} to use
    */
   public AnimatedSprite(Animation animation, boolean keepSize) {
      super(animation.getKeyFrame(0));
      this.animation = animation;
      this.keepSize = keepSize;
   }

   /** updates the {@link AnimatedSprite} with the delta time fetched from {@link Graphics#getDeltaTime()  Gdx.graphics.getDeltaTime()} */
   public void update() {
      update(Gdx.graphics.getDeltaTime());
   }

   /** updates the {@link AnimatedSprite} with the given delta time */
   public void update(float delta) {
      if(playing) {
         setRegion(animation.getKeyFrame(time += delta));
         if(!keepSize)
            setSize(getRegionWidth(), getRegionHeight());
      }
   }

   @Override
   public void draw(SpriteBatch spriteBatch) {
      if(centerFrames && !keepSize) {
         float x = getX(), y = getY(), width = getWidth(), height = getHeight(), originX = getOriginX(), originY = getOriginY();

         if(autoUpdate)
            update();

         float differenceX = width - getRegionWidth(), differenceY = height - getRegionHeight();
         setOrigin(originX - differenceX / 2, originY - differenceY / 2);
         setBounds(x + differenceX / 2, y + differenceY / 2, width - differenceX, height - differenceY);

         super.draw(spriteBatch);

         setOrigin(originX, originY);
         setBounds(x, y, width, height);
         return;
      }

      if(autoUpdate)
         update();

      super.draw(spriteBatch);
   }

   /** sets {@link #playing} to true */
   public void play() {
      playing = true;
   }

   /** sets {@link #playing} to false */
   public void pause() {
      playing = false;
   }

   /** pauses and sets the {@link #time} to 0 */
   public void stop() {
      playing = false;
      time = 0;
   }

   /** @param time the {@link #time} to go to */
   public void setTime(float time) {
      this.time = time;
   }

   /** @return the current {@link #time} */
   public float getTime() {
      return time;
   }

   /** @return the {@link #animation} */
   public Animation getAnimation() {
      return animation;
   }

   /** @param animation the {@link #animation} to set */
   public void setAnimation(Animation animation) {
      this.animation = animation;
   }

   /** @return if this {@link AnimatedSprite} is playing */
   public boolean isPlaying() {
      return playing;
   }

   /** @param playing if the {@link AnimatedSprite} should be playing */
   public void setPlaying(boolean playing) {
      this.playing = playing;
   }

   /** @return if the {@link #animation} has finished playing */
   public boolean isAnimationFinished() {
      return animation.isAnimationFinished(time);
   }

   /** @return the {@link #autoUpdate} */
   public boolean isAutoUpdate() {
      return autoUpdate;
   }

   /** @param autoUpdate the {@link #autoUpdate} to set */
   public void setAutoUpdate(boolean autoUpdate) {
      this.autoUpdate = autoUpdate;
   }

   /** @return the {{@link #keepSize} */
   public boolean isKeepSize() {
      return keepSize;
   }

   /** @param keepSize the {@link #keepSize} to set */
   public void setKeepSize(boolean keepSize) {
      this.keepSize = keepSize;
   }

   /** @return the {@link #centerFrames} */
   public boolean isCenterFrames() {
      return centerFrames;
   }

   /** @param centerFrames the {@link #centerFrames} to set */
   public void setCenterFrames(boolean centerFrames) {
      this.centerFrames = centerFrames;
   }

}


Usage:
Code: Select all
Animation animation;
TextureRegion frame1, frame2, frame3;
// ...
animation = new Animation(1 / 3f, frame1, frame2, frame3);

// or load from a TextureAtlas
TextureAtlas frames = new TextureAltas("atlas.pack");
Animation = new Animation(1 / 3f, frames.getRegions());

animation.setPlayMode(Animation.LOOP);
animatedSprite = new AnimatedSprite(animation);

// draw it by calling any of the Sprite's draw methods
animatedSprite.draw(batch);
Last edited by dermetfan on Fri Aug 09, 2013 3:18 am, edited 5 times in total.
dermetfan
 
Posts: 283
Joined: Fri May 03, 2013 8:57 pm

Re: AnimatedSprite.java

Postby xeoshow » Tue Jun 25, 2013 6:17 am

Hi dermetfan,

If I have 5000 enemies with animation, I should still new AnimatedSprite with 5000 instances as below, right? Will this cause any extra overhead? Thanks!

Code: Select all
Animation animation1 = new Animation(1 / 3f, new TextureRegion(frame1), new TextureRegion(frame2), new TextureRegion(frame3));
animation1.setPlayMode(Animation.LOOP);
animatedSprite1 = new AnimatedSprite(animation1);

// draw it by calling any of the Sprite's draw methods
animatedSprite1.draw(batch);

Animation animation2 = new Animation(...);
animation2.setPlayMode(Animation.LOOP);
animatedSprite2 = new AnimatedSprite(animation2);

// draw it by calling any of the Sprite's draw methods
animatedSprite2.draw(batch);

......

animatedSprite5000.draw(batch);


dermetfan wrote:I wrote my own AnimatedSprite using com.badlogic.gdx.graphics.g2d.Animation.

Code: Select all
package net.dermetfan.libgdx.graphics;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Graphics;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

/**
 * An {@link AnimatedSprite} holds an {@link Animation} and set's the {@link Texture} of its super type {@link Sprite} to the correct one according to the information in the {@link Animation}.
 * Usage:
 * <pre>Animation animation = new Animation(1 / 3f, new TextureRegion(frame1), new TextureRegion(frame2), new TextureRegion(frame3));
animation.setPlayMode(Animation.LOOP);
animatedSprite = new AnimatedSprite(animation);</pre>
 * Draw it using any of the {@link Sprite Sprite's} draw methods:
<pre>animatedSprite.draw(batch);</pre>
 *
 * @author dermetfan
 */
public class AnimatedSprite extends Sprite {

   /** the {@link Animation} to display */
   private Animation animation;

   /** the current time of the {@link Animation} */
   private float time;

   /** if the animation is playing */
   private boolean playing = true;

   /** if the animation should be updated every time it's drawn */
   private boolean autoUpdate = true;

   /** if the size of the first frame should be kept by the following frames */
   private boolean keepSize;

   /**
    * creates a new {@link AnimatedSprite} with the given {@link Animation}
    * @param animation
    */
   public AnimatedSprite(Animation animation) {
      this(animation, false);
   }
   
   /**
    * creates a new {@link AnimatedSprite} with the given {@link Animation}
    * @param animation the {@link Animation} to use
    * @param keepSize
    */
   public AnimatedSprite(Animation animation, boolean keepSize) {
      super(animation.getKeyFrame(0));
      this.animation = animation;
      this.keepSize = keepSize;
   }

   /** updates the {@link AnimatedSprite} with the delta time fetched from {@link Graphics#getDeltaTime()  Gdx.graphics.getDeltaTime()} */
   public void update() {
      update(Gdx.graphics.getDeltaTime());
   }

   /** updates the {@link AnimatedSprite} with the given delta time */
   public void update(float delta) {
      if(playing) {
         setRegion(animation.getKeyFrame(time += delta).getTexture());
         if(!keepSize)
            setSize(getRegionWidth(), getRegionHeight());
      }
   }

   @Override
   public void draw(SpriteBatch spriteBatch) {
      if(autoUpdate)
         update();
      super.draw(spriteBatch);
   }

   @Override
   public void draw(SpriteBatch spriteBatch, float alphaModulation) {
      if(autoUpdate)
         update();
      super.draw(spriteBatch, alphaModulation);
   }

   /** sets playing to true */
   public void play() {
      playing = true;
   }

   /** sets playing to false */
   public void pause() {
      playing = false;
   }

   /** pauses the {@link AnimatedSprite} and seeks to 0 */
   public void stop() {
      pause();
      seek(0);
   }

   /** @return go to the given time */
   public void seek(float time) {
      this.time = time;
   }

   /** @return the current time */
   public float getTime() {
      return time;
   }

   /** @return the {@link Animation} */
   public Animation getAnimation() {
      return animation;
   }

   /** @param animation the {@link Animation} to set */
   public void setAnimation(Animation animation) {
      this.animation = animation;
   }

   /** @return if this {@link AnimatedSprite} is playing */
   public boolean isPlaying() {
      return playing;
   }

   /** @param playing if the {@link AnimatedSprite} should be playing */
   public void setPlaying(boolean playing) {
      this.playing = playing;
   }

   /** @return the autoUpdate */
   public boolean isAutoUpdate() {
      return autoUpdate;
   }

   /** @param autoUpdate the autoUpdate to set */
   public void setAutoUpdate(boolean autoUpdate) {
      this.autoUpdate = autoUpdate;
   }

   /** @return the keepSize */
   public boolean isKeepSize() {
      return keepSize;
   }

   /** @param keepSize the keepSize to set */
   public void setKeepSize(boolean keepSize) {
      this.keepSize = keepSize;
   }

}


Usage:
Code: Select all
Animation animation = new Animation(1 / 3f, new TextureRegion(frame1), new TextureRegion(frame2), new TextureRegion(frame3));
animation.setPlayMode(Animation.LOOP);
animatedSprite = new AnimatedSprite(animation);

// draw it by calling any of the Sprite's draw methods
animatedSprite.draw(batch);
xeoshow
 
Posts: 166
Joined: Mon Mar 18, 2013 1:12 am

Re: AnimatedSprite.java

Postby dermetfan » Tue Jun 25, 2013 11:41 am

xeoshow wrote:Hi dermetfan,

If I have 5000 enemies with animation, I should still new AnimatedSprite with 5000 instances as below, right? Will this cause any extra overhead? Thanks!

Code: Select all
Animation animation1 = new Animation(1 / 3f, new TextureRegion(frame1), new TextureRegion(frame2), new TextureRegion(frame3));
animation1.setPlayMode(Animation.LOOP);
animatedSprite1 = new AnimatedSprite(animation1);

// draw it by calling any of the Sprite's draw methods
animatedSprite1.draw(batch);

Animation animation2 = new Animation(...);
animation2.setPlayMode(Animation.LOOP);
animatedSprite2 = new AnimatedSprite(animation2);

// draw it by calling any of the Sprite's draw methods
animatedSprite2.draw(batch);

......

animatedSprite5000.draw(batch);


If you did it like this, you would have 5000 individually playing animations, each holding their own Animation with its own TextureRegions, indeed causing alot extra overhead.

Assuming the animation should be the same for all enemies, you could create one Animation and pass it into the AnimatedSprite constructor. This will result in 5000 AnimatedSprite instances, each holding a pointer to the Animation which holds the TextureRegions. This way, you just have the Animation and TextureRegions once, causing no extra overhead.
Code: Select all
// create one Animation for all enemies
Animation animation = new Animation(1 / 3f, new TextureRegion(frame1), new TextureRegion(frame2), new TextureRegion(frame3));
animation.setPlayMode(Animation.LOOP);

// create the individually playing AnimatedSprites
animatedSprite1 = new AnimatedSprite(animation);
animatedSprite2 = new AnimatedSprite(animation);
animatedSprite3 = new AnimatedSprite(animation);


The 5000 AnimatedSprite instances each hold a pointer to the Animation, but they still hold their own time variable and three booleans. This is absolutely no problem at all, and even necessary if you want your animations to play individually.

If you want to save as much memory as possible, you could even go one step furter and create one AnimatedSprite for all enemies. Because each enemy updates the Animation when drawing by default, the Animation is played 5000 times faster, so you have to update it manually. That's a good way if it's ok to have the same animation state for every enemy.
Code: Select all
// create one AnimatedSprite for all enemies
Animation animation = new Animation(1 / 3f, new TextureRegion(frame1), new TextureRegion(frame2), new TextureRegion(frame3));
animation.setPlayMode(Animation.LOOP);
animatedSprite = new AnimatedSprite(animation);
animatedSprite.setAutoUpdate(false);

// set the one animation to all the enemies
enemy1.setSprite(animatedSprite);
enemy2.setSprite(animatedSprite);

// update the AnimatedSprite for all the enemies
@Override
public void render(float delta) {
    animatedSprite.update(delta);
}

Another big problem with this approach is that all the other attributes of the AnimatedSprite such as position, with and height are the same, too.
When using animatedSprite#draw(SpriteBatch), it will draw them all at the same position. You'd have to store necessary information (e.g. position) in your enemy class and set the AnimatedSprite one's to it every time you draw.
Or you update the AnimatedSprite using animatedSprite.update() and draw it using the batch:
Code: Select all
batch.draw(animatedSprite, x, y, width, height);


Briefly:
  • for individually playing animations: use ONE Animation for all AnimatedSprites and create a new AnimatedSprite for each enemy (I recommend this way)
  • for simultaneously playing animations: use ONE Animation for ONE AnimatedSprite for all enemies (watch out, other attributes of the Sprite are the same as well!)

Here's an example of these approaches. Just set the resources to yours and choose an approach with the type variable.
dermetfan
 
Posts: 283
Joined: Fri May 03, 2013 8:57 pm

Re: AnimatedSprite.java

Postby xeoshow » Wed Jun 26, 2013 1:14 am

Hi dermetfan,

Understood and really good way to do animation, thanks a bunch!!

dermetfan wrote:
xeoshow wrote:Hi dermetfan,

If I have 5000 enemies with animation, I should still new AnimatedSprite with 5000 instances as below, right? Will this cause any extra overhead? Thanks!

Code: Select all
Animation animation1 = new Animation(1 / 3f, new TextureRegion(frame1), new TextureRegion(frame2), new TextureRegion(frame3));
animation1.setPlayMode(Animation.LOOP);
animatedSprite1 = new AnimatedSprite(animation1);

// draw it by calling any of the Sprite's draw methods
animatedSprite1.draw(batch);

Animation animation2 = new Animation(...);
animation2.setPlayMode(Animation.LOOP);
animatedSprite2 = new AnimatedSprite(animation2);

// draw it by calling any of the Sprite's draw methods
animatedSprite2.draw(batch);

......

animatedSprite5000.draw(batch);


If you did it like this, you would have 5000 individually playing animations, each holding their own Animation with its own TextureRegions, indeed causing alot extra overhead.

Assuming the animation should be the same for all enemies, you could create one Animation and pass it into the AnimatedSprite constructor. This will result in 5000 AnimatedSprite instances, each holding a pointer to the Animation which holds the TextureRegions. This way, you just have the Animation and TextureRegions once, causing no extra overhead.
Code: Select all
// create one Animation for all enemies
Animation animation = new Animation(1 / 3f, new TextureRegion(frame1), new TextureRegion(frame2), new TextureRegion(frame3));
animation.setPlayMode(Animation.LOOP);

// create the individually playing AnimatedSprites
animatedSprite1 = new AnimatedSprite(animation);
animatedSprite2 = new AnimatedSprite(animation);
animatedSprite3 = new AnimatedSprite(animation);


The 5000 AnimatedSprite instances each hold a pointer to the Animation, but they still hold their own time variable and three booleans. This is absolutely no problem at all, and even necessary if you want your animations to play individually.

If you want to save as much memory as possible, you could even go one step furter and create one AnimatedSprite for all enemies. Because each enemy updates the Animation when drawing by default, the Animation is played 5000 times faster, so you have to update it manually. That's a good way if it's ok to have the same animation state for every enemy.
Code: Select all
// create one AnimatedSprite for all enemies
Animation animation = new Animation(1 / 3f, new TextureRegion(frame1), new TextureRegion(frame2), new TextureRegion(frame3));
animation.setPlayMode(Animation.LOOP);
animatedSprite = new AnimatedSprite(animation);
animatedSprite.setAutoUpdate(false);

// set the one animation to all the enemies
enemy1.setSprite(animatedSprite);
enemy2.setSprite(animatedSprite);

// update the AnimatedSprite for all the enemies
@Override
public void render(float delta) {
    animatedSprite.update(delta);
}

Another big problem with this approach is that all the other attributes of the AnimatedSprite such as position, with and height are the same, too.
When using animatedSprite#draw(SpriteBatch), it will draw them all at the same position. You'd have to store necessary information (e.g. position) in your enemy class and set the AnimatedSprite one's to it every time you draw.
Or you update the AnimatedSprite using animatedSprite.update() and draw it using the batch:
Code: Select all
batch.draw(animatedSprite, x, y, width, height);


Briefly:
  • for individually playing animations: use ONE Animation for all AnimatedSprites and create a new AnimatedSprite for each enemy (I recommend this way)
  • for simultaneously playing animations: use ONE Animation for ONE AnimatedSprite for all enemies (watch out, other attributes of the Sprite are the same as well!)

Here's an example of these approaches. Just set the resources to yours and choose an approach with the type variable.
xeoshow
 
Posts: 166
Joined: Mon Mar 18, 2013 1:12 am

Re: AnimatedSprite.java

Postby xeoshow » Sun Jul 07, 2013 1:52 pm

Hi dermetfan,

Just would like to ask one more question: How can I judge if the Animation is finished in AnimatedSprite? I tried using isPlaying(), while seems not work, anything I missed? Or should I use the isAnimationFinished(stateTime) in the Animation class? If yes, what the stateTime should be? (Now I am using the Animation.NORMAL mode)

Quite thanks!
xeoshow
 
Posts: 166
Joined: Mon Mar 18, 2013 1:12 am

Re: AnimatedSprite.java

Postby dermetfan » Mon Jul 08, 2013 12:58 am

I added AnimatedSprite#isAnimationFinished(). You can just use this method now.
Get the new version here.
Code: Select all
AnimatedSprite anim = (AnimatedSprite) enemy.getSprite();
boolean finished = anim.isAnimationFinished();
// in case you're interested, you could have done it like this:
finished = anim.getAnimation().isAnimationFinished(anim.getTime());
dermetfan
 
Posts: 283
Joined: Fri May 03, 2013 8:57 pm

Re: AnimatedSprite.java

Postby xeoshow » Mon Jul 08, 2013 10:17 am

Hi dermetfan,

The new version works perfect, thanks!!
xeoshow
 
Posts: 166
Joined: Mon Mar 18, 2013 1:12 am

Re: AnimatedSprite.java

Postby xeoshow » Wed Jul 10, 2013 2:07 am

Hi dermetfan,

For below scenario, not sure if I met a bug: If there is more Enemy instances, the Animation will showing faster and faster! (animation duration will be shorter, and using Animation.LOOP)
enemy1 = new Enemy(type2AnimatedSprite);
enemy2 = new Enemy(type2AnimatedSprite);
.....
enemy300 = new Enemy(type2AnimatedSprite);

--------------------
“for simultaneously playing animations: use ONE Animation for ONE AnimatedSprite for all enemies (watch out, other attributes of the Sprite are the same as well!)”
xeoshow
 
Posts: 166
Joined: Mon Mar 18, 2013 1:12 am

PreviousNext

Return to Libgdx Contributions

Who is online

Users browsing this forum: No registered users and 1 guest