Simple Texture-based Animation
From XNAWiki
This component allows for frame-based animation using portions of a single Texture2D. The texture is broken down into a sequence of frames based on the single frame width and height passed into the constructor. For example, an image that is 128x128 pixels and has provided a frame width=64 and a frame height=32 would contain 4 rows of 2 animation cells each. Some useful properties:
- Position indicates the screen coordinates of the top-left corner of the animation.
- FrameDelay controls the speed of animation, in milliseconds (i.e. FrameDelay=1000 means one second between each frame). this must be set prior to the loading of content.
- Loop controls whether or not the animation repeats after it completes.
The component could be improved to handle variable delays between frames (supported but not implemented), reuse of SpriteBatches, and more robust error handling. Also, the call to SpriteBatch.Begin with SaveStateMode.SaveState should also be changed for a real game, it is included to ensure the component works for most people out of the box.
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
/// <summary>
/// Component that handles 2D animation based on multiple frames
/// held within a single texture.
/// </summary>
public class AnimationComponent : DrawableGameComponent
{
/// <summary>
/// Screen position of the animation top-right corner.
/// </summary>
public Vector2 Position;
/// <summary>
/// Whether or not the animation will repeat when completed.
/// </summary>
public bool Loop;
/// <summary>
/// Default delay between animation frames (in milliseconds).
/// </summary>
public int FrameDelay;
/// <summary>
/// Renders the current animation frame.
/// </summary>
SpriteBatch spriteBatch;
/// <summary>
/// Texture cintaining the animation frames.
/// </summary>
Texture2D texture;
/// <summary>
/// Path to the texture to animate.
/// </summary>
string texturePath;
/// <summary>
/// Width of a single frame.
/// </summary>
int width;
/// <summary>
/// Height of a single frame.
/// </summary>
int height;
/// <summary>
/// Table of the delay between each animation frame
/// (in milliseconds).
/// </summary>
int[][] frameInfo;
/// <summary>
/// Current frame column index.
/// </summary>
int frameX;
/// <summary>
/// Current frame row index.
/// </summary>
int frameY;
/// <summary>
/// Last game time when the animation frame switched.
/// </summary>
TimeSpan lastGameTime;
/// <summary>
/// Whether or not the animation is running.
/// </summary>
bool paused;
/// <summary>
/// Create a new <see cref="AnimationComponent" /> based on the given texture.
/// </summary>
/// <param name="game">Containing game.</param>
/// <param name="textureName">Name of the texture to animate.</param>
/// <param name="frameHeight">Height of a single animation frame.</param>
/// <param name="frameWidth">Width of a single animation frame.</param>
public AnimationComponent(Game game, string textureName, int frameWidth,
int frameHeight) : base(game)
{
paused = true;
FrameDelay = 66;
// save the texture information
width = frameWidth;
height = frameHeight;
texturePath = textureName;
}
/// <summary>
/// Play the animation.
/// </summary>
/// <remarks>Resets the animation to the first frame if it was
/// not already playing.</remarks>
public void Play()
{
if(paused)
{
frameX = frameY = 0;
}
paused = false;
}
/// <summary>
/// Pause the animation at the current frame.
/// </summary>
public void Pause()
{
paused = true;
}
/// <summary>
/// Stop the animation.
/// </summary>
public void Stop()
{
paused = true;
}
/// <summary>
/// Load the animation content.
/// </summary>
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
// load the animation texture sheet
texture = Game.Content.Load<Texture2D>(texturePath);
// determine the number of rows and columns in the animation
// based on the dimensions of a single frame
int rows = texture.Height / height;
int columns = texture.Width / width;
// populate the animation frame time (in milliseconds) for each
// frame of the rectangular texture. These are initially all the
// same but using arrays supports the ability to tweak the delay
// between individual frames
frameInfo = new int[rows][];
for(int y = 0; y < rows; y++)
{
frameInfo[y] = new int[columns];
for (int x = 0; x < columns; x++)
{
frameInfo[y][x] = FrameDelay;
}
}
}
/// <summary>
/// Handle updating the animation.
/// </summary>
/// <param name="gameTime">Current game time.</param>
public override void Update(GameTime gameTime)
{
if(!paused)
{
int delay = frameInfo[frameY][frameX];
if((gameTime.TotalGameTime - lastGameTime).TotalMilliseconds > delay)
{
// if at the end of a row the animation must move to the next one,
// otherwise it will have moved to the next column
if(++frameX >= frameInfo[frameY].Length)
{
// at the end of the last row in a non-looping animation
if (frameY + 1 == frameInfo.Length && !Loop)
{
paused = true;
frameX--;
}
// the animation loops or there are more rows available.
// Go to the first column in the next row
else
{
frameY = ++frameY % frameInfo.Length;
frameX = 0;
}
}
// record the last time that the current frame switched
lastGameTime = gameTime.TotalGameTime;
}
}
}
/// <summary>
/// Render the current animation frame.
/// </summary>
/// <param name="gameTime">Current game time.</param>
public override void Draw(GameTime gameTime)
{
spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred,
SaveStateMode.SaveState);
spriteBatch.Draw(texture, Position,
new Rectangle(frameX*width, frameY*height, width, height),
Color.White);
spriteBatch.End();
}
}