MonoGame.Extended is a set of utilities and extensions for MonoGame that makes it easier to make games. It provides additional functionality like sprite batching, texture atlases, bitmap fonts, cameras, input handling, and much more. In this tutorial, you will learn how to use sprite sheets with MonoGame.Extended.
The features demonstrated in this tutorial are available with
- MonoGame.Extended 5.0.0 or newer
- TexturePacker 7.9.0 or newer
Why use sprite sheets?
- Loading all graphics at once instead of numerous single images can significantly reduce your app's loading time.
- Using a sprite sheet also enhances the game's performance because textures only need to be set on the graphics device once, resulting in improved frame rates.
Setting up a MonoGame.Extended project
First, let's create a new MonoGame project and add MonoGame.Extended:
dotnet new mgdesktopgl -o DemoApp
cd DemoApp
dotnet add package MonoGame.Extended
dotnet add package MonoGame.Extended.Content.PipelineRunning dotnet run will open a window with a blue background.
Creating a sprite sheet
The easiest way to create a sprite sheet is using TexturePacker — available for Windows, macOS and Linux:
Creating a sprite sheet with TexturePacker is straightforward:
- Drag and drop all the files you want to add to the left pane of TexturePacker
- Select the MonoGame.Extended framework and set the file path for the generated Data file. The Texture file can be left empty; TexturePacker will automatically save the PNG containing the sprite sheet next to the data file.
- Click Publish sprite sheet
The demo project including the sprite images is available on GitHub.
By default, pivot points are at position (0,0), i.e., in the top-left corner of each sprite. For character sprites that should stand on the ground, use TexturePacker's Pivot point editor to set the pivot points of the walk+turn sprites to bottom-center.
We recommend storing the individual sprite images in a root folder named assets, while only placing the sprite sheet data and texture file in the Content folder. This approach makes it easier to determine which files must be deployed later.
Configuring the Content Pipeline
To use sprite sheets with MonoGame.Extended, you need to configure the Content Pipeline Extension to process
TexturePacker's JSON files. After installing, the DLL of the Content Pipeline Extension is located somewhere inside your
NuGet cache: ~/.nuget/packages/.../MonoGame.Extended.Content.Pipeline.dll
This DLL must be referenced in the MGCB file of your project. Using a relative path to this location is not recommended: each time your project is moved to another directory, the relative path breaks and must be updated. It's better to copy the DLL to your project directory and reference this local copy.
Add these lines to your .csproj file to automatically copy the Content Pipeline DLLs to a local
pipeline-references directory when starting a build (this MSBuild property tells the MonoGame.Extended package
where to copy its pipeline DLLs):
<PropertyGroup>
<MonoGameExtendedPipelineReferencePath>$(MSBuildThisFileDirectory)pipeline-references</MonoGameExtendedPipelineReferencePath>
</PropertyGroup>After calling dotnet build a pipeline-references directory should appear in your project root, containing two DLLs.
Then, add a reference to the MonoGame.Extended Content Pipeline in your Content/Content.mgcb file:
#-------------------------------- References --------------------------------#
/reference:../pipeline-references/MonoGame.Extended.Content.Pipeline.dll
#---------------------------------- Content ---------------------------------#Passing texture atlas to Content Pipeline
With dotnet mgcb-editor you can launch the MGCB Editor app. Use the menu item File → Open to open
your Content/Content.mgcb file. With Edit → Add → Existing item... you can add the sprite sheet image
and its corresponding JSON data file. If the content pipeline is configured correctly, the editor will
automatically select the TexturePacker Importer+Processor for the JSON file. Otherwise, go back to the previous
section and make sure that the pipeline reference is set correctly.
Use Build to save the .mgcb file and process the assets. If the build fails, please check if you
have selected the "MonoGame.Extended" framework in TexturePacker and if you are using the latest MonoGame.Extended
version.
Alternative approach:
Instead of using the MGCB editor, you can edit Content.mgcb directly with a text editor. Add the following lines to the content section of your Content.mgcb file:
#begin spritesheet-texture.png
/importer:TextureImporter
/processor:TextureProcessor
/processorParam:ColorKeyColor=255,0,255,255
/processorParam:ColorKeyEnabled=True
/processorParam:GenerateMipmaps=False
/processorParam:PremultiplyAlpha=True
/processorParam:ResizeToPowerOfTwo=False
/processorParam:MakeSquare=False
/processorParam:TextureFormat=Color
/build:spritesheet-texture.png
#begin spritesheet.json
/importer:TexturePackerJsonImporter
/processor:TexturePackerProcessor
/build:spritesheet.jsonUsing a sprite from a texture atlas
Let's start with the Game1.cs class generated by the MonoGame project template. Its default implementation displays a blue background. Let's load the sprite sheet and extract sprites from it:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.Graphics;
namespace DemoApp;
public class Game1 : Game
{
private GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch;
private Sprite _backgroundSprite;
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
_graphics.PreferredBackBufferWidth = 1000;
_graphics.PreferredBackBufferHeight = 650;
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);
// Load sprite atlas from Content Pipeline
Texture2DAtlas spriteAtlas = Content.Load<Texture2DAtlas>("spritesheet");
// Create sprite from atlas
_backgroundSprite = spriteAtlas.CreateSprite("Background");
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
_spriteBatch.Begin();
_spriteBatch.Draw(_backgroundSprite, Vector2.Zero);
_spriteBatch.End();
base.Draw(gameTime);
}
}We've loaded the sprite atlas using the Content Pipeline and created a sprite from it. The background sprite is now displayed instead of the solid blue color.
Creating an animation
Now, we want to add an animated character to our scene.
The SpriteSheet class manages the animation frames: We define the animation by adding frames with specific
durations and setting it to loop.
The AnimatedSprite handles the animation playback, its Update() method automatically selects the frame
to display, depending on the current game time.
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.Graphics;
namespace DemoApp;
public class Game1 : Game
{
private GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch;
private Sprite _backgroundSprite;
private AnimatedSprite _capguySprite;
private int _xPosition = 100;
private double _speedInPixelsPerSecond = 150;
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
_graphics.PreferredBackBufferWidth = 1000;
_graphics.PreferredBackBufferHeight = 650;
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);
// Load sprite atlas from Content Pipeline
Texture2DAtlas spriteAtlas = Content.Load<Texture2DAtlas>("spritesheet");
// Create sprite from atlas
_backgroundSprite = spriteAtlas.CreateSprite("Background");
// Create animation
SpriteSheet animationSheet = new SpriteSheet("capguy", spriteAtlas);
animationSheet.DefineAnimation("walk", builder =>
{
for (int i = 1; i <= 16; i++)
{
// Add frame using sprite name (e.g., "walk/0001", "walk/0002", etc.)
builder.AddFrame($"walk/{i:D4}", TimeSpan.FromMilliseconds(40));
}
builder.IsLooping(true);
});
_capguySprite = new AnimatedSprite(animationSheet, "walk");
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
_capguySprite.Update(gameTime);
_xPosition = _xPosition + (int)(gameTime.ElapsedGameTime.TotalSeconds * _speedInPixelsPerSecond);
_xPosition = _xPosition % (_graphics.PreferredBackBufferWidth + 100);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
_spriteBatch.Begin();
_spriteBatch.Draw(_backgroundSprite, Vector2.Zero);
_spriteBatch.Draw(_capguySprite, new Vector2(_xPosition, 580));
_spriteBatch.End();
base.Draw(gameTime);
}
}The character now walks across the screen continuously. The complete example project is available on GitHub.
