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: 113
Joined: Sat Sep 10, 2011 11:39 am

Previous

Return to Libgdx Contributions

Who is online

Users browsing this forum: kalle_h and 1 guest

x