Monday, October 20, 2008

XNA Series - Animation Part 1

Now, we may not always be using static images as our characters in our games, and sometimes we may want to have an animation, like when our character moves, his legs actually go back and forth. In 3d graphics, we would build these actions into our models, but in 2d we can pre-render these animations in frames and create a sprite sheet with all the different images of our animation.

Here is an example of such a sheet. It has 20 images of the same size, laid out in a strip.

I did this simple animation in Blender3D and then stuck it together using a little program called SpriteStripComposer that I someone put together for this very purpose. I wish Blender had a built in method for doing this, perhaps I should hit up some of my old dev pals for such a feature (or they would say "Code it Yourself!") Anyway,I am going to extend my GameObject class (see example code) with a derived class called AnimGameObject and I will add a few member variables to it
private float frames = 0;
public float frameNumber = 0;
public byte direction = 0;
The frames will hold the total number of frames in the spritesheet, the frameNumber will hold the current frame to be displayed and the direction will tell the Draw method to flip the image or not. We also add a new constructor

 public AnimGameObject(Texture2D inSprite, float x, float y, Rectangle src,float frm)
: base(inSprite, x, y, src)
{
frames = frm;
}
Which sends most of the construction to the base constructor, but also sets the frames to the number of frames passed in. Also note the when we pass in the src rectangle that we will point to the first frame of the animation and then use that data to find the rest. We also add an overridden version of the Draw method
public override void Draw(SpriteBatch sb)
{
Rectangle source = new Rectangle(((int)frameNumber)%(int)frames * texSource.Width,
0,
texSource.Width,
texSource.Height);

SpriteEffects se = SpriteEffects.None;
if (direction == 1)
{
se = SpriteEffects.FlipHorizontally;
}
sb.Draw(sprite, position, source, Color.White,0f,new Vector2(0,0),1f,se, 0);
}

First we build a new source rectangle that takes the float frame number and does a % frames on it so that we have an int between 0 and 20, we then pull the height and width from our original source rectangle. Next we create an object of type SpriteEffects and set it to None. then if direction == 1 we set it to FlipHorizontally. This will render our sprite facing the other way. Then finally we call the sb.Draw function and pass it the new source rectangle rather than the original and later in the call we pass it the SpriteEffects parameter.

Now in our game class we do things alot like we have before we declare an object of type AnimGameObject and load it up in our LoadContent method

a = new AnimGameObject(Content.Load<texture2d>("test"),0,0,new Rectangle(0,0,128,128),20);


and draw it the same in the Draw method

spriteBatch.Begin();
a.Draw(spriteBatch);
spriteBatch.End();


the main difference is in Update where we check our keyboard state and update our position, we also add to the frameNumber of the object. Now keep in mind that by default XNA runs at 60 frames per second, so you would not want to set this to update 1 frame per update, or this 20 frame animation would go all the way through 3 times per second. Instead we will set it to advance the frame 0.4f per update, that will run 24 frames per second or a little under 1 time per second (if we are holding down one of the keys) since we chop off the fractional part, when we use that number as an index, it will only make the frame change after it adds up past a whole number.
if(ks.IsKeyDown(Keys.Left)){
a.position -= 1f;
a.frameNumber += 0.4f;
}
Here is a link to the source code. SpriteAnimation.zip

Next time we will talk about frame rates and we will extend this example to make it more interesting.
Post a Comment