MonoGame Sprite Animation

Game Development

In games, you often want to have your characters or game objects appear as if they are in motion. When you move game objects around the screen to create movement, using static images the game feel somewhat lifeless. To address this problem, you can use animation. As you may know, you create an animation by rapidly displaying related images in succession to create a sense of motion—and animation; is no different in games. Typically, you use animation for simple motions such as walking and running, as well as more complex motions such as fighting strikes or dance moves. This article shows how you can add motion to your objects by using a sequence of images and a sprite sheet.

The Sprite Animation
This article demonstrates how to move two animated characters around the screen. You are able to adjust the animation speed of each character in order to create a more fluid sense of movement across the screen. You can see an example of the ended work here:

getfile

The controls are as follows:
Right thumbstick (arrow-keys) Moves the character around
Left thumbstick’s x-axis (AD-keys) Controls the animation speed
Button A (K-key) Selects the left character
Button B (L-key) Selects the right character

The goals are as follows:
To experience components involved in sprite animation
To understand the support MonoGame SpriteBatch provides for sprite animation

The steps for creating the project are as follows:
Gain an overview of sprite sheets.
Create a SpritePrimitive class.
Modify the GameState class to include the newly created sprite functionality.

Add the following resource, into your content project before you begin:
SimpleSpriteSheet.png

getfile (1)

Overview of sprite sheets
To achieve animation, games often use what are known as sprites, or more specifically, sprite sheets. A sprite sheet is an image that contains an animation separated into one or more rows and columns (R×C). For example, in the previous figure you can see a 4×2 sprite sheet that contains two separate animations. The animations depict a character walking right and left. In this example, the animations are separated into separate rows; however, this is not always the case. The organization of a sprite sheet is generally handled by its creator.

For a more specific example, take a look at the top row of the sprite sheet. The images in this row depict the character walking to the right. To achieve this effect, each character image must to be displayed from left to right, in the sequence 1, 2, 3, 4. After the last character image has been displayed, the animation is looped back to the first image in the row. You can see an example of this:

getfile (2)

Now that you know the general usage that sprite sheets can provide, here’s a quick look at the math you need to achieve this type of behavior.Given that the sprite sheet is 4×2 and the image has a resolution of 256×128 pixels, you can deduce that each sprite has a resolution of 256/4×128/2 or 64×64 pixels.
While that works great for this particular sprite sheet example, you’ll need a more generalized equation to implement a class that can handle sprite sheets that contain varying numbers of sprites and resolutions.
However, this is still fairly simple to calculate. Given that the pixel resolution of the sprite sheet is equal to P×Q, where P represents the pixel width and Q represents the pixel height, then each sprite’s resolution within the sprite sheet is equal to P/R×Q/C, where R and C represent the numbers of rows and columns, respectively.
For example, if a sprite sheet has a pixel resolution of 256×256 and its rows and columns are 4×4, then each sprite has a resolution of 256/4×256/2, or 64×64 pixels. In general, sprite sheets are composed of sprites of equal size to allow easy targeting of each individual sprite. Sprite sheets with varying-size sprites do exist; however, you then need a more sophisticated calculation to single out each sprite.
With the basic knowledge of sprite sheets under your belt, you can now tackle the creation of a sprite class.

Creating the SpritePrimitive class

1. Begin by creating a new class called SpritePrimitive that inherits from GameObject. Add instance variables for rows, columns, padding, and the sprite sheet’s width and height. The padding variable is used to define the space between each frame or sprite if necessary.

public class SpritePrimitive :GameObject
{
private int mNumRow, mNumColumn, mPaddings;
private int mSpriteImageWidth, mSpriteImageHeight;

#region Per Animation setting

#endregion


}

2.Now create the following variables to provide support for sprite animation:
private int mUserSpecifedTicks;
private int mCurrentTick;
private int mCurrentRow, mCurrentColumn;
private int mBeginRow, mEndRow;
private int mBeginCol, mEndCol;

mUserSpecifedTicks keeps track of the number of machine ticks before changing to the next frame.
mCurrentTick is for keeping track of the number of ticks since the current frame started to be displayed.
mCurrentRow and mCurrentColumn are for displaying the current frame’s row and column.
mBeginRow, mEndRow, mBeginCol, and mEndCol are used to keep track of which frame you start and end on.

3.Add a constructor to initialize the variables to the default values. Make sure to support parameters for the file name, position, and size, as well as the amount of rows, columns, and padding that the sprite sheet contains.

public SpritePrimitive(String image, Vector2 position, Vector2 size,
int rowCounts, int columnCount, int padding) :
base(image, position, size)
{
mNumRow = rowCounts;
mNumColumn = columnCount;
mPaddings = padding;
mSpriteImageWidth = mImage.Width / mNumRow;
mSpriteImageHeight = mImage.Height / mNumColumn;

mUserSpecifedTicks = 1;
mCurrentTick = 0;
mCurrentRow = 0;
mCurrentColumn = 0;
mBeginRow = mBeginCol = mEndRow = mEndCol = 0;
}

4.Now create get and set accessors for the variables, as shown in the code that follows:

public int SpriteBeginRow
{
get { return mBeginRow; }
set { mBeginRow = value; mCurrentRow = value; }
}
public int SpriteEndRow
{
get { return mEndRow; }
set { mEndRow = value; }
}
public int SpriteBeginColumn
{
get { return mBeginCol; }
set { mBeginCol = value; mCurrentColumn = value; }
}
public int SpriteEndColumn
{
get { return mEndCol; }
set { mEndCol = value; }
}
public int SpriteAnimationTicks
{
get { return mUserSpecifedTicks; }
set { mUserSpecifedTicks = value; }
}

5.Next, create a public method that allows you to set the beginning and ending frames for the animation. To do this, you must know which row and column the beginning frame resides in. Additionally, you allow for the modification of the tick interval, which specifies how often the animation should change frames. The smaller the tick interval, the faster the animation will be.

public void SetSpriteAnimation(int beginRow, int beginCol, int endRow, int endCol, int tickInterval)
{
mUserSpecifedTicks = tickInterval;
mBeginRow = beginRow;
mBeginCol = beginCol;
mEndRow = endRow;
mEndCol = endCol;

mCurrentRow = mBeginRow;
mCurrentColumn = mBeginCol;
mCurrentTick = 0;
}

6.Create the Update() function. During each update, increment the current tick and check whether or not the tick time is up. If so, reset the current tick and move to next sprite frame. When the current frame is equal to the last frame in the row, set the current frame back to the first frame in the row.

public override void Update()
{
base.Update();

mCurrentTick++;
if (mCurrentTick > mUserSpecifedTicks)
{
mCurrentTick = 0;
mCurrentColumn++;
if (mCurrentColumn > mEndCol)
{
mCurrentColumn = mBeginCol;
mCurrentRow++;

if (mCurrentRow > mEndRow)
mCurrentRow = mBeginRow;
}
}
}

7.Now it is time to create the Draw() function. First and foremost, you need to create the destination rectangle where the image will be displayed by calling the Camera class’s ComputePixelRectangle() function. Then add a source rectangle that specifies the frame on the sprite sheet that will be mapped to the destination rectangle. This is done using the position of the desired frame’s top-left corner (taking padding into account) and the sprite image’s width and height. Finally, define the origin of rotation and draw the destination rectangle using the Game1.sSpriteBatch.Draw() function call.

public override void Draw()
{
// Define location and size of the texture
Rectangle destRect = Camera.ComputePixelRectangle(Position, Size);

int imageTop = mCurrentRow * mSpriteImageWidth;
int imageLeft = mCurrentColumn * mSpriteImageHeight;
// Define the area to draw from the spriteSheet
Rectangle srcRect = new Rectangle(
imageLeft + mPaddings,
imageTop + mPaddings,
mSpriteImageWidth, mSpriteImageHeight);

// Define the rotation origin
Vector2 org = new Vector2(mSpriteImageWidth/2, mSpriteImageHeight/2);

// Draw the texture
Game1.sSpriteBatch.Draw(
mImage,
destRect, // Area to be drawn in pixel space
srcRect, // Rect on the spriteSheet
Color.White, //
mRotateAngle, // Angle to rotate (clockwise)
org, // Image reference position
SpriteEffects.None, 0f);

if (null != Label)
FontSupport.PrintStatusAt(Position, Label, LabelColor);
}

Now you’ll modify the GameState class to use the SpritePrimitive class.

Modifying the GameState class

1.Start by adding variables for sprite animation speed, the hero primitives, and the currently selected hero primitive.

const int kSpriteSpeedFactor = 10; // Value of 1 maps to updates of 10 ticks
SpritePrimitive mHero, mAnotherHero;
SpritePrimitive mCurrent;

2.Now modify the constructor to initialize the instance variables with the values shown in the code that follows:

public GameState()
{
mHero = new SpritePrimitive(
“SimpleSpriteSheet”,
new Vector2(20, 30), new Vector2(10, 10),
4, // Number of rows
2, // Number of columns
0); // Padding between images

mAnotherHero = new SpritePrimitive(

“SimpleSpriteSheet”,
new Vector2(80, 30), new Vector2(10, 10),
4, // Number of rows
2, // Number of columns
0); // Padding between images

// Start mHero by walking left and mAnotherHero by walking right
mHero.SetSpriteAnimation(0, 0, 0, 3, 10); // Slowly
mAnotherHero.SetSpriteAnimation(1, 0, 1, 3, 5); // Twice as fast
mCurrent = mAnotherHero;
}

3.Remember, mHero.SetSpriteAnimation(0, 0, 0, 3, 10); means the animation will start from frame 0, 0 and continue to frame 0, 3. The 10 used indicates the number of ticks before changing to the next frame. The following image provides a visual representation of the frame-numbering system.

getfile (3)

4.Next, in the UpdateGame() function, perform the following steps:
Update both heroes by calling their Update() functions:

public void UpdateGame()
{
mHero.Update();
mAnotherHero.Update();

UserControlUpdate();
}

Implement the UserControlUpdate() function by doing the following:
• Changing the currently selected hero via the A and B buttons
• Rotating the image when the X or Y button is pressed
• Mapping the current hero’s position to the right thumbstick for movement
• Mapping the left thumbstick to the animation speed of the hero

private void UserControlUpdate()
{
#region Selecting Hero
if (InputWrapper.Buttons.A == ButtonState.Pressed)
mCurrent = mHero;
if (InputWrapper.Buttons.B == ButtonState.Pressed)
mCurrent = mAnotherHero;
mCurrent.Position += InputWrapper.ThumbSticks.Right;
#endregion

#region Specifying rotation
if (InputWrapper.Buttons.X == ButtonState.Pressed)
mCurrent.RotateAngleInRadian += MathHelper.ToRadians(1);
if (InputWrapper.Buttons.Y == ButtonState.Pressed)
mCurrent.RotateAngleInRadian += MathHelper.ToRadians(-1);
#endregion

#region spriteSheet Update
if (InputWrapper.ThumbSticks.Left.X == 0)
{
mCurrent.SpriteEndColumn = 0; // Stops the animation
}
Else
{
float useX = InputWrapper.ThumbSticks.Left.X;
mCurrent.SpriteEndColumn = 3;
if (useX < 0)
{
mCurrent.SpriteBeginRow = 1;
mCurrent.SpriteEndRow = 1;
useX *= -1f;
}
else
{
mCurrent.SpriteBeginRow = 0;
mCurrent.SpriteEndRow = 0;
}
mCurrent.SpriteAnimationTicks = (int)((1f – useX) * kSpriteSpeedFactor);
}
#endregion
}

5.Finally, modify the DrawGame() function so it draws both heroes:

public void DrawGame()
{
mHero.Draw();
mAnotherHero.Draw();
}

References:
Learn 2D Game Development with C#
By: Jebediah Pavleas; Jack Keng-Wei Chang; Kelvin Sung; Robert Zhu