Skip to content

Commit 42e8169

Browse files
Merge pull request MonoGame#9 from AristurtleDev/07_the_sprite_class
Add Chapter 07: The Sprite Class
2 parents 69b3f8b + 796fa50 commit 42e8169

File tree

5 files changed

+320
-3
lines changed

5 files changed

+320
-3
lines changed

articles/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@
124124
href: tutorials/building_2d_games/05_working_with_textures/
125125
- name: "06: Optimizing Texture Rendering"
126126
href: tutorials/building_2d_games/06_optimizing_texture_rendering/
127+
- name: "07: The Sprite Class"
128+
href: tutorials/building_2d_games/07_the_sprite_class/
127129
- name: Console Access
128130
href: console_access.md
129131
- name: Help and Support
Loading
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
---
2+
title: "Chapter 07: The Sprite Class"
3+
description: "Explore creating a reusable Sprite class to efficiently sprites and their rendering properties, including position, rotation, scale, and more."
4+
---
5+
6+
In [Chapter 06](../06_optimizing_texture_rendering/index.md), you learned how to use texture atlases to optimize rendering performance. While this solved the issue of texture swapping, managing individual sprites and their properties becomes increasingly complex as your game grows. Even in our simple example with just a slime and a bat, we would eventually need to track various properties for each sprite:
7+
8+
- Color mask for tinting.
9+
- Origin for rotation and scale.
10+
- Scale for size adjustments.
11+
- Rotation for orientation.
12+
- Sprite effects to flip horizontally and/or vertically.
13+
- Layer depth for draw order layering.
14+
15+
Imagine scaling this up to dozens of sprites, each with multiple instances on screen. Tracking all these properties through individual variables quickly becomes unmanageable. In this chapter, we'll solve this by creating a class that encapsulates sprite information and handles rendering.
16+
17+
## The Sprite Class
18+
19+
A sprite in our game represents a visual object created from a texture region along with its rendering properties. While multiple sprites might use the same texture region (like multiple enemies of the same type), each sprite can have unique properties that control how it appears on screen; its position, rotation, scale, and other visual characteristics.
20+
21+
By creating a `Sprite` class, we can encapsulate both the texture region and its rendering parameters into a single, reusable component. This not only makes our code more organized but also makes it easier to manage multiple instances of the same type of sprite.
22+
23+
In the *Graphics* directory within the *MonoGameLibrary* project, add a new file named *Sprite.cs*. Add the following code for the foundation of the `Sprite` class to the *Sprite.cs* file:
24+
25+
```cs
26+
using Microsoft.Xna.Framework;
27+
using Microsoft.Xna.Framework.Graphics;
28+
29+
namespace MonoGameLibrary.Graphics;
30+
31+
public class Sprite
32+
{
33+
34+
}
35+
```
36+
37+
### Properties
38+
39+
The `Sprite` class will utilize properties that mirror the parameters used in [**SpriteBatch.Draw**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.Draw(Microsoft.Xna.Framework.Graphics.Texture2D,Microsoft.Xna.Framework.Vector2,System.Nullable{Microsoft.Xna.Framework.Rectangle},Microsoft.Xna.Framework.Color,System.Single,Microsoft.Xna.Framework.Vector2,System.Single,Microsoft.Xna.Framework.Graphics.SpriteEffects,System.Single)) so the rendering parameter for each sprite is self contained. Add the following properties:
40+
41+
```cs
42+
/// <summary>
43+
/// Gets or Sets the source texture region represented by this sprite.
44+
/// </summary>
45+
public TextureRegion Region { get; set; }
46+
47+
/// <summary>
48+
/// Gets or Sets the color mask to apply when rendering this sprite.
49+
/// </summary>
50+
/// <remarks>
51+
/// Default value is Color.White
52+
/// </remarks>
53+
public Color Color { get; set; } = Color.White;
54+
55+
/// <summary>
56+
/// Gets or Sets the amount of rotation, in radians, to apply when rendering this sprite.
57+
/// </summary>
58+
/// <remarks>
59+
/// Default value is 0.0f
60+
/// </remarks>
61+
public float Rotation { get; set; } = 0.0f;
62+
63+
/// <summary>
64+
/// Gets or Sets the scale factor to apply to the x- and y-axes when rendering this sprite.
65+
/// </summary>
66+
/// <remarks>
67+
/// Default value is Vector2.One
68+
/// </remarks>
69+
public Vector2 Scale { get; set; } = Vector2.One;
70+
71+
/// <summary>
72+
/// Gets or Sets the xy-coordinate origin point, relative to the top-left corner, of this sprite.
73+
/// </summary>
74+
/// <remarks>
75+
/// Default value is Vector2.Zero
76+
/// </remarks>
77+
public Vector2 Origin { get; set; } = Vector2.Zero;
78+
79+
/// <summary>
80+
/// Gets or Sets the sprite effects to apply when rendering this sprite.
81+
/// </summary>
82+
/// <remarks>
83+
/// Default value is SpriteEffects.None
84+
/// </remarks>
85+
public SpriteEffects Effects { get; set; } = SpriteEffects.None;
86+
87+
/// <summary>
88+
/// Gets or Sets the layer depth to apply when rendering this sprite.
89+
/// </summary>
90+
/// <remarks>
91+
/// Default value is 0.0f
92+
/// </remarks>
93+
public float LayerDepth { get; set; } = 0.0f;
94+
95+
/// <summary>
96+
/// Gets the width, in pixels, of this sprite.
97+
/// </summary>
98+
/// <remarks>
99+
/// Width is calculated by multiplying the width of the source texture region by the x-axis scale factor.
100+
/// </remarks>
101+
public float Width => Region.Width * Scale.X;
102+
103+
/// <summary>
104+
/// Gets the height, in pixels, of this sprite.
105+
/// </summary>
106+
/// <remarks>
107+
/// Height is calculated by multiplying the height of the source texture region by the y-axis scale factor.
108+
/// </remarks>
109+
public float Height => Region.Height * Scale.Y;
110+
```
111+
112+
The `TextureRegion` property works to provide the texture and source rectangle when rendering the sprite. Other properties directly correspond to [**SpriteBatch.Draw**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.Draw(Microsoft.Xna.Framework.Graphics.Texture2D,Microsoft.Xna.Framework.Vector2,System.Nullable{Microsoft.Xna.Framework.Rectangle},Microsoft.Xna.Framework.Color,System.Single,Microsoft.Xna.Framework.Vector2,System.Single,Microsoft.Xna.Framework.Graphics.SpriteEffects,System.Single)) parameters with the same default values, making it easy to understand how each property affects the sprite's appearance.
113+
114+
> [!TIP]
115+
> The calculated `Width` and `Height` properties make it easier to position sprites relative to each other without manually applying scale factors.
116+
117+
### Constructors
118+
119+
The `Sprite` class will provide two ways to create a new sprite. Add the following constructors:
120+
121+
```cs
122+
/// <summary>
123+
/// Creates a new sprite.
124+
/// </summary>
125+
public Sprite() { }
126+
127+
/// <summary>
128+
/// Creates a new sprite using the specified source texture region.
129+
/// </summary>
130+
/// <param name="region">The texture region to use as the source texture region for this sprite.</param>
131+
public Sprite(TextureRegion region)
132+
{
133+
Region = region;
134+
}
135+
```
136+
137+
The default constructor creates an empty sprite that can be configured later, while the parameterized constructor allows you to specify the source texture region for the sprite.
138+
139+
### Methods
140+
141+
Finally, the `Sprite` class provides the following two methods:
142+
143+
```cs
144+
/// <summary>
145+
/// Sets the origin of this sprite to the center
146+
/// </summary>
147+
public void CenterOrigin()
148+
{
149+
Origin = new Vector2(Region.Width, Region.Height) * 0.5f;
150+
}
151+
152+
/// <summary>
153+
/// Submit this sprite for drawing to the current batch.
154+
/// </summary>
155+
/// <param name="spriteBatch">The SpriteBatch instance used for batching draw calls.</param>
156+
/// <param name="position">The xy-coordinate position to render this sprite at.</param>
157+
public void Draw(SpriteBatch spriteBatch, Vector2 position)
158+
{
159+
Region.Draw(spriteBatch, position, Color, Rotation, Origin, Scale, Effects, LayerDepth);
160+
}
161+
```
162+
163+
- `CenterOrigin`: Sets the origin point of the sprite to its center.
164+
165+
> [!NOTE]
166+
> The origin needs to be set based on the width and height of the source texture region itself, regardless of the scale the sprite is rendered at.
167+
168+
- `Draw`: Uses the `TextureRegion` property to submit the sprite for rendering using the properties of the sprite itself.
169+
170+
## Create Sprites With The TextureAtlas Class
171+
172+
While the `GetRegion` method of the `TextureAtlas` class we created in [Chapter 06](../06_optimizing_texture_rendering/index.md#the-textureatlas-class) works well for retrieving regions, creating sprites requires multiple steps:
173+
174+
1. Get the region by name.
175+
2. Store it in a variable.
176+
3. Create a new sprite with that region.
177+
178+
We can simplify this process by adding a sprite creation method to the `TextureAtlas` class. Open *TextureAtlas.cs* and add the following method:
179+
180+
```cs
181+
/// <summary>
182+
/// Creates a new sprite using the region from this texture atlas with the specified name.
183+
/// </summary>
184+
/// <param name="regionName">The name of the region to create the sprite with.</param>
185+
/// <returns>A new Sprite using the texture region with the specified name.</returns>
186+
public Sprite CreateSprite(string regionName)
187+
{
188+
TextureRegion region = GetRegion(regionName);
189+
return new Sprite(region);
190+
}
191+
```
192+
193+
## Using the `Sprite` Class
194+
195+
Let's adjust our game now to use the `Sprite` class instead of just the texture regions. Replace the contents of *Game1.cs* with the following:
196+
197+
[!code-csharp[](./src/Game1-sprite-usage.cs?highlight=12-13,36-38,55-59)]
198+
199+
The key changes in this implementation are:
200+
201+
- The `_slime` and `_bat` members were changed from `TextureRegion` to `Sprite`.
202+
- In [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent) the `_slime` and `_bat` sprites are now created using the new `TextureAtlas.CreateSprite` method.
203+
- In [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)), the draw calls were updated to use the `Sprite.Draw` method.
204+
205+
Running the game now will produce the same result as in the previous chapter.
206+
207+
<figure><img src="./images/slime-and-bat-rendered.png" alt="Figure 7-1: The slime and bat sprites being rendered in the upper-left corner of the game window."><figcaption><p><strong>Figure 7-1: The slime and bat sprites being rendered in the upper-left corner of the game window.</strong></p></figcaption></figure>
208+
209+
Try adjusting the various properties available for the slime and the bat sprites to see how they affect the rendering.
210+
211+
## Conclusion
212+
213+
In this chapter, we created a reusable `Sprite` class that encapsulates the properties for each sprite that we would render. The `TextureAtlas` class was updated to simplify sprite creation based on the `Sprite` class we created.
214+
215+
In the next chapter, we'll build upon the `Sprite` class to create an `AnimatedSprite` class that will allow us to bring our sprites to life through animation.
216+
217+
## Test Your Knowledge
218+
219+
1. What is the benefit of using a Sprite class instead of managing texture regions directly?
220+
221+
<details>
222+
<summary>Question 1 Answer</summary>
223+
224+
> The `Sprite` class encapsulates all rendering properties (position, rotation, scale, etc.) into a single, reusable component. This makes it easier to manage multiple instances of the same type of sprite without having to track properties through individual variables.
225+
</details><br />
226+
227+
2. Why do the `Width` and `Height` properties of a Sprite take the Scale property into account?
228+
229+
<details>
230+
<summary>Question 2 Answer</summary>
231+
232+
> The `Width` and `Height` properties account for scaling to make it easier to position sprites relative to each other without having to manually calculate the scaled dimensions. This is particularly useful when sprites are rendered at different scales.
233+
</details><br />
234+
235+
3. When using the `CenterOrigin` method, why is the origin calculated using the region's dimensions rather than the sprite's scaled dimensions?
236+
237+
<details>
238+
<summary>Question 3 Answer</summary>
239+
240+
> The origin needs to be set based on the texture region's actual dimensions because it represents the point around which scaling and rotation are applied. Using the scaled dimensions would result in incorrect positioning since the origin would change based on the current scale factor.
241+
</details><br />
242+
243+
4. What advantage does the `TextureAtlas.CreateSprite` method provide over using `GetRegion`?
244+
245+
<details>
246+
<summary>Question 4 Answer</summary>
247+
248+
> The `CreateSprite` method simplifies sprite creation by combining multiple steps (getting the region, storing it, creating a sprite) into a single method call. This reduces code repetition and makes sprite creation more straightforward.
249+
</details><br />
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using Microsoft.Xna.Framework;
2+
using Microsoft.Xna.Framework.Graphics;
3+
using Microsoft.Xna.Framework.Input;
4+
using MonoGameLibrary.Graphics;
5+
6+
namespace MonoGameSnake;
7+
8+
public class Game1 : Game
9+
{
10+
private GraphicsDeviceManager _graphics;
11+
private SpriteBatch _spriteBatch;
12+
private Sprite _slime;
13+
private Sprite _bat;
14+
15+
public Game1()
16+
{
17+
_graphics = new GraphicsDeviceManager(this);
18+
Content.RootDirectory = "Content";
19+
IsMouseVisible = true;
20+
}
21+
22+
protected override void Initialize()
23+
{
24+
// TODO: Add your initialization logic here
25+
26+
base.Initialize();
27+
}
28+
29+
protected override void LoadContent()
30+
{
31+
_spriteBatch = new SpriteBatch(GraphicsDevice);
32+
33+
// Create the texture atlas from the XML configuration file
34+
TextureAtlas atlas = TextureAtlas.FromFile(Content, "images/atlas-definition.xml");
35+
36+
// Create the slime and bat sprites
37+
_slime = atlas.CreateSprite("slime");
38+
_bat = atlas.CreateSprite("bat");
39+
}
40+
41+
protected override void Update(GameTime gameTime)
42+
{
43+
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
44+
Exit();
45+
46+
base.Update(gameTime);
47+
}
48+
49+
protected override void Draw(GameTime gameTime)
50+
{
51+
GraphicsDevice.Clear(Color.CornflowerBlue);
52+
53+
_spriteBatch.Begin();
54+
55+
// Draw the slime sprite
56+
_slime.Draw(_spriteBatch, Vector2.One);
57+
58+
// Draw the bat sprite 10px to the right of the slime.
59+
_bat.Draw(_spriteBatch, new Vector2(_slime.Width + 10, 0));
60+
61+
_spriteBatch.End();
62+
63+
base.Draw(gameTime);
64+
}
65+
}

articles/tutorials/building_2d_games/index.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,19 @@ This documentation will introduce game development concepts using the MonoGame f
2020
> This is currently a work in progress and is not finished.
2121
2222
| Chapter | Summary | Source Files |
23-
| ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
23+
|------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
2424
| [01: What Is MonoGame](01_what_is_monogame/index.md) | Learn about the history of MonoGame and explore the features it provides developers when creating games. | |
2525
| [02: Getting Started](02_getting_started/index.md) | Setup your development environment for dotnet development and MonoGame using Visual Studio Code as your IDE. | |
2626
| [03: The Game1 File](03_the_game1_file/index.md) | Explore the contents of the Game1 file generated when creating a new MonoGame project. | |
2727
| [04: Content Pipeline](04_content_pipeline/index.md) | Learn the advantages of using the **Content Pipeline** to load assets and go through the processes of loading your first asset | |
2828
| [05: Working with Textures](05_working_with_textures/index.md) | Learn how to load and render textures using the MonoGame content pipeline and [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch). | |
29-
| [05: Optimizing Texture Rendering](06_optimizing_texture_rendering/index.md) | Explore optimization techniques when rendering textures using a texture atlas. | |
29+
| [06: Optimizing Texture Rendering](06_optimizing_texture_rendering/index.md) | Explore optimization techniques when rendering textures using a texture atlas. | |
30+
| [07: The Sprite Class](07_the_sprite_class/index.md) | Explore creating a reusable Sprite class to efficiently sprites and their rendering properties, including position, rotation, scale, and more. | |
3031

3132
In additional to the chapter documentation, supplemental documentation is also provided to give a more in-depth look at different topics with MonoGame. These are provided through the Appendix documentation below:
3233

3334
| Appendix | Summary |
34-
| -------- | ------- |
35+
|----------|---------|
3536
| | |
3637

3738
## Conventions Used in This Documentation

0 commit comments

Comments
 (0)