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 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: 375
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: 249
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: 375
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: 249
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: 249
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: 375
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: 249
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: 249
Joined: Mon Mar 18, 2013 1:12 am

Re: AnimatedSprite.java

Postby dermetfan » Wed Jul 10, 2013 2:24 pm

Maybe you forgot, but I mentioned this already.
dermetfan wrote: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);
}

It looks like you modified my example code (because of type2AnimatedSprite). Don't forget that there were these lines in it:
Code: Select all
type2AnimatedSprite.setAutoUpdate(false); // in show()
type2AnimatedSprite.update(delta); // in render()


If you know how many enemies you'll have and their number won't change, you could also set a frame duration that is the according time slower. I wouldn't usually recommend this, though.
For example, if you have and always will have 300 enemies and the frame duration should be a third of a second, you could set it to 1 / 3f * 300. This means you have to update 300 times to get past the same duration, which the 300 enemies will do for you, because each enemy updates the AnimatedSprite once individually.
Code: Select all
Animation animation = new Animation(frameDuration * numberOfEnemies, frame1, frame2, frame3);
AnimatedSprite animatedSprite = new AnimatedSprite(animation);
enemy1 = new Enemy(animatedSprite);
// ...
enemy300 = new Enemy(animatedSprite);
dermetfan
 
Posts: 375
Joined: Fri May 03, 2013 8:57 pm

Re: AnimatedSprite.java

Postby xeoshow » Wed Jul 10, 2013 2:43 pm

Hi dermetfan,

Sorry that I missed out the related info regarding bold words! Finally I decided to follow way 2, which is recommended by you. It is convenient and the memory consumption is small.

Thanks so much for your patience and really appreciate all your nice help!!
xeoshow
 
Posts: 249
Joined: Mon Mar 18, 2013 1:12 am

PreviousNext

Return to Libgdx Contributions

Who is online

Users browsing this forum: No registered users and 3 guests