Page 1 of 1

Frame Animation with XNA

#1 SixOfEleven  Icon User is offline

  • using Caffeine;
  • member icon

Reputation: 942
  • View blog
  • Posts: 6,342
  • Joined: 18-October 08

Posted 10 December 2010 - 09:10 PM

*
POPULAR

This tutorial covers animation using frames with XNA. I used XNA 3.0 for the tutorial but it will work with version of XNA. To get started, create a new XNA Windows game, call it AnimationTest.

Before getting to the code I'm going to discuss the process of animating sprites. Animation in computer games is done much like it was done when the first animated cartoons came out. It is the process of repeatedly drawing one image after another to give the illusion that the image is changing. When done at an appropriate speed the person seeing the illusion will see the image changing. If you look at the image below the blue squares show the different images, or frames. There are three images in each row. The artist created the images so that you go from the first frame to the second frame to the third and then back to the first. That is the way I will be implementing the animation of the sprite. I will have a frame rate, the number of frames that will be drawn each second. I've found that five frames per second is a reasonable rate. I will be creating a class for this type of animation.

Posted Image

You will of course want the sprite, with out the blue lines. Download the zip file below and decompress it. In version previous to XNA 4.0, right click the Content folder, select Add|Existing Item, navigate to the malefighter.png file and add it. In XNA 4.0 there is a separate content project. Right click the content project, select Add|Existing Item, navigate to the malefighter.png file and add it.

Attached File  malefighter.zip (3.33K)
Number of downloads: 354

You will now want to add in the class that controls the animation process. Right click your game project, select Add|Class. Name this class Animation. I will explain the code after you've read it.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;

namespace AnimationTest
{
    public enum AnimationKey { Down, Left, Right, Up }

    public class Animation : ICloneable
    {
        #region Field Region

        Rectangle[] frames;
        int framesPerSecond;
        TimeSpan frameLength;
        TimeSpan frameTimer;
        int currentFrame;
        int frameWidth;
        int frameHeight;

        #endregion

        #region Property Region

        public int FramesPerSecond
        {
            get { return framesPerSecond; }
            set
            {
                if (value < 1)
                    framesPerSecond = 1;
                else if (value > 60)
                    framesPerSecond = 60;
                else
                    framesPerSecond = value;
                frameLength = TimeSpan.FromSeconds(1 / (double)framesPerSecond);
            }
        }

        public Rectangle CurrentFrameRect
        {
            get { return frames[currentFrame]; }
        }

        public int CurrentFrame
        {
            get { return currentFrame; }
            set
            {
                currentFrame = (int)MathHelper.Clamp(value, 0, frames.Length - 1);
            }
        }

        public int FrameWidth
        {
            get { return frameWidth; }
        }

        public int FrameHeight
        {
            get { return frameHeight; }
        }

        #endregion

        #region Constructor Region

        public Animation(int frameCount, int frameWidth, int frameHeight, int xOffset, int yOffset)
        {
            frames = new Rectangle[frameCount];
            this.frameWidth = frameWidth;
            this.frameHeight = frameHeight;

            for (int i = 0; i < frameCount; i++)
            {
                frames[i] = new Rectangle(
                        xOffset + (frameWidth * i),
                        yOffset,
                        frameWidth,
                        frameHeight);
            }
            FramesPerSecond = 5;
            Reset();
        }

        private Animation(Animation animation)
        {
            this.frames = animation.frames;
            FramesPerSecond = 5;
        }

        #endregion

        #region Method Region

        public void Update(GameTime gameTime)
        {
            frameTimer += gameTime.ElapsedGameTime;

            if (frameTimer >= frameLength)
            {
                frameTimer = TimeSpan.Zero;
                currentFrame = (currentFrame + 1) % frames.Length;
            }
        }

        public void Reset()
        {
            currentFrame = 0;
            frameTimer = TimeSpan.Zero;
        }

        #endregion

        #region Interface Method Region

        public object Clone()
        {
            Animation animationClone = new Animation(this);

            animationClone.frameWidth = this.frameWidth;
            animationClone.frameHeight = this.frameHeight;
            animationClone.Reset();

            return animationClone;
        }

        #endregion
    }
}


I've broken the code into regions using the #region and #endregion preprocessor directives. It is just for organizational purposes, to group similar code together.

There is a using statement for the XNA Framework to bring the base XNA Framework name space into scope because this class uses the Rectangle class. There is an enumeration called AnimationKey. It describes the different types of animations the sprite has. It can be down, up, left and right. You could have many more animations, like moving on the diagonals, jumping, or ducking. This class implements the ICloneable interface. The reason is you could have multiple sprites in the game that have the same lay out. It is better to store the animations in a master list of animations and return copies of them. This is a class so if you pass just the instance and you make changes it will affect the original.

There are quite a few fields in this class. The first one, frames, is an array of rectangles for the source rectangles of the animation. If you look back at the image all of the animations for one direction are in the same row. The reason will become clear when I get to the constructor. The next field is an integer and holds the number of frames to animate per second. The next two fields are of type TimeSpan. They hold the length of each frame of the animation and the time since the last animation started. Using the TimeSpan structure gives you a finer degree of control over the animations than using a double or floating point value. The next field, currentFrame, is the index of the current animation in the array of rectangles. The last two fields, frameWidth and frameHeight, hold the height and width of the frames. The one downfall of this method is that all of the frames must have the same width and height. There is another method that you could use to animate the sprites but I believe this is the best approach.

There are several properties in this class to expose the fields they represent. The first one is FramesPerSecond and it is used to get and set the number of frames the sprite will animate in one second. The get part is trivial, it just returns the value in the framesPerSecond field. The set part is a little more interesting. It does a little validation on the values passed in. The first check is to make sure that the value is not less than one. If it was you would get some pretty bizarre results. It also makes sure that the frame rate is not greater than sixty. This value is actually pretty high. Checking for a value of ten would probably be a better idea. After validating the values it sets the frameLength field. What is important here is you want to make sure you cast the framesPerSecond field as a double when you do the division. If you don't then integer division will be preformed and you will get either one or zero, depending on if framesPerSecond was one or not. You will often need to know what the current rectangle of the animation is so there is a property CurrentFrameRect to retrieve that. You will not only want to be able to get what the current frame is but also set it. You will again have to make sure that the value passed in is valid. I did that using the MathHelper.Clamp method which makes sure that the value is with in the minimum and maximum values, inclusive, passed in. There are also properties that get the width and height of the frames, FrameWidth and FrameHeight respectively.

There are two constructors for this class, a public one and a private one. The public takes as parameters the number of frames for the animation, the width of each frame, the height of each frame, the X offset of the frame and the Y offset of the frame. The last two's purpose probably isn't obvious. If you go back to the image of the sprite sheet above you will see that it is split up into rows and columns. The first row of animations begins at coordinates (0, 0). The second row of animations begins at coordinates (0, 32). The third and forth at (0, 64) and (0, 96) respectively. As you can see as you move down in rows the Y value changes. This is the Y offset value. You could also have had the animations in two rows and two columns. In this case you would also have had an X offset. Later I will get to having more than just the walking animation and the X offset will be useful.

The constructor then sets the frames, frameWidth, and frameHeight first. Then, in a for loop, it creates each of the rectangles to describe the frames for the animation. This is where the X and Y offsets become important. When you create the first animation you will be passing in the values 3, 32, 32, 0, and 0. For the second you will be passing in 3, 32, 32, 0, and 32. In the for loop to find the X coordinate in the sprite sheet for the rectangle you take the X offset and add the width of the frame times the frame counter, which is i the loop index. In this case since all of the animations or on the same row you can just use the Y offset value. The Width and Height values are set using the frameWidth and frameHeight values passed in. I then use the FramesPerSecond field to set the number of frames for the sprite to be 5. I then call the Reset method of the class which sets the animation in its starting position.

The second private constructor is used when I implement the ICloneable interface. This constructor takes an Animation object as its parameter. This constructor sets the frames field of the new animation to the frames field of the old animation. It then sets the FramesPerSecond to 5 like the other constructor.

There are two methods, Update and Reset, that are for controlling the animation of the sprite. The third method, Clone, implements the ICloneable interface. The Update method takes a GameTime parameter. This parameter measures how much time has elapsed since the last call to Update in the Game1 class. This is used to determine when it is time to move to the next frame of the animation. What I do is add the ElapsedGameTime property of the GameTime parameter to frameTimer. This is used to determine when to move to the next frame. It is time to move to the next frame when frameTimer is greater than or equal to frameLength so there is an if statement that checks for that. If it is you want to reset frameTimer to the zero position and move to the next frame. There is an nice mathematical formula next to calculate which frame to move to. In our case we have three frames that are at index 0, 1, and 2. If you add 1 to that you will have the values 1, 2, and 3. You then get the remainder of dividing those by the number of frames. Those values will be 1, 2, and 0. So if you are on frame 0 you will move to frame 1, from frame 1 to frame 2, and from frame 2 back to frame 0. The Reset method is very simple. It sets the current frame to be 0 and then sets the time back to 0 as well.

The last method in this class is the Clone method. This method is from the ICloneable interface. This method returns a copy of the current animation. The first step is to create a new Animation object using the private constructor, passing in the current Animation object. The next step is to set the frameWidth and frameHeight fields. I call the Reset method to reset the Animation to the first frame. I then return the new Animation but the Clone method returns an object. When you use the Clone method of the Animation class you will have to cast the return value to be of type Animation.

The next class I'm going to add is a class for an animated sprite. Right click your game project, select Add|Class. Name this new class AnimatedSprite. This is the code for that class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace AnimationTest
{
    public class AnimatedSprite
    {
        #region Field Region

        Dictionary<AnimationKey, Animation> animations;
        AnimationKey currentAnimation;
        bool isAnimating;

        Texture2D texture;
        Vector2 position;
        Vector2 velocity;
        float speed = 4.0f;

        #endregion

        #region Property Region

        public AnimationKey CurrentAnimation
        {
            get { return currentAnimation; }
            set { currentAnimation = value; }
        }

        public bool IsAnimating
        {
            get { return isAnimating; }
            set { isAnimating = value; }
        }

        public int Width
        {
            get { return animations[currentAnimation].FrameWidth; }
        }

        public int Height
        {
            get { return animations[currentAnimation].FrameHeight; }
        }

        public float Speed
        {
            get { return speed; }
            set { speed = MathHelper.Clamp(speed, 1.0f, 16.0f); }
        }

        public Vector2 Position
        {
            get { return position; }
            set
            {
                position = value;
            }
        }

        public Vector2 Velocity
        {
            get { return velocity; }
            set
            {
                velocity = value;
                if (velocity != Vector2.Zero)
                    velocity.Normalize();
            }
        }

        #endregion

        #region Constructor Region

        public AnimatedSprite(Texture2D sprite, Dictionary<AnimationKey, Animation> animation)
        {
            texture = sprite;
            animations = new Dictionary<AnimationKey, Animation>();

            foreach (AnimationKey key in animation.Keys)
                animations.Add(key, (Animation)animation[key].Clone());

        }

        #endregion

        #region Method Region

        public void Update(GameTime gameTime)
        {
            if (isAnimating)
                animations[currentAnimation].Update(gameTime);
        }

        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(
                texture,
                position,
                animations[currentAnimation].CurrentFrameRect,
                Color.White);
        }

        #endregion
    }
}


There are using statements to bring a few classes of the XNA Framework into scope. There are a number of fields in this class. The first is a Dictionary<AnimationKey, Animation> to hold the animations of the sprite. I used a dictionary because you can only have one entry per key and it is easier than having to remember which animation was added when if you were to use a List<T>. The currentAnimation field is for the current animation of the sprite. The bool field isAnimating is used to tell if the sprite is currently animating so the animation should be updated in the Update method. The texture field is for the sprite sheet. The next two field are Vector2 fields and are for the position and velocity of the sprite. I will be controlling the speed the sprite moves across the screen using a motion vector. I will normalize the vector and then multiply it by a constant value, the speed field that holds the speed the sprite moves.

There are properties to expose information about the sprite. CurrentAnimation is a get and set property for the currentAnimation field. The IsAnimating property is also a get and set property and is for the isAnimating field. You will often need to know the height and width of the sprite. The Height and Width properties return the height and width of the current frame of the animation. The Speed property is also get and set and exposes the speed field. The set part clamps the speed between 1 and 16 using MathHelper.Clamp. The Position property exposes the position field and is a read/write property, get/set. The Velocity property exposed the velocity field. It is get/set as well. However, the set part checks to see if the value passed in is Vector2.Zero because it normalizes the vector and you can't normalize a vector that has zero length.

The constructor takes two parameters. A Texture2D for the sprite and a Dictionary<AnimationKey, Animation> for the animations. You don't have to pass a clone of the animations to the constructor as it will clone the animations passed in. The constructor sets the texture field to the texture passed in and then creates a new Dictionary<AnimationKey, Animation>. Then in a for each look it loops through all of the keys in the key collection of the dictionary passed in. Inside the loop it adds the key with a clone of the animation to the dictionary in the class.

The Update takes the GameTime parameter to be able to update the animation. It checks to see if the sprite is currently animating. If it is it calls the Update method of the current animation.

The Draw method takes three parameters. The GameTime parameter of our game's Draw method, a SpriteBatch between calls to Begin and End. To draw the sprite I use the overload that takes the texture for the sprite, a Vector2 for its position, a source rectangle, and the tint color.

With all of these classes, we can now add in an animated sprite. You will need a couple fields in your Game1 class. You will want to check for keyboard and game pad input so you will need fields to hold the keyboard state and the game pad state. Game pad input is optional but I'm including it for those who may be interested. You will also want an AnimatedSprite field. Add these fields to the Game1 class with the SpriteBatch field.

KeyboardState keyboardState;
GamePadState gamePadState;
AnimatedSprite sprite;



Next you will want to load in the image for the sprite, create the animations for the sprite and create the animated sprite. That is best done in the LoadContent method. You can change that method to the following.

protected override void LoadContent()
{
    spriteBatch = new SpriteBatch(GraphicsDevice);

    Dictionary<AnimationKey, Animation> animations = 
        new Dictionary<AnimationKey, Animation>();

    Animation animation = new Animation(3, 32, 32, 0, 0);
    animations.Add(AnimationKey.Down, animation);

    animation = new Animation(3, 32, 32, 0, 32);
    animations.Add(AnimationKey.Left, animation);

    animation = new Animation(3, 32, 32, 0, 64);
    animations.Add(AnimationKey.Right, animation);

    animation = new Animation(3, 32, 32, 0, 96);
    animations.Add(AnimationKey.Up, animation);

    sprite = new AnimatedSprite(
        Content.Load<Texture2D>("malefighter"),
        animations);
}



So, what the code does is create a new Dictionary<AnimationKey, Animation> to hold the animations for the sprite. You could have easily had this as a field if you had multiple sprites but this servers the purpose of the tutorial. The next step is to create each of the individual animations and add them to the dictionary. The animations in the sheet are the down, left, right and then up animation. There are three frames per animation and they are 32 pixels by 32 pixels. The offset for X is always 0 and the offset for Y starts at 0 then increases by 32 for each animation. After creating the dictionary I create the sprite passing in the texture and the dictionary.

Now you will want the sprite to move and animate according to the player, or possibly computer controlled objects but for this tutorial the player controls the sprite. You will want to get the player's input in the Update method and based on their input move the sprite. Change the Update method to the following.

protected override void Update(GameTime gameTime)
{
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit();

    Vector2 motion = new Vector2();

    keyboardState = Keyboard.GetState();
    gamePadState = GamePad.GetState(PlayerIndex.One);

    if (keyboardState.IsKeyDown(Keys.Down) ||
        gamePadState.IsButtonDown(Buttons.LeftThumbstickDown))
    {
        sprite.IsAnimating = true;
        sprite.CurrentAnimation = AnimationKey.Down;
        motion.Y = 1;
    }
    else if (keyboardState.IsKeyDown(Keys.Up) ||
        gamePadState.IsButtonDown(Buttons.LeftThumbstickUp))
    {
        sprite.IsAnimating = true;
        sprite.CurrentAnimation = AnimationKey.Up;
        motion.Y = -1;
    }

    if (keyboardState.IsKeyDown(Keys.Left) ||
        gamePadState.IsButtonDown(Buttons.LeftThumbstickLeft))
    {
        sprite.IsAnimating = true;
        sprite.CurrentAnimation = AnimationKey.Left;
        motion.X = -1;
    }
    else if (keyboardState.IsKeyDown(Keys.Right) ||
        gamePadState.IsButtonDown(Buttons.LeftThumbstickRight))
    {
        sprite.IsAnimating = true;
        sprite.CurrentAnimation = AnimationKey.Right;
        motion.X = 1;
    }

    if (motion != Vector2.Zero)
    {
        sprite.IsAnimating = true;
        motion.Normalize();
        sprite.Position += motion * sprite.Speed;
    }
    else
    {
        sprite.IsAnimating = false;
    }

    sprite.Update(gameTime);

    base.Update(gameTime);
}



I first create a Vector2 called motion to hold the players input. The way I handled input certain directions take precedence over others. I find the sprite looks better if you use the horizontal animations for diagonals rather than the vertical ones. Also, the down animation takes precedence over the up and the left over the right. If I was just using the game pad I wouldn't have this problem as I can read the left thumb stick and just process that. I'm using keyboard input as not everybody has a game pad that works with Windows.

The next thing I do is get the current state of the keyboard and the game pad for player one. I then check if the down arrow key is being pressed or the left thumb stick is being pushed down. If that is true I set that the sprite is animating, its animation to down, and the Y component of motion to 1 as Y increases as you move down the screen. In the else I check to see if the up arrow key is being pressed or the left thumb stick is being pushed up. If that is true I set that the sprite is animating, its animation to up, and the Y component of motion to -1. By using an or for the comparison if either, or both are true, the animation will take place. In C# if the first is true, the second is ignored as it doesn't need to be evaluated. So, if the player is pressing the down key and the left thumb stick down the sprite only moves one unit per frame instead of two.

I then check if the left arrow key is being pressed or the left thumb stick is being pushed down. If that is true I set that the sprite is animating, its animation to left, and the X component of motion to -1 as X increases as you move right across the screen. In the else I check to see if the right arrow key is being pressed or the left thumb stick is being pushed right. If that is true I set that the sprite is animating, its animation to right, and the X component of motion to 1.

After gathering all of the input I check to see if the motion vector is not the zero vector. Again, I'm normalizing the vector and you can't normalize a vector that has zero length. After normalizing the vector I update the sprite's position by multiplying the motion vector by the speed of the sprite. If the motion vector was the zero vector then there was no movement so I set the sprite to no longer animate. The last thing I do is call the Update method of the sprite so it will update the animation.

That just leaves drawing the sprite. All I did was in between calls to Begin and End of the sprite batch is call the Draw method of the sprite passing in the game time and the sprite batch. This is the Draw method.

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    spriteBatch.Begin();

    sprite.Draw(gameTime, spriteBatch);

    spriteBatch.End();

    base.Draw(gameTime);
}



Well, that was a rather long tutorial on animating a sprite. It is a powerful technique and can easily be added to your games. Something you can do is have a class PlayerSprite that inherits from AnimatedSprite and handle all of your input inside of that class to keep input related to the sprite with the sprite.

Is This A Good Question/Topic? 6
  • +

Replies To: Frame Animation with XNA

#2 Kilorn  Icon User is offline

  • XNArchitect
  • member icon



Reputation: 1355
  • View blog
  • Posts: 3,528
  • Joined: 03-May 10

Posted 13 December 2010 - 08:47 AM

Nice tutorial, SixOfEleven. It's good to see another way of handling sprite animations. I think there are three sprite animation tutorials up here now, so anyone who needs help with it should be able to find a method that fits their style.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1