Creating Animations in XNA

For this lecture, we will discuss creating animations in XML. The first step is to create the actual animation frames themselves. We will do this using gimp (though, of course, you can use any graphics program that you like). We will then show how to create a class to handle the animation, and the put it all together. Finally, we will modify our animation to be a little more flexible, so that we could have multiple

Creating the Animation Frames

For this example, we will create a 50x50 pixel spinning coin. First, create a new 50x50 image in gimp. This time we will want to fill with the the background color (instead of a transparency)

Create new gimp image

Next, we will zoom in a bit (400% is good), and select a circle that fills the entire space

Create Circle

Fill this area with a nice dark brown color, by first changing the color (clicking on the forground panel, then selecting the fill tool, and then clicking anywhere within the circle:

Fill Circle

You might want to save this brown color for later by dragging from the "current" area to one of the presets to the right (note that in this example, I've saved this color as the top-left color preset). We can then select a smaller circle inscribed in the brown circle (the ruler guides to the top and left help us line things up well), and color it a nice yellow:

FillCenter

If we were feeling particularly artistic, we could draw in some more detail, but this is good enough for our demo. This will be the first frame of our spinning coin. Now for the next frames. Create a new 50x50 image filled with the same background color. Then copy the original image (click anywhere in the image to bring it to the foreground, hit ctrl-c), and paste it into the new image (click anywhere inside it, and hit ctrl-v)

Duplicate Coin

Next, select the perspective tool, move all 4 corners of the new image in 4 pixels (two marks on the ruler) and hit transform

Using Perspective tool

 

Once the image is nicely transformed, repeat the process. this time shrinking the image by 8 pixels (4 ruler marks) in either direction. Repeat until you have a nice sequence. I did a special-case for the last frame -- just a rectangle filled in with the same brown color, to make the coin look a little more like a 3D object with a brown rim and a yellow face.

AllFramesCreated

Now that we have all of the individual frames, we want to make a SpriteSheet -- a single images that contains all of the frames. Since we want the animation to go from the full circle, to the edge, back the the full circle, we will need a total of 12 frames, for an image of size 50*12 x 50. This time, we do want the background to be transparent:

Final Sheet creation

With our final sheet in place, we can just cut and paste our images in. Since our images have backrounds, and the target is transparent, it is easy to get them lined up nicely. Be sure to copy the entire image (whole 50x50 square) before pasting:

Lineing up sprites on spritesheet

When we have the entire sheet ready, we will want to remove the background, so that our spinning coin will work anywhere (not just on a white background). From the colors menu, select Color to Alpha, and make the color white into alpha:

Color To alpha

Now we have our animation frames! Save them as a .png file (preserves alpha values), and we are ready for putting the animation in!

Animation Class

We are now ready to put the animation in. We will make a lightweight animation class to handle actually animating our image. We do not want this to be a GameComponent -- that's way too much overhead for a simple animation. Instead, we will create a plain C# class for an animated object. This object will contain a draw and update method. Since we don't want the overhead of each animated object owning its own spritebatch, the draw method will take as input a spritebatch parameter:

    class AnimatedObject
    {
        virtual public void Update(GameTime gameTime)
        {
        }

        virtual public void Draw(SpriteBatch s)
        {
        }
    }
 
What do we need to represent an animated object on the screen? In addtion, we will need some run-time information:

We will also store the number of frames of the animation (we can get that from a simple division, but we will go ahead and store that information so that we don't need to keep recalculating it.)

Our constructor needs the Texture2D and the width of each frame. Everything else can be default values. (We could also provide additional constructors that set various of these values, as a convenience). This gives us a constructor and properties:

    class AnimatedObject
    {    
        public int Width { get; private set; }
        public bool Looping { get; set; }
        public Vector2 Position { get; set; }
        public float TimePerFrame { get; set; }
        private Texture2D mTexture;
        private int mCurrentFrame;
        private float mCurrentFrameTime;
        private int mNumFrames;

        public AnimatedObject(int imageWidth, Texture2D texture, bool looping)
        {
            Width = imageWidth;
            mTexture = texture;
            Looping = looping;
            mCurrentFrame = 0;
            TimePerFrame = 0.1f;
            mCurrentFrameTime = 0;
            mNumFrames = texture.Width / imageWidth;
            Position = new Vector2(0, 0);
        }
        ...
    }

A note on code convetions -- for this document, I am using:

You do not need to follow this naming convention in your own code (and I admit that sometimes using m<name>, and sometimes using Properies, can be a bit confusing), but you should have your own code standards that you follow!

Note that the Width property has a public getter, but a private setter -- once we set the width of each frame of the animation, we don't really want to change it. Our update function just needs to add the elapsed game time to the time so far for this frame, incrementing the frame (and looping) as necessary:

        virtual public void Update(GameTime gameTime)
{
mCurrentFrameTime += (float) gameTime.ElapsedGameTime.TotalSeconds;
if (mCurrentFrameTime >= TimePerFrame)
{
mCurrentFrameTime = 0;
if (mCurrentFrame == mNumFrames - 1)
{
if (Looping)
{
mCurrentFrame = 0;
}
}
else
{
mCurrentFrame++;
}
}
}
Note that we want this function to be virtual, just in case we want to subclass our basic animation with slighly different update logic. Our Draw method just needs to draw the correct region of the spritesheet image on the correct location of the screen:
        Rectangle FrameToRectangle(int frameNum)
        {
            return new Rectangle(frameNum * Width, 0, Width, Texture.Height);
        }
        
        virtual public void Draw(SpriteBatch s)
        {
            s.Draw(Texture, Position, FrameToRectangle(mCurrentFrame), Color.White);
        } 
 

Putting it All Together

To get our animation to show up, we will need to:

  1. Add the animation texture to the Content directory (simple drag and drop)
  2. Load the image into a Texture2D
  3. Create at least one AnimatedObject using this texture
  4. Call the update and draw method on the animated object every frame
If we were doing this for real, steps 2-4 would be done inside some other component (player component, AI component, world component, dynamic object component -- many ways to break the problem down), but for example purposes only we will put this logic in the main loop. Note that your projects should be better decomposed!
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            // Load our coin texture
            Texture2D animTexture = Content.Load<Texture2D>("coinFlip");
            // Create a new coin object using the coin texture
            coin = new AnimatedObject(50, animTexture, true);
            // Set the coin's position
            coin.Position = new Vector2(50, 50);
        }


        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // Once again, low-level stuff like updating individual objects should
            //  not be done in the main loop for a real game!

            coin.Update(gameTime);            
            base.Update(gameTime);
        }

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

            spriteBatch.Begin();
            coin.Draw(spriteBatch);
            spriteBatch.End();
             
            base.Draw(gameTime);
        }   

Now we can run our example game, and see a nice rotating coin. You can do something similar to animate any other object. If you want to have some game object that runs an animation, you can subclass this animation class, and override the update method:

        override public void Update(GameTime gameTime)
        {
            // Game logic for this object (change position, etc)
            base.Update(gameTime); // Update animation
        }

Extending to an Animation System

While the approach above will work, it is a little difficult to extend. What if we wanted to have an object that had multiple animations (run left, run right, idle, jump, etc)? Instead of having our object subclass an animation, we could instead have our object contain an animation -- that way we could swap out animations easily. Our animation would no longer own the position of the object (that will need to be passed in on a draw), only the information needed to draw the animation:

    class Animation
    {
        public int Width { get; private set; }
        public bool Looping { get; set; }
        public float TimePerFrame { get; set; }
        private Texture2D Texture;
        private int mCurrentFrame;
        private float mCurrentFrameTime;
        private int mNumFrames;

        public Animation(int imageWidth, Texture2D texture, bool looping)
        {
            Width = imageWidth;
            Texture = texture;
            Looping = looping;
            mCurrentFrame = 0;
            TimePerFrame = 0.1f;
            mCurrentFrameTime = 0;
            mNumFrames = texture.Width / imageWidth;
        }
        Rectangle frameToRectangle(int frameNum)
        {
            return new Rectangle(frameNum * Width, 0, Width, Texture.Height);
        }

        public void Reset()
        {
            mCurrentFrame = 0;
        }

        virtual public void Update(GameTime gameTime)
        {
            mCurrentFrameTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
            if (mCurrentFrameTime >= TimePerFrame)
            {
                mCurrentFrameTime = 0;
                if (mCurrentFrame == mNumFrames - 1)
                {
                    if (Looping)
                    {
                        mCurrentFrame = 0;
                    }
                }
                else
                {
                    mCurrentFrame++;
                }
            }
        }

        virtual public void Draw(SpriteBatch s, Vector2 postion)
        {
            s.Draw(Texture, postion, frameToRectangle(mCurrentFrame), Color.White);
        }

    }
    

If we wanted to animate an object, it would contain an animation -- and each call to update would call the update method on the contained animation. We could easily extend to have multiple animations per object, storing them in a dictionary:

    class AnimatedObject2
    {
        public Vector2 Position { get; set; }
        private Dictionary mAnimations;
        private string mCurrentAnimation;

        public AnimatedObject2()
        {
            mAnimations = new Dictionary();
        }

        public void AddAnimation(string name, Animation animation)
        {
            mAnimations.Add(name, animation);
            if (mCurrentAnimation == null)
            {
                mCurrentAnimation = name;
            }
        }
        public void SetAnimation(string name)
        {
            if (!mAnimations.ContainsKey(name))
            {
                throw new InvalidOperationException();
            }
            mCurrentAnimation = name;
            mAnimations[name].Reset();
        }

        virtual public void Update(GameTime gameTime)
        {
            // Do any updating that we want here ...
            mAnimations[mCurrentAnimation].Update(gameTime);
        }

        virtual public void Draw(SpriteBatch s)
        {
            mAnimations[mCurrentAnimation].Draw(s, Position);
        }
    }   

Once we create one of these animated objects, we will need to set one or more animations:

            Texture2D flipTexture = Content.Load("coinFlip");
            Texture2D shimmerTexture = Content.Load("coinShimmer");

            coin2 = new AnimatedObject2();
            coin2.AddAnimation("flip", new Animation(50, flipTexture, true));
            coin2.AddAnimation("shimmer", new Animation(50, shimmerTexture, true));   

Further Work

There are lots of things we could do to modify this: