Skip to main content

Spritesheet

Up to date

This page is up to date for MonoGame.Extended 4.0.3. If you find outdated information, please open an issue.

A SpriteSheet is a wrapper around a Texture2DAtlas that provides additional methods for defining frame based animations based on the regions within the Texture2DAtlas.

Take a look at the following example texture atlas of an adventurer character.

Animated Pixel Adventurer by rvros; Licensed for free and commercial use.

We can see that this texture atlas has 16 separate regions, some of which can be grouped together to form an animation. For instance, the first 6 regions can be grouped together to form an attack animation

Animated Pixel Adventurer by rvros; Licensed for free and commercial use.

Knowing the regions that represent our frames of animation, we can use a SpriteSheet to define the animations.

Using SpriteSheet

To create a SpriteSheet you first need to create a Texture2DAtlas, then you use that in the constructor to create the SpriteSheet.

private SpriteSheet _spriteSheet;

protected override void LoadContent()
{
Texture2D adventurerTexture = Content.Load<Texture2D>("adventurer");
Texture2DAtlas atlas = Texture2DAtlas.Create("Atlas/adventurer", adventurerTexture, 50, 37);
_spriteSheet = new SpriteSheet("SpriteSheet/adventurer", atlas);
}

We created the Texture2DAltas using the Texture2DAtlas.Create method since all of the regions for each animation frame are uniform within the texture. Now that we have the SpriteSheet created, we can define our animations using the SpriteSheet.DefineAnimation method

private SpriteSheet _spriteSheet;

protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);

Texture2D adventurerTexture = Content.Load<Texture2D>("adventurer");
Texture2DAtlas atlas = Texture2DAtlas.Create("Atlas/adventurer", adventurerTexture, 50, 37);
_spriteSheet = new SpriteSheet("SpriteSheet/adventurer", atlas);

_spriteSheet.DefineAnimation("attack", builder =>
{
builder.IsLooping(true)
.AddFrame(regionIndex: 0, duration: TimeSpan.FromSeconds(0.1))
.AddFrame(1, TimeSpan.FromSeconds(0.1))
.AddFrame(2, TimeSpan.FromSeconds(0.1))
.AddFrame(3, TimeSpan.FromSeconds(0.1))
.AddFrame(4, TimeSpan.FromSeconds(0.1))
.AddFrame(5, TimeSpan.FromSeconds(0.1));
});
}
caution

When giving the name for an animation definition, the name must be unique across all animations defined in a single SpriteSheet.

Getting An Animation

Once you have defined the animations in the SpriteSheet, you can retrieve them by using the name you gave them when defining them. This will give you an instance of SpriteSheetAnimation which is an implementation of the IAnimation interface

private SpriteSheet _spriteSheet;

protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);

Texture2D adventurerTexture = Content.Load<Texture2D>("adventurer");
Texture2DAtlas atlas = Texture2DAtlas.Create("Atlas/adventurer", adventurerTexture, 50, 37);
_spriteSheet = new SpriteSheet("SpriteSheet/adventurer", atlas);

_spriteSheet.DefineAnimation("attack", builder =>
{
builder.IsLooping(true)
.AddFrame(regionIndex: 0, duration: TimeSpan.FromSeconds(0.1))
.AddFrame(1, TimeSpan.FromSeconds(0.1))
.AddFrame(2, TimeSpan.FromSeconds(0.1))
.AddFrame(3, TimeSpan.FromSeconds(0.1))
.AddFrame(4, TimeSpan.FromSeconds(0.1))
.AddFrame(5, TimeSpan.FromSeconds(0.1));
});

SpriteSheetAnimation attackAnimation = _spriteSheet.GetAnimation("attack");
}

You can then use this animation with the AnimationController class to control the animation including play, pause, reset, stop, and updating it each frame.

private SpriteSheet _spriteSheet;
private AnimationController _attackAnimationController;

protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);

Texture2D adventurerTexture = Content.Load<Texture2D>("adventurer");
Texture2DAtlas atlas = Texture2DAtlas.Create("Atlas/adventurer", adventurerTexture, 50, 37);
_spriteSheet = new SpriteSheet("SpriteSheet/adventurer", atlas);

_spriteSheet.DefineAnimation("attack", builder =>
{
builder.IsLooping(true)
.AddFrame(regionIndex: 0, duration: TimeSpan.FromSeconds(0.1))
.AddFrame(1, TimeSpan.FromSeconds(0.1))
.AddFrame(2, TimeSpan.FromSeconds(0.1))
.AddFrame(3, TimeSpan.FromSeconds(0.1))
.AddFrame(4, TimeSpan.FromSeconds(0.1))
.AddFrame(5, TimeSpan.FromSeconds(0.1));
});

SpriteSheetAnimation attackAnimation = _spriteSheet.GetAnimation("attack");
_attackAnimationController = new AnimationController(attackAnimation);
}

Updating the Animation Controller

The AnimationController needs to be updated each frame so it can track the progress of the animation and change frames when the duration for the current frame has passed

protected override void Update(GameTime gameTime)
{
_attackAnimationController.Update(gameTime);
}

Drawing the Animation

The AnimationController has a CurrentFrame property you can use to get the region index of the current frame of the animation. You can use this to know which Texture2DRegion from the source Texture2DAtlas of the SpriteSheet to draw

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

Texture2DRegion currentFrameTexture = _spriteSheet.TextureAtlas[_attackAnimationController.CurrentFrame];

_spriteBatch.Begin(samplerState: SamplerState.PointClamp);
_spriteBatch.Draw(currentFrameTexture, Vector2.Zero, Color.White, 0.0f, Vector2.Zero, new Vector2(3, 3), SpriteEffects.None, 0.0f);
_spriteBatch.End();

}

The result of drawing the attack animation from the example code above.

Conclusion

We have now learned how to create a new SpriteSheet based on a Texture2DAtlas, define animations within the SpriteSheet, then retrieve the animations and use them with an AnimationController. However, creating a controller for each possible animation can be tedious and a nightmare to maintain. In the next document, we'll discuss the AnimatedSprite class which provides a convenient encapsulation of the SpriteSheet where we can set what animation we want to play.