From 993066fa7aa51bb7774586812472cf4e39e7256e Mon Sep 17 00:00:00 2001 From: Christopher Whitley <103014489+AristurtleDev@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:51:26 -0500 Subject: [PATCH 1/2] Remove inline code block in header --- .../tutorials/building_2d_games/07_the_sprite_class/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/tutorials/building_2d_games/07_the_sprite_class/index.md b/articles/tutorials/building_2d_games/07_the_sprite_class/index.md index 9d7ea17b..2bc4bb15 100644 --- a/articles/tutorials/building_2d_games/07_the_sprite_class/index.md +++ b/articles/tutorials/building_2d_games/07_the_sprite_class/index.md @@ -190,7 +190,7 @@ public Sprite CreateSprite(string regionName) } ``` -## Using the `Sprite` Class +## Using the Sprite Class 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: From c98927087178ab579303a1a1f42bb22d35b99feb Mon Sep 17 00:00:00 2001 From: Christopher Whitley <103014489+AristurtleDev@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:51:35 -0500 Subject: [PATCH 2/2] Add chapter 8 --- articles/toc.yml | 2 + .../images/bat-animation-example.gif | Bin 0 -> 6567 bytes .../images/slime-bat-animated.gif | Bin 0 -> 13522 bytes .../08_the_animatedsprite_class/index.md | 534 ++++++++++++++++++ .../src/Game1-animatedsprite-usage.cs | 72 +++ articles/tutorials/building_2d_games/index.md | 1 + 6 files changed, 609 insertions(+) create mode 100644 articles/tutorials/building_2d_games/08_the_animatedsprite_class/images/bat-animation-example.gif create mode 100644 articles/tutorials/building_2d_games/08_the_animatedsprite_class/images/slime-bat-animated.gif create mode 100644 articles/tutorials/building_2d_games/08_the_animatedsprite_class/index.md create mode 100644 articles/tutorials/building_2d_games/08_the_animatedsprite_class/src/Game1-animatedsprite-usage.cs diff --git a/articles/toc.yml b/articles/toc.yml index 3878364f..3acdcdc2 100644 --- a/articles/toc.yml +++ b/articles/toc.yml @@ -126,6 +126,8 @@ href: tutorials/building_2d_games/06_optimizing_texture_rendering/ - name: "07: The Sprite Class" href: tutorials/building_2d_games/07_the_sprite_class/ + - name: "07: The AnimatedSprite Class" + href: tutorials/building_2d_games/08_the_animatedsprite_class/ - name: Console Access href: console_access.md - name: Help and Support diff --git a/articles/tutorials/building_2d_games/08_the_animatedsprite_class/images/bat-animation-example.gif b/articles/tutorials/building_2d_games/08_the_animatedsprite_class/images/bat-animation-example.gif new file mode 100644 index 0000000000000000000000000000000000000000..c23d07a3bc7af9ec84253ab66dc6e3bdf6b21f77 GIT binary patch literal 6567 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 Z&C`BEcxW1%h{B)$XS*m&KF+~m^dAjK7IOdq literal 0 HcmV?d00001 diff --git a/articles/tutorials/building_2d_games/08_the_animatedsprite_class/images/slime-bat-animated.gif b/articles/tutorials/building_2d_games/08_the_animatedsprite_class/images/slime-bat-animated.gif new file mode 100644 index 0000000000000000000000000000000000000000..5403616e8dc25dbbc3227a239147903e4fad0f76 GIT binary patch literal 13522 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 literal 0 HcmV?d00001 diff --git a/articles/tutorials/building_2d_games/08_the_animatedsprite_class/index.md b/articles/tutorials/building_2d_games/08_the_animatedsprite_class/index.md new file mode 100644 index 00000000..9eaf61e7 --- /dev/null +++ b/articles/tutorials/building_2d_games/08_the_animatedsprite_class/index.md @@ -0,0 +1,534 @@ +--- +title: "Chapter 08: The AnimatedSprite Class" +description: "Create an AnimatedSprite class that builds upon our Sprite class to support frame-based animations." +--- + +While packing images into a texture atlas and managing them through our `Sprite` class improves performance and organization, games need animation to bring their visuals to life. 2D animation in games works much like a flip book; a sequence of individual images (*frames*) displayed in rapid succession creates the illusion of movement. Each frame represents a specific point in the sprite's animation, and when these frames are cycled through quickly, our eyes perceive fluid motion. + +> [!NOTE] +> The term "frame" in animation refers to a single image in an animation sequence. This is different from a game frame, which represents one complete render cycle of your game. + +In MonoGame, we can create these animations by cycling through different regions of our texture atlas, with each region representing a single frame of the animation. For example, Figure 8-1 below shows three frames that make up a bat's wing-flapping animation: + +
Figure 8-1: Animation example of a bat flapping its wings.

Figure 8-1: Animation example of a bat flapping its wings.

+ +By drawing each frame sequentially over time, we create the illusion that the bat is flapping its wings. The speed at which we switch between frames determines how smooth or rapid the animation appears. + +In this chapter, we'll build off of the `Sprite` class we created in [Chapter 07](../07_the_sprite_class/) to create an `AnimatedSprite` class we can use to bring animations to life. + +## The Animation Class + +Before we can create animated sprites, we need a way to manage animation data. Let's create an `Animation` class to encapsulate this information. In the *Graphics* directory within the *MonoGameLibrary* project, add a new file named *Animation.cs* with this initial structure: + +```cs +using System; +using System.Collections.Generic; + +namespace MonoGameLibrary.Graphics; + +public class Animation +{ + +} +``` + +### Animation Properties + +An animation requires two key pieces of information: the sequence of frames to display and the timing between them. Let's add these properties to the `Animation` class: + +```cs +/// +/// The texture regions that make up the frames of this animation. The order of the regions within the collection +/// are the order that the frames should be displayed in. +/// +public List Frames {get; set;} + +/// +/// The amount of time to delay between each frame before moving to the next frame for this animation. +/// +public TimeSpan Delay {get; set;} +``` + +The `Frames` property stores the collection of texture regions that make up the animation sequence. The order of regions in this collection is important; they'll be displayed in the same sequence they're added, creating the animation's movement. For example, in our bat animation, the frames would be ordered to show the wings moving up, then fully extended, then down. + +The `Delay` property defines how long each frame should be displayed before moving to the next one. This timing control allows us to adjust the speed of our animations; a shorter delay creates faster animations, while a longer delay creates slower ones. + +> [!NOTE] +> Using `TimeSpan` for the delay allows us to specify precise timing intervals, making it easier to synchronize animations with game time. In other scenarios, you could opt to use `float` values instead. + +### Animation Constructors + +The `Animation` class will provide two ways to create an animation. Add the following constructors: + +```cs +/// +/// Creates a new animation. +/// +public Animation() +{ + Frames = new List(); + Delay = TimeSpan.FromMilliseconds(100); +} + +/// +/// Creates a new animation with the specified frames and delay. +/// +/// An ordered collection of the frames for this animation. +/// The amount of time to delay between each frame of this animation. +public Animation(List frames, TimeSpan delay) +{ + Frames = frames; + Delay = delay; +} +``` + +The default constructor creates an animation with an empty collection of frames and a default delay of 100 milliseconds between each frame. The parameterized constructor allows you to specify the frames of animation and the delay for the animation. + +> [!TIP] +> The default 100 milliseconds delay provides a good starting point for most animations, roughly equivalent to 10 animation frame changes per second. + +## Creating Animations With The TextureAtlas Class + +The `TextureAtlas` class we created in [Chapter 06](../06_optimizing_texture_rendering/index.md#the-textureatlas-class) can do more than just manage texture regions and create sprites; it can also store and manage animation data to create animated sprites with. The *atlas.png* image we are currently using contains the frames of animation for both a slime and a bat, as well as sprites for other things. Let's first update our *atlas-definition.xml* file to include all regions in the atlas, as well as add new `` elements to define the animations. Open the *atlas-definition.xml* file and replace the contents with the following: + +```xml + + + images/atlas + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +The key changes here are: + +- Regions have been added for all regions within the atlas. +- The slime and bat regions have been renamed to reflect the frame number of the animation. +- A new `` element has been added that defines `` elements. + +> [!NOTE] +> In the bat animation, we reuse frame "bat-1" in the sequence (bat-1, bat-2, bat-1, bat-3). This creates a smoother wing-flapping animation by returning to the neutral position between up and down wing positions. + +Now that we have a fully configured XML configuration for the atlas, we need to update the `TextureAtlas` class to manage animation data. Open the *TextureAtlas.cs* file and make the following changes: + +1. Add storage for animations + + ```cs + private Dictionary _animations; + ``` + +2. Update the constructors so that the animations dictionary is initialized: + + ```cs + /// + /// Creates a new texture atlas. + /// + public TextureAtlas() + { + _regions = new Dictionary(); + _animations = new Dictionary(); + } + + /// + /// Creates a new texture atlas instance using the given texture. + /// + /// The source texture represented by the texture atlas. + public TextureAtlas(Texture2D texture) + { + Texture = texture; + _regions = new Dictionary(); + _animations = new Dictionary(); + } + ``` + +3. Add methods to manage animations, similar to those that we use to manage regions: + + ```cs + /// + /// Adds the given animation to this texture atlas with the specified name. + /// + /// The name of the animation to add. + /// The animation to add. + public void AddAnimation(string animationName, Animation animation) + { + _animations.Add(animationName, animation); + } + + /// + /// Gets the animation from this texture atlas with the specified name. + /// + /// The name of the animation to retrieve. + /// The animation with the specified name. + public Animation GetAnimation(string animationName) + { + return _animations[animationName]; + } + + /// + /// Removes the animation with the specified name from this texture atlas. + /// + /// The name of the animation to remove. + /// true if the animation is removed successfully; otherwise, false. + public bool RemoveAnimation(string animationName) + { + return _animations.Remove(animationName); + } + ``` + +4. Update the `FromFile` method to parse the new `` animation definitions from the XML configuration file + + ```cs + /// + /// Creates a new texture atlas based a texture atlas xml configuration file. + /// + /// The content manager used to load the texture for the atlas. + /// The path to the xml file, relative to the content root directory.. + /// The texture atlas created by this method. + public static TextureAtlas FromFile(ContentManager content, string fileName) + { + TextureAtlas atlas = new TextureAtlas(); + + string filePath = Path.Combine(content.RootDirectory, fileName); + + using (Stream stream = TitleContainer.OpenStream(filePath)) + { + using (XmlReader reader = XmlReader.Create(stream)) + { + XDocument doc = XDocument.Load(reader); + XElement root = doc.Root; + + // The element contains the content path for the Texture2D to load. + // So we'll retrieve that value then use the content manager to load the texture. + string texturePath = root.Element("Texture").Value; + atlas.Texture = content.Load(texturePath); + + // The element contains individual elements, each one describing + // a different texture region within the atlas. + // + // Example: + // + // + // + // + // + // So we retrieve all of the elements then loop through each one + // and generate a new TextureRegion instance from it and add it to this atlas. + var regions = root.Element("Regions")?.Elements("Region"); + + if (regions != null) + { + foreach (var region in regions) + { + string name = region.Attribute("name")?.Value; + int x = int.Parse(region.Attribute("x")?.Value ?? "0"); + int y = int.Parse(region.Attribute("y")?.Value ?? "0"); + int width = int.Parse(region.Attribute("width")?.Value ?? "0"); + int height = int.Parse(region.Attribute("height")?.Value ?? "0"); + + if (!string.IsNullOrEmpty(name)) + { + atlas.AddRegion(name, x, y, width, height); + } + } + } + + // The element contains individual elements, each one describing + // a different animation within the atlas. + // + // Example: + // + // + // + // + // + // + // + // So we retrieve all of the elements then loop through each one + // and generate a new Animation instance from it and add it to this atlas. + var animationElements = root.Element("Animations").Elements("Animation"); + + if(animationElements != null) + { + foreach(var animationElement in animationElements) + { + string name = animationElement.Attribute("name")?.Value; + float delayInMilliseconds = float.Parse(animationElement.Attribute("delay")?.Value ?? "0"); + TimeSpan delay = TimeSpan.FromMilliseconds(delayInMilliseconds); + + List frames = new List(); + + var frameElements = animationElement.Elements("Frame"); + + if(frameElements != null) + { + foreach(var frameElement in frameElements) + { + string regionName = frameElement.Attribute("region").Value; + TextureRegion region = atlas.GetRegion(regionName); + frames.Add(region); + } + } + + Animation animation = new Animation(frames, delay); + atlas.AddAnimation(name, animation); + } + } + + return atlas; + } + } + } + ``` + +The updated `FromFile` method now handles both region and animation definitions from the XML configuration. For animations, it: + +- Reads the `` section from the XML. +- For each animation: + - Gets the name and frame delay. + - Collects the referenced texture regions. + - Creates and stores a new `Animation` instance. + +## The AnimatedSprite Class + +With our `Animation` class handling animation data, and the `TextureAtlas` updated to store the animation data, we can now create a class that represents an animated sprites. Since an animated sprite is essentially a sprite that changes its texture region over time, we can build upon our existing `Sprite` class through inheritance. + +> [!NOTE] +> By inheriting from `Sprite`, our `AnimatedSprite` class automatically gets all the rendering properties (position, rotation, scale, etc.) while adding animation-specific functionality. + +The key to this design is the `Sprite.Region` property. Our `Sprite` class already knows how to render whatever region is currently set, so our `AnimatedSprite` class just needs to update this region property to the correct animation frame at the right time. + +Let's create the initial structure for our `AnimatedSprite` class. In the *Graphics* directory within the *MonoGameLibrary* project, add a new file named *AnimatedSprite.cs*: + +```cs +using System; +using Microsoft.Xna.Framework; + +namespace MonoGameLibrary.Graphics; + +public class AnimatedSprite : Sprite +{ + +} +``` + +### AnimatedSprite Members + +An animated sprite needs to track both its current animation state and timing information. Let's add the following members to the `AnimatedSprite` class: + +```cs +private int _currentFrame; +private TimeSpan _elapsed; +private Animation _animation; + +/// +/// Gets or Sets the animation for this animated sprite. +/// +public Animation Animation +{ + get => _animation; + set + { + _animation = value; + Region = _animation.Frames[0]; + } +} +``` + +The class uses three private fields to manage its animation state: + +- `_currentFrame`: Tracks which frame of the animation is currently being displayed. +- `_elapsed`: Keeps track of how much time has passed since the last frame change. +- `_animation`: Stores the current animation being played. + +The `Animation` property provides access to the current animation while ensuring the sprite always starts with the first frame when a new animation is set. When you assign a new animation, the property's setter automatically updates the sprite's region to display the first frame of that animation. + +> [!NOTE] +> Starting with the first frame when setting a new animation ensures consistent behavior when switching between different animations. + +### AnimatedSprite Constructors + +The `AnimatedSprite` class will provide two ways to create an animated sprite. Add the following constructors: + +```cs +/// +/// Creates a new animated sprite. +/// +public AnimatedSprite() { } + +/// +/// Creates a new animated sprite with the specified frames and delay. +/// +/// The animation for this animated sprite. +public AnimatedSprite(Animation animation) +{ + Animation = animation; +} +``` + +The default constructor creates an empty animated sprite that can be configured later. The parameterized constructor creates an animated sprite with a specified animation, which automatically sets the sprite's initial region to the first frame of that animation through the `Animation` property. + +> [!NOTE] +> Both constructors inherit from the base `Sprite` class, so an `AnimatedSprite` will have all the same rendering properties (position, rotation, scale, etc.) as a regular sprite. + +### AnimatedSprite Methods + +The `AnimatedSprite` class needs a way to update its animation state over time. This is handled by a single `Update` method: + +```cs +/// +/// Updates this animated sprite. +/// +/// A snapshot of the game timing values provided by the framework. +public void Update(GameTime gameTime) +{ + _elapsed += gameTime.ElapsedGameTime; + + if (_elapsed >= _animation.Delay) + { + _elapsed -= _animation.Delay; + _currentFrame++; + + if (_currentFrame >= _animation.Frames.Count) + { + _currentFrame = 0; + } + + Region = _animation.Frames[_currentFrame]; + } +} +``` + +The `Update` method manages the animation timing and frame progression: + +1. Accumulates the time passed since the last update in `_elapsed`. +2. When enough time has passed (defined by the animation's delay): + - Resets the elapsed time counter + - Advances to the next frame + - Loops back to the first frame if we've reached the end + - Updates the sprite's region to display the current frame + +> [!NOTE] +> Unlike the `Sprite` class which only needs a `Draw` method, the `AnimatedSprite` requires this additional `Update` method to handle frame changes over time. This follows MonoGame's update/draw pattern we first saw in [Chapter 03](../03_the_game1_file/index.md) + +The `Draw` method inherited from the base `Sprite` class remains unchanged, as it will automatically use whatever frame is currently set as the sprite's region. + +## Creating AnimatedSprites With The TextureAtlas Class + +Similar to the update we did to the `TextureAtlas` class in [Chapter 07](../07_the_sprite_class/index.md#create-sprites-with-the-textureatlas-class), creating an `AnimatedSprite` from the atlas would require + +1. Get the animation by name. +2. Store it in a variable. +3. Create a new animated sprite with that animation. + +We can simplify this process by adding an animated spirte creation method to the `TextureAtlas` class. Open *TextureAtlas.cs* and add the following method: + +```cs +/// +/// Creates a new animated sprite using the animation from this texture atlas with the specified name. +/// +/// The name of the animation to use. +/// A new AnimatedSprite using the animation with the specified name. +public AnimatedSprite CreateAnimatedSprite(string animationName) +{ + Animation animation = GetAnimation(animationName); + return new AnimatedSprite(animation); +} +``` + +## Using the AnimatedSprite Class + +Let's adjust our game now to use the `AnimatedSprite` class to see our sprites come to life. Replaces the contents of *Game1.cs* with the following: + +[!code-csharp[](./src/Game1-animatedsprite-usage.cs?highlight=13-14,37-41,49-51)] + +Let's examine the key changes in this implementation: + +- The `_slime` and `_bat` members were changed from `Sprite` to `AnimatedSprite`. +- In [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent) the `_slime` and `_bat` sprites are now created using the new `TextureAtlas.CreateAnimatedSprite` method. +- In [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)), the animations are updated based on the game time using hte `AnimatedSprite.Update` method. + +Running the game now shows both sprites animating automatically: + +- The slime bounces between two frames +- The bat's wings flap in a continuous cycle + +
Figure 8-2: The slime and bat sprite animating..

Figure 8-2: The slime and bat sprite animating.

+ +## Conclusion + +Let's review what you accomplished in this chapter: + +- Created an `Animation` class to manage frame sequences and timing. +- Extended the `TextureAtlas` class to support animation definitions. +- Built an `AnimatedSprite` class that inherits from `Sprite`. +- Applied inheritance to add animation capabilities while maintaining existing sprite functionality. +- Used XML configuration to define animations separately from code. + +Now that we can efficiently manage and render sprites and animations, in the next chapter we'll start taking a look at user input. + +## Test Your Knowledge + +1. Why did we create a separate `Animation` class instead of putting animation properties directly in `AnimatedSprite`? + +
+ Question 1 Answer + + > Separating animation data into its own class allows multiple `AnimatedSprite` instances to share the same animation definition. This is more efficient than each sprite having its own copy of the frame sequence and timing information. +

+ +2. What is the benefit of using `TimeSpan` for animation delays instead of float values? + +
+ Question 2 Answer + + > `TimeSpan` provides precise timing control and makes it easier to synchronize animations with game time. It also makes the delay values more explicit (milliseconds vs arbitrary numbers) and helps prevent timing errors. +

+ +3. Why does the `AnimatedSprite` class need an `Update` method while the base `Sprite` class doesn't? + +
+ Question 3 Answer + + > The `AnimatedSprite` needs to track elapsed time and change frames based on the animation's timing. This requires updating its state over time, while a regular sprite's appearance remains static until explicitly changed. +

+ +4. In the `TextureAtlas` XML configuration, why might you want to reuse a frame in an animation sequence, like we did with the bat animation? + +
+ Question 4 Answer + + > Reusing frames in an animation sequence can create smoother animations by providing transition states. In the bat animation, reusing the neutral position (bat-1) between wing movements creates a more natural flapping motion without requiring additional sprite frames. +

\ No newline at end of file diff --git a/articles/tutorials/building_2d_games/08_the_animatedsprite_class/src/Game1-animatedsprite-usage.cs b/articles/tutorials/building_2d_games/08_the_animatedsprite_class/src/Game1-animatedsprite-usage.cs new file mode 100644 index 00000000..721c1901 --- /dev/null +++ b/articles/tutorials/building_2d_games/08_the_animatedsprite_class/src/Game1-animatedsprite-usage.cs @@ -0,0 +1,72 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using MonoGameLibrary.Graphics; + +namespace MonoGameSnake; + +public class Game1 : Game +{ + private GraphicsDeviceManager _graphics; + private SpriteBatch _spriteBatch; + private AnimatedSprite _slime; + private AnimatedSprite _bat; + + public Game1() + { + _graphics = new GraphicsDeviceManager(this); + Content.RootDirectory = "Content"; + IsMouseVisible = true; + } + + protected override void Initialize() + { + // TODO: Add your initialization logic here + + base.Initialize(); + } + + protected override void LoadContent() + { + _spriteBatch = new SpriteBatch(GraphicsDevice); + + // Create the texture atlas from the XML configuration file + TextureAtlas atlas = TextureAtlas.FromFile(Content, "images/atlas-definition.xml"); + + // Create the slime animated sprite + _slime = atlas.CreateAnimatedSprite("slime-animation"); + + // Create the bat animated sprite + _bat = atlas.CreateAnimatedSprite("bat-animation"); + } + + protected override void Update(GameTime gameTime) + { + if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) + Exit(); + + // Update the slime and bat animated sprites + _slime.Update(gameTime); + _bat.Update(gameTime); + + base.Update(gameTime); + } + + protected override void Draw(GameTime gameTime) + { + GraphicsDevice.Clear(Color.CornflowerBlue); + + _spriteBatch.Begin(samplerState: SamplerState.PointClamp); + + // Draw the slime animated sprite + _slime.Draw(_spriteBatch, Vector2.One); + + // Draw the bat animated sprite 10px to the right of the slime. + _bat.Draw(_spriteBatch, new Vector2(_slime.Width + 10, 0)); + + _spriteBatch.End(); + + base.Draw(gameTime); + } +} \ No newline at end of file diff --git a/articles/tutorials/building_2d_games/index.md b/articles/tutorials/building_2d_games/index.md index b9df9646..06165feb 100644 --- a/articles/tutorials/building_2d_games/index.md +++ b/articles/tutorials/building_2d_games/index.md @@ -28,6 +28,7 @@ This documentation will introduce game development concepts using the MonoGame f | [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). | | | [06: Optimizing Texture Rendering](06_optimizing_texture_rendering/index.md) | Explore optimization techniques when rendering textures using a texture atlas. | | | [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. | | +| [08: The AnimatedSprite Class](07_the_sprite_class/index.md) | Create an AnimatedSprite class that builds upon our Sprite class to support frame-based animations. | | 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: