Texture Atlases

Introduction

This tutorial is kind of a long one, so I've broken it up into three separate pages. In this first page, we will look at what a texture atlas is, and how it can be used, as well as preparing our content for the rest of the tutorial. On the second page, we will create a class that will handle a texture atlas, and finally, on the third page, we will use this class to do 2D animation with a texture atlas.

Texture Atlases

In other tutorials, we have done a lot of work with basic 2D drawing. Of course, one of the things people want to do next is 2D animation. It is pretty easy to simply draw a sprite at different locations on the screen to show an object moving around. But imagine that we have a game where the player controls a character that walks around on the screen. In addition to having the character move, we would also want it to look like it is walking. This is commonly done using a technique called a texture atlas or texture strip. This tutorial will walk us through the process of doing 2D animation using a texture atlas, and in the mean time, it will help us see some of the more advanced features of 2D drawing and the SpriteBatch class.

Let's say we have a character like the one below:

screenshot1.png

If we want to have this character walking or jumping around on the screen, we would first need to set it up so that we can draw this texture at different locations on the screen. We would probably also want to have multiple versions of him at different stages of the walk, like below:

screenshot2.png

To do this, we could make a texture at each of the different stages that we want, and then draw the right one at the right time. However, having all of these little textures can actually be quite inefficient. Often, it is more efficient to put all of these various textures together into one big texture. This is called a texture atlas. A texture atlas gets its name because it is a collection of texture maps. A collection of real-world maps is called an atlas, so a collection of texture maps gets the name texture atlas. Below is an example of a texture atlas, which we will be using during this tutorial.

SmileyWalk.png

This texture atlas contains 16 different frames of our character walking. Frame 1 is in the top left corner, and the frames go across the row, and then down to the next row when the end of the row is reached, as shown below:

1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16

Note that texture atlases are not just confined to being used as an animation. Many games will place completely unrelated textures within the same texture atlas.

Download this texture and add it to your project like we have done before, with the link below:

SmileyWalk.png

We are now ready to move on and create a class that will handle 2D animation with texture atlases.

Creating an AnimatedSprite Class

In this part of the tutorial, we are going to create a class that will handle the texture atlas, and take care of the drawing for us.

Creating the Class

To create a new class, right-click on your project in the Solution pad and choose Add > New File from the popup menu. The New File dialog will appear. Select the General category on the left and choose the Empty Class template. Change the name (near the bottom) to AnimatedSprite.cs. Click on the New button when you have done this. The new class file will open up in the main editor window.

The first change we will want to make is to make the class public, so near the top of the file, find the line that says:

using Directives

Another thing we will need to do is include references to a couple of other assemblies, so that we can use the MonoGame libraries. Add the following two statements to the very top of the file with the other using statements:

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

Adding Instance Variables

Next, we will want to add the necessary instance variables, so add the following lines, just inside of the class declaration:

public Texture2D Texture { get; set; }
public int Rows { get; set; }
public int Columns { get; set; }
private int currentFrame;
private int totalFrames;

The variable Texture stores the texture atlas for our animation. Rows is the number of rows in the texture atlas. Columns is the number of columns in the atlas. Additionally, we need to keep track of which frame of the animation we are currently on, and how many frames there are total. These are stored in the currentFrame and totalFrames variables.

Adding a Constructor

The next thing to add to the class is a constructor, that we can use later to make a new AnimatedSprite object. We can do that with the following code:

public AnimatedSprite(Texture2D texture, int rows, int columns)
{
    Texture = texture;
    Rows = rows;
    Columns = columns;
    currentFrame = 0;
    totalFrames = Rows * Columns;
}

This constructor requires the user to give us a texture, along with the number of rows and columns in the texture atlas. We then assign these values to the appropriate instance variables. Additionally, we set the current frame to be 0, and we calculate the total number of frames which is simply the rows in the texture atlas multiplied by the columns.

The Update() Method

Next we will add an Update() method to our class which will change the current frame to the next frame. Add the following code to your class:

public void Update()
{
    currentFrame++;
    if (currentFrame == totalFrames)
        currentFrame = 0;
}

This simply increments the frame, and if it needs to start back over at the beginning, it does.

The Draw() Method

This method will probably be the most interesting part of this class. This is also where we will do some new stuff with 2D graphics. Add the following code as a method in your class:

public void Draw(SpriteBatch spriteBatch, Vector2 location)
{
    int width = Texture.Width / Columns;
    int height = Texture.Height / Rows;
    int row = (int)((float)currentFrame / (float)Columns);
    int column = currentFrame % Columns;
 
    Rectangle sourceRectangle = new Rectangle(width * column, height * row, width, height);
    Rectangle destinationRectangle = new Rectangle((int)location.X, (int)location.Y, width, height);
 
    spriteBatch.Begin();
    spriteBatch.Draw(Texture, destinationRectangle, sourceRectangle, Color.White);
    spriteBatch.End();
}

In this method, the first thing we need to do is determine which part of the texture we are going to draw to draw only the current frame. So we start off by calculating the width and height of the frame. We then need to calculate which row and column the current frame is located at.

In the second section, we calculate a "source rectangle", which is a rectangle within the texture (the source) that we want to draw. At this point, we also calculate a "destination rectangle" which is a rectangle that represents where the texture will be drawn.

Finally, we draw the correct part of the texture on the screen with a call one of the SpriteBatch.Draw() methods. This is a version of this method that we haven't seen yet, but takes a texture, a source rectangle, a destination rectangle, and a color. This will draw only the part of the texture that is used in the current frame.

The entire code for the AnimatedSprite class is below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
 
namespace TextureAtlas
{
    public class AnimatedSprite
    {
        public Texture2D Texture { get; set; }
        public int Rows { get; set; }
        public int Columns { get; set; }
        private int currentFrame;
        private int totalFrames;
 
        public AnimatedSprite(Texture2D texture, int rows, int columns)
        {
            Texture = texture;
            Rows = rows;
            Columns = columns;
            currentFrame = 0;
            totalFrames = Rows * Columns;
        }
 
        public void Update()
        {
            currentFrame++;
            if (currentFrame == totalFrames)
                currentFrame = 0;
        }
 
        public void Draw(SpriteBatch spriteBatch, Vector2 location)
        {
            int width = Texture.Width / Columns;
            int height = Texture.Height / Rows;
            int row = (int)((float)currentFrame / (float)Columns);
            int column = currentFrame % Columns;
 
            Rectangle sourceRectangle = new Rectangle(width * column, height * row, width, height);
            Rectangle destinationRectangle = new Rectangle((int)location.X, (int)location.Y, width, height);
 
            spriteBatch.Begin();
            spriteBatch.Draw(Texture, destinationRectangle, sourceRectangle, Color.White);
            spriteBatch.End();
        }
    }
}

Using Our AnimatedSprite Class

We will now go back to our main game class and draw an animated sprite with our new AnimatedSprite class that we created in Part 2. Go back over to your main game class (called Game1.cs by default). We will need to make a few simple changes here to get our animated sprite to draw.

First, we will need an instance variable to store our animated sprite, so add the following code as an instance variable in your game class:

private AnimatedSprite animatedSprite;

Next, go down to your LoadContent() method, and add the following code, to get your animated sprite ready to use:

Texture2D texture = Content.Load<Texture2D>("SmileyWalk");
animatedSprite = new AnimatedSprite(texture, 4, 4);

These two lines load the texture, and then create the animated sprite from it. This texture has four rows and four columns, so we pass those values in as well.

Now all we need to do is update the animated sprite and draw it. To update it, add the following line of code to your Update() method:

animatedSprite.Update();

To draw the animated sprite, add the following line to your Draw() method:

animatedSprite.Draw(spriteBatch, new Vector2(400, 200));

You should now be able to run the game and see the character walking in place!

screenshot1.png

Below is the code for the main game class:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
 
namespace TextureAtlas
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        private AnimatedSprite animatedSprite;
 
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }
 
        protected override void Initialize()
        {
            base.Initialize();
        }
 
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
 
            Texture2D texture = Content.Load<Texture2D>("SmileyWalk");
            animatedSprite = new AnimatedSprite(texture, 4, 4);
        }
 
        protected override void UnloadContent()
        {
        }
 
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
 
            animatedSprite.Update();
 
            base.Update(gameTime);
        }
 
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
 
            animatedSprite.Draw(spriteBatch, new Vector2(400, 200));
 
            base.Draw(gameTime);
        }
    }
}

What's Next?

This tutorial covered texture atlases, and covered some more features of 2D games. As a next step, you might want to check out the tutorial on rotating sprites, to see some more interesting things you can do in a 2D game.


Troubleshooting.png Having problems with this tutorial? Try the troubleshooting page!