Sgt. Conker We are "absolutely fine"

15Nov/091

Article : Simple 2D Animated Sprite

In this final part of this article, we are going to take out the animation code and create a nice animated sprite class. First off, let's add a new class file to our project called AnimatedSprite.

AnimateThis New Class

First off inside this newly created class file, let's add 2 using statements to the top.

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

Next let's copy over the private fields that pertained to the animated sprite from Game1.cs into AnimatedSprite.cs.

		private Texture2D scribbles;
	    private List rectagles;
		private Vector2 scribbleOrigin;
		private Vector2 scribblePos;
		private int frameCount;
		private float timePerFrame;
		private float totalElapsed;
		private int currentFrame;
		private int framesPerSec;

Now we should make a loading function to load up the sprite. This should take care of setting up the texture, and all of our rectangles and vectors.

		public void LoadSprite(Texture2D _texture2D, int _posX, int _posY, int _numFrames, int _frameWidth, int _frameHeight, int _framesPerSec)
		{
			scribbles = _texture2D;
            rectagles = new List();

            rectagles.Add(new Rectangle(0, 0, 128, 128));
            rectagles.Add(new Rectangle(128, 0, 128, 128));
            rectagles.Add(new Rectangle(256, 0, 128, 128));
            rectagles.Add(new Rectangle(384, 0, 128, 128));
            rectagles.Add(new Rectangle(0, 128, 128, 128));
            rectagles.Add(new Rectangle(128, 128, 128, 128));
            rectagles.Add(new Rectangle(256, 128, 128, 128));
            rectagles.Add(new Rectangle(384, 128, 128, 128));
            rectagles.Add(new Rectangle(0, 256, 128, 128));
            rectagles.Add(new Rectangle(128, 256, 128, 128));

            frameCount = _numFrames;
			framesPerSec = _framesPerSec;
			timePerFrame = (float) 1/framesPerSec;
			currentFrame = 0;
			totalElapsed = 0;
			scribbleOrigin = new Vector2(_frameWidth / 2, _frameHeight / 2);
			scribblePos = new Vector2(_posX, _posY);
		}

This takes care of loading the sprite for now. Later we will come back to this to implement a better way to get the rectangle of the sprite.

The next part for this Animated Sprite class is an update function. This will update what frame the sprite is on. This is almost a complete copy/paste from the Game1.cs update function.

		public void Update(GameTime _gameTime)
		{
			float elapsed = (float)_gameTime.ElapsedGameTime.TotalSeconds;
			totalElapsed += elapsed;

			if (totalElapsed > timePerFrame)
			{
				currentFrame++;
				currentFrame = currentFrame % frameCount;
				totalElapsed -= timePerFrame;
			}
		}

Last thing is we need to draw the sprite. This again, is just a copy from what we have in the Game1.cs draw function.

		public void Draw(SpriteBatch _spriteBatch)
		{
			_spriteBatch.Draw(spriteTexture, spritePos, rectagles[currentFrame], Color.White, 0,
				spriteOrigin, 1.0f, SpriteEffects.None, 0.6f);
		}

Simple enough? Yes it was. Now lets go into our Game1.cs file and remove all the items regarding the animated sprite and replace them with the new AnimatedSprite Class. Instead of all those private fields lets add a field for our animated sprite.

private AnimatedSprite sprite;

In the Game1 constructor, let's initialize our sprite object like so.

		public Game1()
		{
			graphics = new GraphicsDeviceManager(this);
			Content.RootDirectory = "Content";
			sprite = new AnimatedSprite();
		}

In LoadContent() we should just now have the animated sprite call the LoadSprite function, like so.

		protected override void LoadContent()
		{
			// Create a new SpriteBatch, which can be used to draw textures.
			spriteBatch = new SpriteBatch(GraphicsDevice);

			sprite.LoadSprite(Content.Load("scribbles"),
				(graphics.PreferredBackBufferWidth / 2), (graphics.PreferredBackBufferHeight / 2),
				10, 128, 128, 15);
		}

In the Update function, we call the sprite's update function like so.

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

			sprite.Update(gameTime);

			base.Update(gameTime);
		}

Lastly, we need to change the Draw function to call the sprite's own draw function, like so.

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

			spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred,
							  SaveStateMode.SaveState);

			sprite.Draw(spriteBatch);

			spriteBatch.End();

			base.Draw(gameTime);
		}

Compile and run it, and you should see the lovely scribble in the middle of the screen. Now there is a few more things we can do to tidy up the AnimatedSprite class, for one to allow new size of sprites instead of our hard coded 128x128.

Back in AnimatedSprite.cs, we want to add a few more fields, and change some field names.

		private Texture2D spriteTexture; // was scribbles
	    //private List rectagles; we don't need this anymore
		private Vector2 spriteOrigin // was scribbleOrigin;
		private Vector2 spritePos // was scribblePos;
		private int frameCount;
		private float timePerFrame;
		private float totalElapsed;
		private int currentFrame;
		private int framesPerSec;
		private int framesPerRow;
		private int frameWidth;
		private int frameHeight;

Now let's change the LoadSprite function to take in 2 more parameters for horizontal spacing and verticle spacing. Your new LoadSprite should look like the following.

		public void LoadSprite(Texture2D _texture2D, int _posX,
			int _posY, int _numFrames, int _frameWidth, int _frameHeight, int _horizontalSpace,
			int _verticalSpace, int _framesPerSec)
		{
			spriteTexture = _texture2D;
			frameWidth = _frameWidth;
			frameHeight = _frameHeight;
			framesPerRow = spriteTexture.Width/(frameWidth + _horizontalSpace);

			frameCount = _numFrames;
			framesPerSec = _framesPerSec;
			timePerFrame = (float)1 / framesPerSec;
			currentFrame = 0;
			totalElapsed = 0;
			spriteOrigin = new Vector2(frameWidth / 2, frameHeight / 2);
			spritePos = new Vector2(_posX, _posY);
		}

Since we removed the Rectangles to hold the positions of the sprites, we need to add a new feature to get the rectangle. To do this, let's add a new private function to grab the rectangle of the current frame.

		private Rectangle GetFrameRectangle(int _frameNumber)
		{
			return new Rectangle(
			  (_frameNumber % framesPerRow) * frameWidth,
			  (_frameNumber / framesPerRow) * frameHeight,
			  frameWidth,
			  frameHeight
			);

		}

And now the last thing to do, is change our sprite's draw function to use the helper function to grab the current frame's rectangle.

		public void Draw(SpriteBatch _spriteBatch)
		{
			_spriteBatch.Draw(spriteTexture, spritePos, GetFrameRectangle(currentFrame), Color.White, 0,
				spriteOrigin, 1.0f, SpriteEffects.None, 0.6f);
		}

Back to Game1.cs, update the call to LoadSprite to pass in 0 for vertical and horizontal spacing in the LoadContent function. Compile and run, and enjoy the new animated sprite class.

Click here to download the completed project.

About ElementCy

Private Slaq0r
Comments (1) Trackbacks (1)
  1. Very nice. With a few modifications I was able to use your class to animate character movement as well as regular animation. Before this I was having a lot of trouble understanding animation in C# and XNA. Thanks and keep up the great work.


Leave a comment


*