diff --git a/articles/getting_started/5_adding_basic_code.md b/articles/getting_started/5_adding_basic_code.md
index 918385ba..b6fc27ed 100644
--- a/articles/getting_started/5_adding_basic_code.md
+++ b/articles/getting_started/5_adding_basic_code.md
@@ -319,6 +319,6 @@ We recommend browsing through the [Getting to know MonoGame](../getting_to_know/
## Further Reading
-Check out the [Tutorials section](../tutorials.md) for many more helpful guides and tutorials on building games with MonoGame. We have an expansive library of helpful content, all provided by other MonoGame developers in the community.
+Check out the [Tutorials section](../tutorials/index.md) for many more helpful guides and tutorials on building games with MonoGame. We have an expansive library of helpful content, all provided by other MonoGame developers in the community.
Additionally, be sure to check out the official [MonoGame Samples](../samples.md) page for fully built sample projects built with MonoGame and targeting our most common platforms.
diff --git a/articles/help_and_support.md b/articles/help_and_support.md
index b5a41498..d6d6ea25 100644
--- a/articles/help_and_support.md
+++ b/articles/help_and_support.md
@@ -5,7 +5,7 @@ description: Where to get help and support when using MonoGame.
# Help and Support
-There is a wealth of [community created content, blogs and tutorials](tutorials.md) available.
+There is a wealth of [community created content, blogs and tutorials](tutorials/index.md) available.
If you want to find an answer to a more specific problem, you can ask it on our [GitHub Discussions](https://github.com/MonoGame/MonoGame/discussions) page.
diff --git a/articles/toc.yml b/articles/toc.yml
index aa673967..3e1841d1 100644
--- a/articles/toc.yml
+++ b/articles/toc.yml
@@ -67,48 +67,106 @@ items:
- name: Getting to know MonoGame
href: getting_to_know/
items:
- - name: What is
- href: getting_to_know/whatis/
- items:
- - name: Audio
- href: getting_to_know/whatis/audio/
- - name: Content Pipeline
- href: getting_to_know/whatis/content_pipeline/
- - name: Graphics
- href: getting_to_know/whatis/graphics/
- - name: Input
- href: getting_to_know/whatis/input/
- - name: The Game Loop
- href: getting_to_know/whatis/game_loop/
- - name: Vector / Matrix / Quaternions
- href: getting_to_know/whatis/vector_matrix_quat/
- - name: MonoGame Class Library
- href: getting_to_know/whatis/monogame_class_library/
- - name: How to
- href: getting_to_know/howto/
- items:
- - name: Audio
- href: getting_to_know/howto/audio/
- - name: Content Pipeline
- href: getting_to_know/howto/content_pipeline/
- - name: Graphics
- href: getting_to_know/howto/graphics/
- - name: Input
- href: getting_to_know/howto/input/
+ - name: What is
+ href: getting_to_know/whatis/
+ items:
+ - name: Audio
+ href: getting_to_know/whatis/audio/
+ - name: Content Pipeline
+ href: getting_to_know/whatis/content_pipeline/
+ - name: Graphics
+ href: getting_to_know/whatis/graphics/
+ - name: Input
+ href: getting_to_know/whatis/input/
+ - name: The Game Loop
+ href: getting_to_know/whatis/game_loop/
+ - name: Vector / Matrix / Quaternions
+ href: getting_to_know/whatis/vector_matrix_quat/
+ - name: MonoGame Class Library
+ href: getting_to_know/whatis/monogame_class_library/
+ - name: How to
+ href: getting_to_know/howto/
+ items:
+ - name: Audio
+ href: getting_to_know/howto/audio/
+ - name: Content Pipeline
+ href: getting_to_know/howto/content_pipeline/
+ - name: Graphics
+ href: getting_to_know/howto/graphics/
+ - name: Input
+ href: getting_to_know/howto/input/
- name: Migration
items:
- - name: Migrating from XNA
- href: migration/migrate_xna.md
- - name: Migrating from 3.7
- href: migration/migrate_37.md
- - name: Migrating from 3.8.0
- href: migration/migrate_38.md
- - name: Updating Versions
- href: migration/updating_versions.md
+ - name: Migrating from XNA
+ href: migration/migrate_xna.md
+ - name: Migrating from 3.7
+ href: migration/migrate_37.md
+ - name: Migrating from 3.8.0
+ href: migration/migrate_38.md
+ - name: Updating Versions
+ href: migration/updating_versions.md
- name: Samples and Demos
href: samples.md
-- name: Community Tutorials
- href: tutorials.md
+- name: Tutorials
+ href: tutorials/
+ items:
+ - name: Building 2D Games
+ href: tutorials/building_2d_games/
+ items:
+ - name: "01: What Is MonoGame?"
+ href: tutorials/building_2d_games/01_what_is_monogame/
+ - name: "02: Getting Started"
+ href: tutorials/building_2d_games/02_getting_started/
+ - name: "03: The Game1 File"
+ href: tutorials/building_2d_games/03_the_game1_file/
+ - name: "04: Creating a Class Library"
+ href: tutorials/building_2d_games/04_creating_a_class_library/
+ - name: "05: Content Pipeline"
+ href: tutorials/building_2d_games/05_content_pipeline/
+ - name: "06: Working with Textures"
+ href: tutorials/building_2d_games/06_working_with_textures/
+ - name: "07: Optimizing Texture Rendering"
+ href: tutorials/building_2d_games/07_optimizing_texture_rendering/
+ - name: "08: The Sprite Class"
+ href: tutorials/building_2d_games/08_the_sprite_class/
+ - name: "09: The AnimatedSprite Class"
+ href: tutorials/building_2d_games/09_the_animatedsprite_class/
+ - name: "10: Handling Input"
+ href: tutorials/building_2d_games/10_handling_input/
+ - name: "11: Input Management"
+ href: tutorials/building_2d_games/11_input_management/
+ - name: "12: Collision Detection"
+ href: tutorials/building_2d_games/12_collision_detection/
+ - name: "13: Working With Tilemaps"
+ href: tutorials/building_2d_games/13_working_with_tilemaps/
+ - name: "14: Sound Effects and Music"
+ href: tutorials/building_2d_games/14_soundeffects_and_music/
+ - name: "15: Audio Controller"
+ href: tutorials/building_2d_games/15_audio_controller/
+ - name: "16: Working with SpriteFonts"
+ href: tutorials/building_2d_games/16_working_with_spritefonts/
+ - name: "17: Scenes"
+ href: tutorials/building_2d_games/17_scenes/
+ - name: "18: Texture Sampling"
+ href: tutorials/building_2d_games/18_texture_sampling/
+ - name: "19: User Interface Fundamentals"
+ href: tutorials/building_2d_games/19_user_interface_fundamentals/
+ - name: "20: Implementing UI with Gum"
+ href: tutorials/building_2d_games/20_implementing_ui_with_gum/
+ - name: "21: Customizing Gum UI"
+ href: tutorials/building_2d_games/21_customizing_gum_ui/
+ - name: "22: Snake Game Mechanics"
+ href: tutorials/building_2d_games/22_snake_game_mechanics/
+ - name: "23: Completing the Game"
+ href: tutorials/building_2d_games/23_completing_the_game/
+ - name: "24: Shaders"
+ href: tutorials/building_2d_games/24_shaders/
+ - name: "25: Packaging Your Game for Distribution"
+ href: tutorials/building_2d_games/25_packaging_game/
+ - name: "26: Publishing Your Game to itch.io"
+ href: tutorials/building_2d_games/26_publish_to_itch/
+ - name: "27: Conclusion and Next Steps"
+ href: tutorials/building_2d_games/27_conclusion/
- name: Console Access
href: console_access.md
- name: Help and Support
diff --git a/articles/tutorials/building_2d_games/01_what_is_monogame/images/bastion.jpg b/articles/tutorials/building_2d_games/01_what_is_monogame/images/bastion.jpg
new file mode 100644
index 00000000..316df14c
Binary files /dev/null and b/articles/tutorials/building_2d_games/01_what_is_monogame/images/bastion.jpg differ
diff --git a/articles/tutorials/building_2d_games/01_what_is_monogame/images/celeste.png b/articles/tutorials/building_2d_games/01_what_is_monogame/images/celeste.png
new file mode 100644
index 00000000..c9bd51d8
Binary files /dev/null and b/articles/tutorials/building_2d_games/01_what_is_monogame/images/celeste.png differ
diff --git a/articles/tutorials/building_2d_games/01_what_is_monogame/images/sor4.jpg b/articles/tutorials/building_2d_games/01_what_is_monogame/images/sor4.jpg
new file mode 100644
index 00000000..5f6a4710
Binary files /dev/null and b/articles/tutorials/building_2d_games/01_what_is_monogame/images/sor4.jpg differ
diff --git a/articles/tutorials/building_2d_games/01_what_is_monogame/images/stardew-valley.png b/articles/tutorials/building_2d_games/01_what_is_monogame/images/stardew-valley.png
new file mode 100644
index 00000000..e8be681e
Binary files /dev/null and b/articles/tutorials/building_2d_games/01_what_is_monogame/images/stardew-valley.png differ
diff --git a/articles/tutorials/building_2d_games/01_what_is_monogame/index.md b/articles/tutorials/building_2d_games/01_what_is_monogame/index.md
new file mode 100644
index 00000000..13bc416a
--- /dev/null
+++ b/articles/tutorials/building_2d_games/01_what_is_monogame/index.md
@@ -0,0 +1,87 @@
+---
+title: "Chapter 01: What is MonoGame"
+description: Learn about the history of MonoGame and explore the features it provides to developers when creating games.
+---
+
+## A Brief History
+
+In 2006, Microsoft released a game development framework named *[XNA Game Studio](https://learn.microsoft.com/en-us/previous-versions/windows/xna/bb203894(v=xnagamestudio.42))* to facilitate game development for Windows PC and the Xbox 360 console. It revolutionized game development for indie creators by bringing a simplified approach to building games and offering a set of tools that lowered the entry barrier for aspiring game developers. Out of XNA Game Studio came critically acclaimed titles such as [Bastion](https://www.supergiantgames.com/games/bastion/) and [Terraria](https://terraria.org/). In 2008, XNA was expanded to support development for both the Zune and Windows Phone.
+
+> [!NOTE]
+>
+> Fun fact, provided by community member [stromkos](https://github.com/stromkos), The release of XNA 3.0 in 2008, which added the support for Windows Phone, is also the release that specified the default window resolution of 800x480 for new projects as this was the preferred resolution on Windows Phone. [It is still the default resolution used in MonoGame projects today](https://github.com/MonoGame/MonoGame/blob/8b35cf50783777507cd6b21828ed0109b3b07b50/MonoGame.Framework/GraphicsDeviceManager.cs#L44).
+
+As XNA became more popular, the need for cross-platform development started to grow. In 2009, [José Antonio Leal de Farias](https://github.com/jalf) introduced *XNA Touch*, an open-source project that aimed to make games with XNA playable on iOS devices. This marked the beginning of what would later become MonoGame. [Dominique Louis](https://github.com/CartBlanche) came on board in 2009 and soon took over as full-time project lead, driving its initial development and expansion. The project attracted other developers, such as [Tom Spilman](https://github.com/tomspilman), who were interested in expanding the scope of the project as well as its reach.
+
+The official first release of MonoGame occurred in 2011, as an open-source version of XNA. While it still had the same familiar API as XNA, the cross-platform support was expanded to include Windows, macOS, Linux, iOS, Android, Xbox, and PlayStation. Despite Microsoft discontinuing XNA in 2013, MonoGame continued to grow and develop. Maintenance of the project was given to [Steve Williams](https://github.com/KonajuGames) and [Tom Spilman](https://github.com/tomspilman) in 2014. In order to direct its future development and undertaking, the [MonoGame Foundation](https://monogame.net/about/) was formed on September 29th, 2023.
+
+Today, MonoGame is a mature cross-platform framework, that is built with the spirit of preserving XNA while adopting modern game development practices. Some popular titles created using MonoGame includes [Celeste](https://store.steampowered.com/app/504230/Celeste/), [Stardew Valley](https://store.steampowered.com/app/413150/Stardew\_Valley/), and [Streets of Rage 4](https://store.steampowered.com/app/985890/Streets\_of\_Rage\_4/).
+
+|  |  |
+| :-------------------------------------------------: | :--------------------------------------------------------: |
+| **Figure 1-1 Celeste.** | **Figure 1-2: Stardew Valley** |
+|  | [Figure 1-4: Bastion](./images/bastion.jpg) |
+| **Figure 1-3: Streets of Rage 4** | **Figure 1-4: Bastion** |
+
+> [!NOTE]
+>
+> For more details about MonoGame's history, check the [About](https://monogame.net/about/) on the official MonoGame website.
+
+## Features
+
+MonoGame, following in the footsteps of XNA, is a "bring your own tools" framework. It provides developers the basic blocks to design the game, engines, and/or tools. As a code-first approach to game development, MonoGame does not include any pre-built editors or interfaces; instead, it gives developers the freedom to create their own working environment.
+
+### API
+
+At its core, MonoGame offers a set of libraries and APIs to handle common game development tasks. These include:
+
+1. **Graphics Rendering**: 2D and 3D rendering are supported through the graphics API offered by MonoGame. This API provides sprite batching for 2D graphics, a flexible 3D pipeline, and shaders for custom visuals and effects.
+2. **Input Handling**: Input from keyboard, mouse, gamepads, and touchscreens are supported, allowing for development of games for any platform and different styles of play.
+3. **Audio**: A comprehensive audio system that can be used to create sound effects as well as play music with included support for many audio formats.
+4. **Content Pipeline**: An out-of-the-box workflow for importing and processing game assets such as textures, models, and audio, and compiling them to a format that is optimal for the game's target platform.
+5. **Math Library**: A math library specifically optimized for game development, providing essential mathematical functions and operations.
+
+### Cross Platform
+
+One of the main advantages of MonoGame is its cross-platform support. Games built with MonoGame are compatible with a variety of platforms, including:
+
+* **Desktop**: Windows, macOS, and Linux.
+* **Mobile**: iOS and Android.
+* **Consoles** [(with appropriate license)](https://docs.monogame.net/articles/console\_access.html): Xbox, PlayStation, and Nintendo Switch.
+
+By providing cross-platform support, developers can target multiple platforms from a single code base, significantly reducing development time and resources needed for porting.
+
+### Programming Language Support
+
+MonoGame is designed and built in C#. It is the official programming language supported in documentation, samples, and community discussion. However, MonoGame is not exclusively tied to C#. As a .NET library, MonoGame can be used with any .NET-compatible language, including Visual Basic and F#.
+
+> [!CAUTION]
+> While the alternative .NET languages can be used, community support may be limited outside the scope of C#.
+
+Regardless of which .NET language is used, developers should have a foundational understanding of the language and programming concepts such as:
+
+* Object-oriented programming.
+* Data types and structures.
+* Control flow and loops.
+* Error handling and debugging.
+
+## See Also
+
+* [About MonoGame | MonoGame](https://monogame.net/about)
+
+## Test Your Knowledge
+
+1. Name one of the advantages of using the MonoGame framework to develop games.
+
+ :::question-answer
+ Any of the following are advantages of using the MonoGame framework.
+ 1. It provides cross-platform support, allowing developers to target multiple platforms from a single code base.
+ 2. It offers a set of libraries and APIs common for game development tasks, such as graphics rendering, input handling, audio, and content management.
+ 3. It is a "bring your own tools" framework, giving developers flexibility in their working environment.
+ :::
+
+2. What programming languages can be used when creating a game with MonoGame?
+
+ :::question-answer
+ The primary language used is C#, which is the same language that the MonoGame framework is developed in. However, any .NET language can be used, such as F# or Visual Basic.
+ :::
diff --git a/articles/tutorials/building_2d_games/02_getting_started/images/devkit-extension.png b/articles/tutorials/building_2d_games/02_getting_started/images/devkit-extension.png
new file mode 100644
index 00000000..586df141
Binary files /dev/null and b/articles/tutorials/building_2d_games/02_getting_started/images/devkit-extension.png differ
diff --git a/articles/tutorials/building_2d_games/02_getting_started/images/game-window.png b/articles/tutorials/building_2d_games/02_getting_started/images/game-window.png
new file mode 100644
index 00000000..1aef5ef4
Binary files /dev/null and b/articles/tutorials/building_2d_games/02_getting_started/images/game-window.png differ
diff --git a/articles/tutorials/building_2d_games/02_getting_started/images/vscode.png b/articles/tutorials/building_2d_games/02_getting_started/images/vscode.png
new file mode 100644
index 00000000..bc0b67c7
Binary files /dev/null and b/articles/tutorials/building_2d_games/02_getting_started/images/vscode.png differ
diff --git a/articles/tutorials/building_2d_games/02_getting_started/index.md b/articles/tutorials/building_2d_games/02_getting_started/index.md
new file mode 100644
index 00000000..5b04c73f
--- /dev/null
+++ b/articles/tutorials/building_2d_games/02_getting_started/index.md
@@ -0,0 +1,264 @@
+---
+title: "Chapter 02: Getting Started"
+description: Setup your development environment for .NET development and MonoGame using Visual Studio Code as your IDE.
+---
+
+Unlike game engines (such as Unity, Unreal or Godot), MonoGame is a *framework*. This means it does not come as a standalone program that you download and install, and does not include a graphical user interface used to create games. Instead, MonoGame integrates into the standard .NET development workflow, offering a code-first approach to game development. This approach offers several advantages:
+
+* **Flexibility**: Developers are not locked into using a specific editor or interface, allowing them to use their preferred development tools.
+* **Integration**: As a .NET library itself, MonoGame can easily integrate with other .NET libraries and tools.
+* **Cross-platform Development**: Since C# is cross-platform, and MonoGame is cross-platform, developers can develop MonoGame projects on Windows, macOS, or Linux, with only slight differences in the setup process for each operating system.
+* **Version Control Friendly**: The code-first approach makes it easier to use version control systems like Git for your game projects.
+
+While the environment setup process is similar to the standard setup process for C# development, there are some MonoGame specific steps. These can vary slightly depending on your operating system and the *Integrated Development Environment* (IDE).
+
+## Installing the .NET SDK
+
+The first thing we need to do is install the .NET *Software Development Kit* (SDK). To install it, follow the instructions based on your operating system below:
+
+> [!IMPORTANT]
+> As of MonoGame 3.8.2, the minimum supported version of the .NET SDK is .NET 8.
+
+### [Windows](#tab/windows)
+
+1. Open a web browser and navigate to [https://dotnet.microsoft.com/en-us/download](https://dotnet.microsoft.com/en-us/download).
+2. Choose the version of the .NET SDK to install and click the **Download .NET SDK x64** button to start the download.
+
+ > [!NOTE]
+ > The minimum supported version is .NET 8
+
+3. Once the download finishes, run the installer
+
+### [macOS](#tab/macos)
+
+1. Open a web browser and navigate to [https://dotnet.microsoft.com/en-us/download](https://dotnet.microsoft.com/en-us/download).
+2. Choose the version of the .NET SDK to install and click lick the *Download .NET SDK x64 (Intel)* button start the download of the .NET SDK Installer.
+
+ > [!NOTE]
+ > The minimum supported version is .NET 8
+
+3. Once the download finishes, run the installer.
+
+> [!NOTE]
+> For the time being, MonoGame requires that you install the **Intel** version even if you are using an Apple Silicon (M1/M2) Mac. For Apple Silicon Macs, it also requires that [Rosetta](https://support.apple.com/en-us/HT211861) be enabled.
+
+### [Linux](#tab/linux)
+
+1. Open a new *Terminal* window
+2. Enter the following command to install the .NET SDK
+
+```sh
+sudo apt-get update && sudo apt-get install -y dotnet-sdk-8.0
+```
+
+> [!NOTE]
+> The minimum supported version is .NET 8
+
+---
+
+## Install Additional Workloads (Optional)
+
+After installing the .NET SDK, if you intend to target mobile devices such as Android or iOS, you will also need to install the corresponding mobile workloads. To do this, open a *Command Prompt* or *Terminal* window and enter the following commands
+
+```sh
+dotnet workload install ios
+dotnet workload install android
+```
+
+## Install MonoGame Project Templates
+
+MonoGame provides project templates that can be installed to create new projects that are pre-configured to target the current version of MonoGame as a base to begin creating games. To install the MonoGame templates, open a *Command Prompt* or *Terminal* window and enter the following command
+
+```sh
+dotnet new install MonoGame.Templates.CSharp
+```
+
+## Installing Visual Studio Code
+
+*Visual Studio Code* (VSCode) is a free, light weight editor. Depending on the programming language you are using, it is just a matter of installing the appropriate extension to support that particular language. VSCode is also cross-platform, meaning you can use it for development on Windows, macOS, and Linux. To ensure that all readers can follow this tutorial regardless of the operating system used, we will be using VSCode as our IDE.
+
+To install VSCode, follow the instructions for your operating system below:
+
+### [Windows](#tab/windows)
+
+1. Open a browser and navigate to [https://code.visualstudio.com/](https://code.visualstudio.com/).
+2. Click the *Download for Windows* button to start the download of the installer.
+3. Once the download finishes, run the installer.
+
+### [macOS](#tab/macos)
+
+1. Open a web browser and navigate to [https://code.visualstudio.com/](https://code.visualstudio.com/).
+2. Click the *Download for macOS* button to start the download of the *.zip* archive.
+3. Once the download finishes, double click the *.zip* archive to extract the *Visual Studio Code.app* application package
+4. Drag-and-drop the *Visual Studio Code.app* application package into your *Application* folder to make it available in the macOS *LaunchPad*.
+
+### [Linux](#tab/linux)
+
+1. Open a web browser and navigate to [https://code.visualstudio.com/](https://code.visualstudio.com/).
+2. Click the *.deb* download button to download the package for Debian based Linux distributions, or the *.rpm* download button for Red Hat based Linux distributions.
+3. Once the download finishes, open the package downloaded to install.
+
+---
+
+## Install the C# Dev Kit Extension
+
+For C# development using VSCode, it is recommended to use the official *[C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit)* extension provided by Microsoft. Installing this extension will add additional features to VSCode such as a project system and *Solution Explorer* for C# projects. It also provides code editing features such as syntax highlighting, code completion, code navigation, refactoring, NuGet package management, and debugging tools.
+
+> [!NOTE]
+> The *Solution Explorer* panel is a hierarchical view provided by the C# Dev Kit extension that displays your solution structure similar to Visual Studio's Solution Explorer.
+>
+> When you open a workspace in Visual Studio Code containing a .NET solution file (*.sln*), the *Solution Explorer* panel automatically appears and loads your solution. From this panel, you can perform common operations like:
+>
+> * Adding new files.
+> * Managing project references.
+> * Viewing dependencies.
+> * Executing build commands.
+>
+> For more information about the *Solution Explorer* panel in Visual Studio Code offered through the C# Dev Kit extension, you can view the official documentation in the [Project Management](https://code.visualstudio.com/docs/csharp/project-management) documentation.
+
+To install the C# Dev Kit extension, perform the following:
+
+1. Launch the *Visual Studio Code* application.
+2. Open the *Extensions Panel* by clicking the icon in the *Activity Bar* on the left or choosing *View > Extensions* from the top menu.
+3. Enter `C#` in the *Search Box*
+4. Click install for the *C# Dev Kit* extension.
+
+|  |
+| :-------------------------------------------------------------------------------------------------: |
+| **Figure 2-1: The C# Dev Kit Extension listed in Visual Studio Code** |
+
+> [!NOTE]
+> When you search `C#` in the *Extension Panel* you may notice there is the C# Dev Kit extension and a base standard C# extension. When installing the C# Dev Kit extension, the base extension will also be installed as a requirement.
+
+## Installing the "MonoGame for VSCode" Extension
+
+Throughout this tutorial, we will be using the MonoGame Content Builder (MGCB) Editor to add content to the game. MonoGame offers an official extension for Visual Studio 2022 that allows you to double-click the *Content.mgcb* file to automatically open it in the MGCB Editor. While there is no official tool for VSCode, there is a an extension developed by community member r88 to provide similar functionality and is regularly used by the MonoGame developers themselves. We will be using that extension throughout this tutorial.
+
+To install it, with VSCode open:
+
+1. Open the *Extensions Panel* by clicking the icon in the *Activity Bar* on the left or choosing *View > Extensions* from the top menu.
+2. Enter `MonoGame for VSCode` in the *Search Box*
+3. Click install on the *MonoGame for VSCode* extension by r88.
+
+## Setup WINE for Effect Compilation (macOS and Linux Only)
+
+*Effect* (shader) compilation requires access to DirectX. This means it will not work natively on macOS and Linux systems, but it can be used through [WINE](https://www.winehq.org/). MonoGame provides a setup script that can be executed to setup the WINE environment. Below you can find the steps based on your operating system. To do this, follow the instructions for your operating system below:
+
+### [Windows](#tab/windows)
+
+> [!NOTE]
+> Setting up WINE for effect compilation is not required for Windows
+
+### [macOS](#tab/macos)
+
+Open a new *Terminal* window and execute the following commands:
+
+```sh
+brew install p7zip
+brew install --cask wine-stable
+wget -qO- https://monogame.net/downloads/net8_mgfxc_wine_setup.sh | bash
+```
+
+> [!NOTE]
+> After performing these steps, a new folder called *.winemonogame* will be created in your home folder. If you ever wish to undo the setup performed by this script, you can simply delete this folder.
+
+### [Linux](#tab/linux)
+
+Open a new *Terminal* window and execute the following commands:
+
+```sh
+sudo apt-get update && sudo apt-get install -y curl p7zip-full wine64
+wget -qO- https://monogame.net/downloads/net8_mgfxc_wine_setup.sh | bash
+```
+
+> [!NOTE]
+> After performing these steps, a new folder called *.winemonogame* will be created in your home folder. If you ever wish to undo the setup performed by this script, you can simply delete this folder.
+
+---
+
+## Creating Your First MonoGame Application
+
+With your development environment setup, it is time to create your first MonoGame application.
+
+1. Launch the VSCode application
+2. Open the *Command Palette* by clicking *View > Command Palette* or by using the keyboard shortcut `CTRL+SHIFT+P` (`CMD+SHIFT+P` on Mac).
+3. Type `.NET New Project` in the *Command Palette* and choose the *.NET New Project* command
+4. Next, you will be presented with a list of the available .NET project templates. Enter `MonoGame` into the prompt to filter the project templates to only show the MonoGame ones, then choose the *MonoGame Cross-Platform Desktop Application* project template.
+
+ > [!NOTE]
+ > If the MonoGame templates are not showing up, then you skipped the step to install the templates, exit the project creation and click `Terminal -> New Terminal` in VSCode and run the following command:
+ >
+ > ```sh
+ > dotnet new install MonoGame.Templates.CSharp
+ > ```
+
+5. After choosing the template, a dialog window will appear asking you to choose a location to save the project, this is a folder where your projects will will be created by default.
+6. Next, you will be prompted to enter a name for the project. Enter the name `DungeonSlime`, which will create your project in a new folder with the same name.
+7. If this is your first time creating your project, you will be asked to choose a solution format, simply select the default `.sln` option and click `Next` to continue. (This does not occur with subsequent projects)
+8. Finally, select the *Create Project* prompt.
+
+After selecting *Create Project*, a new C# project will be generated based on the chosen MonoGame template and opened automatically in VSCode.
+
+|  |
+| :--------------------------------------------------------------------------------------------------: |
+| **Figure 2-2: A new MonoGame project after being created in Visual Studio Code** |
+
+Now that we have the project created, press the `F5` key on your keyboard, or choose *Run > Start Debugging* from the top menu. If prompted for a configuration, choose *C#*. The project will compile and run, displaying a screen similar to the following:
+
+|  |
+| :---------------------------------------------------------------------------------------: |
+| **Figure 2-3: The default MonoGame cornflower blue game window** |
+
+Be amazed, the default MonoGame Cornflower Blue game window. You have just created your very first MonoGame application. While there is not much happening here visually, there is a log going on behind the scenes that the MonoGame framework is handling for you. When you ran the application, the following occurred:
+
+1. The application started
+2. The game window was created and graphics were initialized
+3. A loop is entered which performs the following over and over, until the game is told to exit:
+ 1. The game is updated
+ 2. The game is rendered to the window
+
+You can exit the game at any time by pressing the `Esc` key on your keyboard.
+
+> [!NOTE]
+> Above, I mentioned that a loop is entered. This is commonly referred to as the *game loop*, which we will discuss in more detail in the next chapter. The reason the application enters this loop is because game applications work differently than traditional desktop applications, such as your web browser.
+>
+> Desktop applications are event based, meaning once loaded, they do not do much at all while waiting for input from the user. They respond to user interactions and redraw the window only when necessary.
+>
+> In games, things are always happening, such as objects moving around like the player or particle effects. To handle this, games implement a loop structure that runs continuously, first calling a method to **`update`** the game logic, and then a **`draw`** method to render the current frame, until it is told to exit.
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+* You setup your operating system to develop .NET applications by installing the .NET SDK
+* You installed the MonoGame project templates.
+* You installed VSCode and the necessary extension to develop C# applications with VSCode
+* You created and ran your first MonoGame project.
+
+Now that your development environment is setup and ready to go, you can dive in and start building your first game. In the next chapter, we will cover the contents of the `Game1.cs` file that was included in the MonoGame project you just created.
+
+## Test Your Knowledge
+
+1. What are two advantages of MonoGame being a framework rather than an engine?
+
+ :::question-answer
+ Any two of the following:
+
+ * Flexibility: Developers can use their preferred development tools
+ * Integration: MonoGame easily integrates with other .NET libraries
+ * Cross-platform Development: Projects can be developed on Windows, macOS, or Linux
+ * Version Control Friendly: The code-first approach works well with systems like Git
+
+ :::
+
+2. What is the primary reason that game applications implement a *game loop* structure instead of using an event-based approach like traditional desktop applications?
+
+ :::question-answer
+ Game application implement a *game loop* structure because games need to continuously update and render, event when there is no user input. In games, objects might be moving, animations playing, and physics calculating regardless of user interaction, requiring constant updating and rendering until the game is told to exit.
+ :::
+
+3. What is the color of the game window when you run a MonoGame project for the first time?
+
+ :::question-answer
+ Cornflower Blue
+ :::
diff --git a/articles/tutorials/building_2d_games/03_the_game1_file/images/monogame-lifecycle.png b/articles/tutorials/building_2d_games/03_the_game1_file/images/monogame-lifecycle.png
new file mode 100644
index 00000000..0e748306
Binary files /dev/null and b/articles/tutorials/building_2d_games/03_the_game1_file/images/monogame-lifecycle.png differ
diff --git a/articles/tutorials/building_2d_games/03_the_game1_file/images/monogame-lifecycle.svg b/articles/tutorials/building_2d_games/03_the_game1_file/images/monogame-lifecycle.svg
new file mode 100644
index 00000000..ab108733
--- /dev/null
+++ b/articles/tutorials/building_2d_games/03_the_game1_file/images/monogame-lifecycle.svg
@@ -0,0 +1,465 @@
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/03_the_game1_file/images/monogame-orange.png b/articles/tutorials/building_2d_games/03_the_game1_file/images/monogame-orange.png
new file mode 100644
index 00000000..643ff341
Binary files /dev/null and b/articles/tutorials/building_2d_games/03_the_game1_file/images/monogame-orange.png differ
diff --git a/articles/tutorials/building_2d_games/03_the_game1_file/images/solitaire.webp b/articles/tutorials/building_2d_games/03_the_game1_file/images/solitaire.webp
new file mode 100644
index 00000000..3d32c291
Binary files /dev/null and b/articles/tutorials/building_2d_games/03_the_game1_file/images/solitaire.webp differ
diff --git a/articles/tutorials/building_2d_games/03_the_game1_file/index.md b/articles/tutorials/building_2d_games/03_the_game1_file/index.md
new file mode 100644
index 00000000..efbf095b
--- /dev/null
+++ b/articles/tutorials/building_2d_games/03_the_game1_file/index.md
@@ -0,0 +1,130 @@
+---
+title: "Chapter 03: The Game1 File"
+description: Explore the contents of the Game1 file generated when creating a new MonoGame project.
+---
+
+After you created a new MonoGame project using the *MonoGame Cross-Platform Desktop Application* template in [Chapter 02](../02_getting_started/index.md#creating-your-first-monogame-application), you will notice the generated files and project structure that serve as a starting point for your game application. While MonoGame offers different templates based on target platform, all projects will contain the `Game1.cs` file.
+
+## Exploring the Game1 Class
+
+At the core of a MonoGame project is the [**Game**](xref:Microsoft.Xna.Framework.Game) class. This class handles the initialization of graphics services, initialization of the game, loading content, updating, and rendering the game. When you create a new MonoGame project, this [**Game**](xref:Microsoft.Xna.Framework.Game) class is implemented as the `Game1` class that you can customize as needed for your specific game.
+
+> [!TIP]
+> While the default template names the class `Game1`, you are free to rename it to something more appropriate for your project. However, for consistency, the documentation will continue to refer to it as `Game1`.
+
+Locate the `Game1.cs` file that was generated when you created the MonoGame project and open it. The default content will be:
+
+[!code-csharp[](./snippets/game1.cs)]
+
+This class provides the following structure:
+
+1. **Graphics and Rendering**: The class declares two core graphics components; the [**GraphicsDeviceManager**](xref:Microsoft.Xna.Framework.GraphicsDeviceManager) for interacting with the Graphics Processing Unit (GPU) and the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) for 2D rendering.
+2. **Initialization**: The constructor and [**Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize) method handle the game's setup sequence.
+3. **Content Loading**: The [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent) method manages game asset loading during startup.
+4. **Game Loop**: The *game loop* consists of the [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)) method for game logic and the [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) method for rendering, running continuously until the game is told to exit.
+
+*Figure 3-1* below shows the lifecycle of a MonoGame game including the [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)) and [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) methods that make up the *game loop*.
+
+|  |
+| :--------------------------------------------------------------------------: |
+| **Figure 3-1: Lifecycle of a MonoGame game** |
+
+## Graphics and Rendering
+
+The graphics pipeline in monogame starts with two components: the [**GraphicsDeviceManager**](xref:Microsoft.Xna.Framework.GraphicsDeviceManager) and [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch).
+
+[!code-csharp[](./snippets/game1.cs?start=9&end=10)]
+
+The [**GraphicsDeviceManager**](xref:Microsoft.Xna.Framework.GraphicsDeviceManager) initializes and the connection to the graphics hardware. It handles tasks such as setting the screen resolution, toggling between fullscreen and windowed mode, and managing the [**GraphicsDevice**](xref:Microsoft.Xna.Framework.Graphics.GraphicsDevice), which is the interface between your game and the Graphics Processing Unit (GPU) the game is running on. The [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) optimizes 2D rendering by batching similar draw calls together, improving draw performance when rendering multiple sprites.
+
+## Initialization
+
+MonoGame's initialization process for your game follows a specific sequence. The constructor runs first, which handles basic setup like creating the [**GraphicsDeviceManager**](xref:Microsoft.Xna.Framework.GraphicsDeviceManager), setting the content directory, and the visibility of the mouse.
+
+[!code-csharp[](./snippets/game1.cs?start=12&end=17)]
+
+After that, the [**Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize) method executes, providing a dedicated place for additional configuration and initializations.
+
+[!code-csharp[](./snippets/game1.cs?start=19&end=22)]
+
+This separation allows you to perform setup tasks in a logical order; core systems in the constructor and game-specific initializations in the [**Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize) method. The call to `base.Initialize()` should never be removed, as this is where the graphics device is initialized for the target platform.
+
+> [!TIP]
+> You may be wondering why there is an [**Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize) method instead of performing all initializations in the constructor. The [**Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize) method is a `virtual` method that is overridden, and [it is advised to not call overridable methods from within a constructor](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2214), as this can lead to unexpected states in object constructor when called. Additionally, when the constructor is called, the base constructor will instantiate properties and services based on the target platform that may be needed first before performing initializations for the game itself.
+
+## Content Loading
+
+The [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent) method serves as the place for asset management. Here you can load textures, sound effects, music, and other game assets. We will cover loading assets in the coming chapters as we discuss each asset type that can be loaded. In a new project, the only task it performs is initializing a new instance of the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch).
+
+[!code-csharp[](./snippets/game1.cs?start=24&end=27)]
+
+This method is only call once during the startup of the game, but *when* it is called can be a little confusing at first. In the [**Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize) method shown above, when the `base.Initialize` call is executed, the final task it performs is calling the [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent) method. This means any initializations you need to perform that have a dependency on assets being loaded should be done *after* the `base.Initialize` call and not *before* it.
+
+## The Game Loop
+
+MonoGame implements a *game loop* by calling [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)) and [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) over and over until the game is told to exit. Recall at the end of [Chapter 02](../02_getting_started/index.md#creating-your-first-monogame-application) when you ran the project for the first time, I mentioned that there is a lot going on behind the scenes? This game loop is what I was referring to.
+
+MonoGame is executing the [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)) method and then the [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) method 60 times per second.
+
+[!code-csharp[](./snippets/game1.cs?start=29&end=42)]
+
+The [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)) method at the moment is not doing much, only checking for input from a controller or keyboard to determine if the game should exit. However, the [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) method is doing more than what it appears to at first glance.
+
+The first line is executing the [**Clear**](xref:Microsoft.Xna.Framework.Graphics.GraphicsDevice.Clear(Microsoft.Xna.Framework.Color)) method of the [**GraphicsDevice**](xref:Microsoft.Xna.Framework.Graphics.GraphicsDevice) property using the color [**CornflowerBlue**](xref:Microsoft.Xna.Framework.Color.CornflowerBlue). Recall that the [**GraphicsDevice**](xref:Microsoft.Xna.Framework.Graphics.GraphicsDevice) object is your direct interface between the game and what is rendered to the screen. Every time the [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) method is called, this line of code of erasing the contents of the game window and refilling it with the color specified. Without clearing the contents of the screen first, every draw call would draw the new frame render over top of the previous render, and you'd end up with something like the old solitaire win screen
+
+|  |
+| :---------------------------------------------------------------------: |
+| **Figure 3-2: Windows XP Solitaire Win Screen** |
+
+While this can make for a neat effect, it is not something you want all the time. So, the screen is cleared and refilled with a solid color.
+
+> [!NOTE]
+> You can test this yourself by modifying the code to use a different color, such as [**Color.MonoGameOrange**](xref:Microsoft.Xna.Framework.Color.MonoGameOrange). (yes, there is a MonoGame Orange color).
+>
+> [!code-csharp[](./snippets/draw.cs?highlight=3)]
+>
+> After making this change and running the game, the screen is cleared to the MonoGame Orange color.
+>
+> |  |
+> | :---: |
+> | **Figure 3-3: The game window clearing the screen using the MonoGame Orange color** |
+
+Each time the game loops completes and the game is drawn to the screen, we call this a *frame*. So if MonoGame is running the game loop at 60 frames per second, that means it is performing and update and a render of each frame every 16ms. Notice that both the [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)) and the [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) methods both receive a parameter of the type [**GameTime**](xref:Microsoft.Xna.Framework.GameTime). The [**GameTime**](xref:Microsoft.Xna.Framework.GameTime) parameter provides a snapshot of the timing values for the game, including the amount of time that it took for the previous frame to execute. This is commonly referred to as the *delta time*.
+
+*Delta time* allows you to track time accurately for things such as animations and events based on *game time* and not the speed of the processor (CPU) on the machine running the game. While in ideal circumstances, the delta time will always be 16ms, there are any number of things that could cause a temporary slow down or hiccup in a frame, and using the delta time ensures that timing based events are always correct.
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+- You read through the default code provided in a `Game1.cs` file created by a MonoGame template.
+- You learned about the lifecycle of a MonoGame game project.
+- You learned what a game loop is and how it is implemented in MonoGame.
+
+In the next chapter, you will start working with sprites and learn how to load and render them.
+
+## Test Your Knowledge
+
+1. Can the `Game1` class be renamed or is it required to be called `Game1`
+
+ :::question-answer
+ It is not a requirement that it be called `Game1`. This is just the default name given to it by the templates when creating a new MonoGame game project. However, you cannot change the name of the *BASE* class `Game`, as this is a MonoGame construct.
+ :::
+
+2. What is the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) used for?
+
+ :::question-answer
+ The [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) provides an optimized method of rendering 2D graphics, like sprites, onto the screen
+ :::
+
+3. When is the [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent) method executed and why is it important to know this?
+
+ :::question-answer
+ [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent) is executed during the `base.Initialize()` method call within the [**Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize) method. It is important to know this because anything being initialized that is dependent on content loaded should be done **after** the `base.Initialize()` call and not **before**.
+ :::
+
+4. How does MonoGame provide a *delta time* value?
+
+ :::question-answer
+ Through the [**GameTime**](xref:Microsoft.Xna.Framework.GameTime) parameter that is given to both the [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)) and the [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) methods.
+ :::
diff --git a/articles/tutorials/building_2d_games/03_the_game1_file/snippets/draw.cs b/articles/tutorials/building_2d_games/03_the_game1_file/snippets/draw.cs
new file mode 100644
index 00000000..56688f7e
--- /dev/null
+++ b/articles/tutorials/building_2d_games/03_the_game1_file/snippets/draw.cs
@@ -0,0 +1,6 @@
+protected override void Draw(GameTime gameTime)
+{
+ GraphicsDevice.Clear(Color.MonoGameOrange);
+
+ base.Draw(gameTime);
+}
diff --git a/articles/tutorials/building_2d_games/03_the_game1_file/snippets/game1.cs b/articles/tutorials/building_2d_games/03_the_game1_file/snippets/game1.cs
new file mode 100644
index 00000000..da77cbe2
--- /dev/null
+++ b/articles/tutorials/building_2d_games/03_the_game1_file/snippets/game1.cs
@@ -0,0 +1,43 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+namespace DungeonSlime;
+
+public class Game1 : Game
+{
+ private GraphicsDeviceManager _graphics;
+ private SpriteBatch _spriteBatch;
+
+ public Game1()
+ {
+ _graphics = new GraphicsDeviceManager(this);
+ Content.RootDirectory = "Content";
+ IsMouseVisible = true;
+ }
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+ }
+
+ protected override void LoadContent()
+ {
+ _spriteBatch = new SpriteBatch(GraphicsDevice);
+ }
+
+ 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)
+ {
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ base.Draw(gameTime);
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/04_creating_a_class_library/images/game-window.png b/articles/tutorials/building_2d_games/04_creating_a_class_library/images/game-window.png
new file mode 100644
index 00000000..02ea4eee
Binary files /dev/null and b/articles/tutorials/building_2d_games/04_creating_a_class_library/images/game-window.png differ
diff --git a/articles/tutorials/building_2d_games/04_creating_a_class_library/images/with-class-library-diagram.svg b/articles/tutorials/building_2d_games/04_creating_a_class_library/images/with-class-library-diagram.svg
new file mode 100644
index 00000000..8d0027d7
--- /dev/null
+++ b/articles/tutorials/building_2d_games/04_creating_a_class_library/images/with-class-library-diagram.svg
@@ -0,0 +1 @@
+
diff --git a/articles/tutorials/building_2d_games/04_creating_a_class_library/images/without-class-library-diagram.svg b/articles/tutorials/building_2d_games/04_creating_a_class_library/images/without-class-library-diagram.svg
new file mode 100644
index 00000000..5bc20460
--- /dev/null
+++ b/articles/tutorials/building_2d_games/04_creating_a_class_library/images/without-class-library-diagram.svg
@@ -0,0 +1 @@
+
diff --git a/articles/tutorials/building_2d_games/04_creating_a_class_library/index.md b/articles/tutorials/building_2d_games/04_creating_a_class_library/index.md
new file mode 100644
index 00000000..d6dd00c7
--- /dev/null
+++ b/articles/tutorials/building_2d_games/04_creating_a_class_library/index.md
@@ -0,0 +1,226 @@
+---
+title: "04: Creating a Class Library"
+description: "Learn how to create and structure a reusable MonoGame class library to organize game components and share code between projects."
+---
+
+One of the goals of this tutorial is to create reusable modules that you can use to jump start your next game project after this. Rather than starting from scratch each time, we will build a collection of game components you can take with you from project to project.
+
+In this chapter you will:
+
+- Learn about class libraries and their benefits for game development.
+- Create a MonoGame class library project using templates.
+- Add library references to your game project.
+- Structure your library for reusability.
+- Set up the foundation for creating shared game components.
+
+## What Is a Class Library
+
+Think of a class library like a toolbox for your game development. Just as a mechanic keeps their most-used tools in a toolbox they bring to every job, a class library stores code components you will want to use in multiple game projects. Instead of recreating these tools for each new game (or copying and pasting code), you organize them in one place where they are easy to find, use, and improve over time.
+
+The following diagrams show how this works:
+
+|  |
+| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 4-1: Diagram displays the block for Game 1 on the left and Game 2 on the right. In this example, when not using a class library, code for input, physics and audio systems are duplicated between both game projects. If a bug is fixed in one system, the effort has to be duplicated in the same system in other game projects** |
+
+|  |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 4-2: Diagram displays a block for a class library which contains common modules at the top, which are then shared between the two game projects below. If a bug is found in a module, fixing the bug will fix it across all game projects that use the class library** |
+
+> [!NOTE]
+> A class library is a project type that compiles into a [Dynamic Link Library](https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-libraries) (DLL) instead of an executable. It contains reusable code that can be referenced by other projects, making it perfect for sharing common functionality across multiple games.
+
+## Why Create a Class Library?
+
+Creating a class library offers several important advantages, especially as your games grow more complex:
+
+1. **Reusability**: Instead of rewriting the same code for each new game project, you build it once in your library and reuse it everywhere. This is like creating a multi-tool that works across all your projects.
+2. **Organization**: Your game code stays focused on the unique aspects of each game, while common functionality lives in the library. This keeps your project folder neat and makes code easier to find.
+3. **Maintainability**: When you improve or fix a bug in your library code, all games using that library benefit automatically. This means fixing one bug once instead of in multiple places.
+4. **Testing**: You can test your library code independently from any specific game. This helps ensure your core systems are solid before you build a game on top of them.
+
+As your library grows, you will accumulate a personal collection of well-tested modules that make starting new projects much faster. The modules we will create in this library will handle common game tasks like input, audio, sprites, and animations.
+
+## Adding the Class Library
+
+MonoGame offers the *MonoGame Game Library* project template to add a new class library project that is configured with the correct monoGame framework references. Using this template saves time and ensures compatibility with MonoGame projects.
+
+To use the template to add the class library, perform the following based on which development environment you are using:
+
+### [Visual Studio Code](#tab/vscode)
+
+To add the class library using the MonoGame Game Library project template in Visual Studio Code, perform the following:
+
+1. In the [*Solution Explorer*](../02_getting_started/index.md#install-the-c-dev-kit-extension) panel, right-click the *DungeonSlime* solution.
+2. Chose *New Project* from the context menu.
+3. Enter "MonoGame Game Library" and select it as the template to use.
+4. Name the project "MonoGameLibrary".
+5. When prompted for a location, use the default option, which will put the new project in a folder next to your game project.
+6. Select "Create Project".
+
+### [Visual Studio 2022](#tab/vs2022)
+
+To add the class library using the MonoGame Game Library project template in Visual Studio 2022, perform the following:
+
+1. Right-click the *DungeonSlime* solution in the Solution Explorer panel.
+2. Choose Add > New Project from the context menu.
+3. Enter "MonoGame Game Library" in the search box, select that template, then click Next.
+4. Name the project "MonoGameLibrary".
+5. The location by default will put the new project in a folder next to your game project; you do not need to adjust this.
+6. Click "Create".
+
+### [dotnet CLI](#tab/dotnetcli)
+
+To add the class library using the MonoGame Game Library project template with the dotnet CLI, perform the following:
+
+1. Open a new Command Prompt or Terminal window in the same folder as the *DungeonSlime.sln* solution file.
+2. Enter the command `dotnet new mglib -n MonoGameLibrary` to create the project, placing it in a folder next to your game project.
+3. Enter the command `dotnet sln DungeonSlime.sln add ./MonoGameLibrary/MonoGameLibrary.csproj` to add the newly created class library project to the *DungeonSlime.sln* solution file.
+
+---
+
+## Adding a Reference To The Class Library
+
+Now that the game library project has been created, a reference to it needs to be added in our game project. Without adding a reference, our game project will be unaware of anything we add to the class library. To do this, perform the following based on which development environment you are using:
+
+### [Visual Studio Code](#tab/vscode)
+
+To add the game library project as a reference to the game project in Visual Studio Code:
+
+1. In the Solution Explorer panel, right-click the *DungeonSlime* project.
+2. Choose "Add Project Reference" from the context menu.
+3. Choose *MonoGameLibrary" from the available options.
+
+> [!TIP]
+> The Solution Explorer panel in VSCode is provided by the C# Dev Kit extension that was installed in [Chapter 02](../02_getting_started/index.md#install-the-c-dev-kit-extension). If you do not see this panel, you can open it by
+>
+> 1. Opening the *Command Palette* (View > Command Palette).
+> 2. Enter "Explorer: Focus on Solution Explorer View" and select the command.
+
+### [Visual Studio 2022](#tab/vs2022)
+
+To add the game library project as a reference to the game project in Visual Studio 2022:
+
+1. In the Solution Explorer panel, right-click the *DungeonSlime* project.
+2. Select Add > Project Reference from the context menu.
+3. Check the box for the *MonoGameLibrary* project.
+4. Click Ok.
+
+### [dotnet CLI](#tab/dotnetcli)
+
+To add the game library project as a reference to the game project with the dotnet CLI:
+
+1. Open a new Command Prompt or Terminal window in the same folder as the *DungeonSlime.csproj* C# project file.
+2. Enter the command `dotnet add ./DungeonSlime.csproj reference ../MonoGameLibrary/MonoGameLibrary.csproj`. This will add the *MonoGameLibrary* reference to the *DungeonSlime* game project.
+
+---
+
+### Clean Up
+
+When using the *MonoGame Game Library* project template, the generated project contains file similar to a standard MonoGame game project, including a *dotnet-tools.json* manifest file, a *Content.mgcb* file, and a `Game1.cs` file. For the purposes of this tutorial, we will not need these. To clean these up, locate the following in the *MonoGameLibrary* project folder and delete them:
+
+1. The *.config/* folder.
+2. The *Content/* folder
+3. The `Game1.cs` file.
+
+> [!TIP]
+> These files are needed in more advanced scenarios such as creating a central code base for game logic that is referenced by other projects of which each target different platforms such as desktop, mobile, and console. Creating a project structure of this type is out of scope for this tutorial.
+>
+> If you would like more information on this, Simon Jackson has written the article [Going cross-platform with MonoGame](https://darkgenesis.zenithmoon.com/going-cross-platform-with-monogame.html) which covers this in more detail.
+>
+> Also the `2D Start Kit` and `2D Blank Start Kit` templates provide you with a richer startup project targetting all platforms, using a common Class Library to reuse code across them all. Although we recommend completing this tutorial first before tackling that beast.
+
+## Creating Our First Library Module
+
+We will create a class for our library called `Core`. This class will extend the MonoGame [**Game**](xref:Microsoft.Xna.Framework.Game) class and provide a starting point for game development with some common functionality built in. Creating this will also let us validate that our class library reference setup was correct.
+
+Create a new file called `Core.cs` in the *MonoGameLibrary* project and add the following code:
+
+[!code-csharp[](./snippets/core.cs)]
+
+The `Core` class provides the following features
+
+1. It extends the MonoGame [**Game**](xref:Microsoft.Xna.Framework.Game) class, so it inherits all of the base functionality.
+2. It implements a [singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) through the `Instance` property, ensure only one core exists.
+3. It provides static access to the graphics device manager, the graphics device, the sprite batch, and the content manager.
+4. It simplifies the game window setup with a constructor that handles common initializations.
+
+> [!NOTE]
+> The `new` keyword in the property declaration `public static new GraphicsDevice GraphicsDevice` and `public static new ContentManager Content` is used to intentionally hide (or "shadow") the inherited `GraphicsDevice` and `Content` properties from the base `Game` class. This creates new properties with the same name but different accessibility (static vs. instance) in the derived class.
+>
+> When you access `Core.GraphicsDevice` or `Core.Content` you will be using this static properties, while `base.GraphicsDevice` or `base.Content` within instance methods of the `Core` class would still access the original property. This pattern allows us to provide convenient static access to the graphics device and content manager throughout our game without having to reference the Core instance every time.
+
+This approach provides a consistent foundation for all our games, handling common setup tasks and providing convenient access to core functionality.
+
+> [!NOTE]
+> As this tutorial progress, we will be coming back to this `Core` class to add more to it.
+
+## Updating Our Game to Use the Core Class
+
+Now that we have our `Core` class, we can modify our game project to use it. Doing this will also help ensure that the project references were setup correctly.
+
+Open the `Game1.cs` file and make the following changes:
+
+[!code-csharp[](./snippets/game1.cs?highlight=4,8,10,22-25)]
+
+The key changes made here are:
+
+1. Adding `using MonoGameLibrary;` directive to reference our library.
+1. Removed the [**GraphicsDeviceManager**](xref:Microsoft.Xna.Framework.GraphicsDeviceManager) and [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) fields, these are now supplied through the `Core` class.
+1. Changed `Game1` class to inherit from `Core` instead of `Game`.
+1. Updated the constructor to call the `Core` base constructor with our game configuration.
+
+Running the game now will show the same window as before, only now it is at a 1280x720 resolution as per the configuration and it is using the `Core` class from our library. This may not seem like a big change visually, but it demonstrates how our library can simplify and standardize game initializations.
+
+> [!NOTE]
+> If you get any additional prompts when starting the project, simply accept the defaults. Adding the Game Library gives the compiler more projects to choose from when starting the project, however, we can only "run" the Game Project, libraries are not executables.
+
+|  |
+| :-----------------------------------------------------------------------------------------------: |
+| **Figure 4-3: The game window at 1280x720 with the title Dungeon Slime** |
+
+> [!IMPORTANT]
+> If you receive an error stating that the following:
+>
+> *The type or namespace name 'Core' could not be found (are you missing a using directive or an assembly reference?)*
+>
+> This means either you forgot to add the `using MonoGameLibrary;` using directive to the top of the `Game1.cs` class file, or you did not add the project reference correctly. Ensure that the project reference was added correctly by revisiting the [Add a Reference to the Class Library](#adding-a-reference-to-the-class-library) section above and that you added the using directive.
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+- Learned about class libraries and their advantages for game development:
+ - Code reusability across projects
+ - Better organization and separation of concerns
+ - Improved maintainability
+ - Easier testing
+- Created a MonoGame class library project
+- Added the library as a reference to your game project
+- Created your first reusable component and referenced and used it in the game project.
+
+In the next chapter, we will learn about the Content Pipeline and how to load game assets.
+
+## Test Your Knowledge
+
+1. What are the main benefits of using a class library for game development?
+
+ :::question-answer
+ The main benefits are:
+ - **Reusability**: Code can be easily shared between different game projects
+ - **Organization**: Separates reusable code from game-specific code
+ - **Maintainability**: Changes to shared code benefit all games using the library
+ - **Testing**: Library code can be tested independently of specific games
+ :::
+
+2. Why should you use the MonoGame Game Library template instead of a standard class library template?
+
+ :::question-answer
+ The MonoGame Game Library template automatically configures the correct MonoGame framework references and ensures compatibility with MonoGame projects, saving time and preventing potential setup issues.
+ :::
+
+3. What happens if you do not add a reference to your class library in your game project?
+
+ :::question-answer
+ > Without adding a reference, your game project will be unaware of any code in the class library. You wo not be able to use any of the classes or components from the library in your game.
+ :::
diff --git a/articles/tutorials/building_2d_games/04_creating_a_class_library/snippets/core.cs b/articles/tutorials/building_2d_games/04_creating_a_class_library/snippets/core.cs
new file mode 100644
index 00000000..581bd849
--- /dev/null
+++ b/articles/tutorials/building_2d_games/04_creating_a_class_library/snippets/core.cs
@@ -0,0 +1,91 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace MonoGameLibrary;
+
+public class Core : Game
+{
+ internal static Core s_instance;
+
+ ///
+ /// Gets a reference to the Core instance.
+ ///
+ public static Core Instance => s_instance;
+
+ ///
+ /// Gets the graphics device manager to control the presentation of graphics.
+ ///
+ public static GraphicsDeviceManager Graphics { get; private set; }
+
+ ///
+ /// Gets the graphics device used to create graphical resources and perform primitive rendering.
+ ///
+ public static new GraphicsDevice GraphicsDevice { get; private set; }
+
+ ///
+ /// Gets the sprite batch used for all 2D rendering.
+ ///
+ public static SpriteBatch SpriteBatch { get; private set; }
+
+ ///
+ /// Gets the content manager used to load global assets.
+ ///
+ public static new ContentManager Content { get; private set; }
+
+ ///
+ /// Creates a new Core instance.
+ ///
+ /// The title to display in the title bar of the game window.
+ /// The initial width, in pixels, of the game window.
+ /// The initial height, in pixels, of the game window.
+ /// Indicates if the game should start in fullscreen mode.
+ public Core(string title, int width, int height, bool fullScreen)
+ {
+ // Ensure that multiple cores are not created.
+ if (s_instance != null)
+ {
+ throw new InvalidOperationException($"Only a single Core instance can be created");
+ }
+
+ // Store reference to engine for global member access.
+ s_instance = this;
+
+ // Create a new graphics device manager.
+ Graphics = new GraphicsDeviceManager(this);
+
+ // Set the graphics defaults
+ Graphics.PreferredBackBufferWidth = width;
+ Graphics.PreferredBackBufferHeight = height;
+ Graphics.IsFullScreen = fullScreen;
+
+ // Apply the graphic presentation changes
+ Graphics.ApplyChanges();
+
+ // Set the window title
+ Window.Title = title;
+
+ // Set the core's content manager to a reference of hte base Game's
+ // content manager.
+ Content = base.Content;
+
+ // Set the root directory for content
+ Content.RootDirectory = "Content";
+
+ // Mouse is visible by default
+ IsMouseVisible = true;
+ }
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+
+ // Set the core's graphics device to a reference of the base Game's
+ // graphics device.
+ GraphicsDevice = base.GraphicsDevice;
+
+ // Create the sprite batch instance.
+ SpriteBatch = new SpriteBatch(GraphicsDevice);
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/04_creating_a_class_library/snippets/game1.cs b/articles/tutorials/building_2d_games/04_creating_a_class_library/snippets/game1.cs
new file mode 100644
index 00000000..a6bc512e
--- /dev/null
+++ b/articles/tutorials/building_2d_games/04_creating_a_class_library/snippets/game1.cs
@@ -0,0 +1,47 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary;
+
+namespace DungeonSlime;
+
+public class Game1 : Core
+{
+ public Game1() : base("Dungeon Slime", 1280, 720, false)
+ {
+
+ }
+
+ protected override void Initialize()
+ {
+ // TODO: Add your initialization logic here
+
+ base.Initialize();
+ }
+
+ protected override void LoadContent()
+ {
+ // TODO: use this.Content to load your game content here
+
+ base.LoadContent();
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
+ Exit();
+
+ // TODO: Add your update logic here
+
+ base.Update(gameTime);
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // TODO: Add your drawing code here
+
+ base.Draw(gameTime);
+ }
+}
diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/images/add-file-popup.png b/articles/tutorials/building_2d_games/05_content_pipeline/images/add-file-popup.png
new file mode 100644
index 00000000..f5202a74
Binary files /dev/null and b/articles/tutorials/building_2d_games/05_content_pipeline/images/add-file-popup.png differ
diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/images/content-pipeline-workflow-full.png b/articles/tutorials/building_2d_games/05_content_pipeline/images/content-pipeline-workflow-full.png
new file mode 100644
index 00000000..4be30284
Binary files /dev/null and b/articles/tutorials/building_2d_games/05_content_pipeline/images/content-pipeline-workflow-full.png differ
diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/images/content-pipeline-workflow-full.svg b/articles/tutorials/building_2d_games/05_content_pipeline/images/content-pipeline-workflow-full.svg
new file mode 100644
index 00000000..5ea48230
--- /dev/null
+++ b/articles/tutorials/building_2d_games/05_content_pipeline/images/content-pipeline-workflow-full.svg
@@ -0,0 +1,773 @@
+
+
diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/images/logo-drawn.png b/articles/tutorials/building_2d_games/05_content_pipeline/images/logo-drawn.png
new file mode 100644
index 00000000..3e330c62
Binary files /dev/null and b/articles/tutorials/building_2d_games/05_content_pipeline/images/logo-drawn.png differ
diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/images/logo.png b/articles/tutorials/building_2d_games/05_content_pipeline/images/logo.png
new file mode 100644
index 00000000..1509036c
Binary files /dev/null and b/articles/tutorials/building_2d_games/05_content_pipeline/images/logo.png differ
diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/images/mgcb-editor-icon.png b/articles/tutorials/building_2d_games/05_content_pipeline/images/mgcb-editor-icon.png
new file mode 100644
index 00000000..33fe5233
Binary files /dev/null and b/articles/tutorials/building_2d_games/05_content_pipeline/images/mgcb-editor-icon.png differ
diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/images/mgcb-editor.png b/articles/tutorials/building_2d_games/05_content_pipeline/images/mgcb-editor.png
new file mode 100644
index 00000000..5ad01f76
Binary files /dev/null and b/articles/tutorials/building_2d_games/05_content_pipeline/images/mgcb-editor.png differ
diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/images/mgcb-logo-added.png b/articles/tutorials/building_2d_games/05_content_pipeline/images/mgcb-logo-added.png
new file mode 100644
index 00000000..4af7ea4f
Binary files /dev/null and b/articles/tutorials/building_2d_games/05_content_pipeline/images/mgcb-logo-added.png differ
diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/images/new-file-popup.png b/articles/tutorials/building_2d_games/05_content_pipeline/images/new-file-popup.png
new file mode 100644
index 00000000..9683fd32
Binary files /dev/null and b/articles/tutorials/building_2d_games/05_content_pipeline/images/new-file-popup.png differ
diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/images/new-folder-popup.png b/articles/tutorials/building_2d_games/05_content_pipeline/images/new-folder-popup.png
new file mode 100644
index 00000000..6a5b499f
Binary files /dev/null and b/articles/tutorials/building_2d_games/05_content_pipeline/images/new-folder-popup.png differ
diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/index.md b/articles/tutorials/building_2d_games/05_content_pipeline/index.md
new file mode 100644
index 00000000..d91157d7
--- /dev/null
+++ b/articles/tutorials/building_2d_games/05_content_pipeline/index.md
@@ -0,0 +1,293 @@
+---
+title: "Chapter 05: Content Pipeline"
+description: Learn the advantages of using the Content Pipeline to load assets and go through the processes of loading your first asset
+---
+
+Every game has assets; images to represent the visual graphics to players, audio to provide sound effects and background music, fonts to render text with, and much more. These assets start out as raw files (e.g. *.png* image files or *.mp3* audio files), which you will need to load into the game to use.
+
+## Loading Assets
+
+Loading assets can be done during runtime directly from file, or it can be loaded through the **Content Pipeline** Both of these methods are two sides of the same coin and there are trade offs to each approach.
+
+For instance, to load an image file directly at runtime, you would need to:
+
+1. Add the image file to your project.
+2. Configure the project to copy the image file on build to the build output folder.
+3. Load the image file as a texture at runtime using the [**Texture2D.FromFile**](xref:Microsoft.Xna.Framework.Graphics.Texture2D.FromFile(Microsoft.Xna.Framework.Graphics.GraphicsDevice,System.String)) method.
+
+> [!IMPORTANT]
+> A big disadvantage to loading an image file as a texture directly, is when that when it loads it, it does so in its compressed format such as *.png* or *.jpg*. These compression formats are not understood by a Graphics Processing Unit (GPU); they will need to be decompressed into raw bytes as a format the GPU does understand before it can store the data. Doing this can potentially leave a larger memory footprint for your assets. You will also need to handle how different compression formats work on the platform you are targeting such as desktops, mobile, and consoles.
+
+On the other side of this coin, MonoGame offers the **Content Pipeline**; a workflow for managing assets. The workflow is made up of a set of tools and utilities that are automatically added by default when you create a new MonoGame project using the MonoGame project templates. To use this workflow, you need to:
+
+1. Add the asset file to your content project (*Content.mgcb* file) using the *MonoGame Content Builder Editor* (MGCB Editor).
+2. Perform a project build. Doing this, the *MonoGame.Content.Builder.Tasks* NuGet reference will compile the assets defined in the content project, optimized for the target platform, and automatically copy them to the game project build folder.
+3. Load the compiled asset at runtime using the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager).
+
+For the same amount of steps, you also get the benefit of the assets being pre-processed and compiled to an optimized format for the target platform. For instance, image files can be compiled using [DXT compression](https://en.wikipedia.org/wiki/S3\_Texture\_Compression), which is a format that is understood by GPUs without needing to be decompressed first, reducing the memory footprint.
+
+> [!NOTE]
+> For more information on the benefits of compiling assets and what optimizations it can offer, see the [Content Pipeline](../../../getting_started/content_pipeline/index.md) documentation.
+
+For this tutorial series, we are going to focus on using the content pipeline workflow to load assets. Doing this will get you as the developer accustomed to using the content pipeline tools and also give the benefits of having assets precompiled to optimized formats.
+
+## The MGCB Editor
+
+As mentioned previously, the content pipeline workflow in MonoGame is made up of a set of tools that come with every new MonoGame project. At the center of this workflow is the MGCB Editor; a graphical tool for managing your game's content.
+
+Opening the MGCB Editor can be done in different ways depending on which IDE and development environment you have. Choose the one you are using below to open the MGCB Editor so we can explore its interface:
+
+### [Visual Studio Code](#tab/vscode)
+
+To open the *Content.mgcb* content project file in the MGCB Editor with Visual Studio Code, you can use the *MonoGame for VSCode* extension. You should have installed this extension in [Chapter 02](../02_getting_started/index.md#installing-the-monogame-for-vscode-extension). With this extension install, anytime you have a code file open, you will see the MonoGame logo in the top-right of the code window like below:
+
+|  |
+| :------------------------------------------------------------------------------: |
+| **Figure 5-1: MonoGame for VSCode extension icon** |
+
+Clicking the MonoGame logo here will open the *Content.mgcb* content project file from the current project in the MGCB Editor.
+
+### [Visual Studio 2022](#tab/vs2022)
+
+To open the *Content.mgcb* content project file in the MGCB Editor with Visual Studio 2022, you can use the *MonoGame Framework C# project templates* extension. Despite the name, this extension does more than just install the MonoGame project templates. With this extension installed, simply double-click the *Content.mgcb* content project file in the Solution Explorer panel and it will open it in the MGCB Editor.
+
+### [dotnet CLI](#tab/dotnetcli)
+
+To open the *Content.mgcb* content project file in the MGCB Editor using the dotnet CLI commands, perform the following:
+
+1. Open a new Command Prompt or Terminal window in the same folder as the *DungeonSlime.csproj* project file (your main game project).
+2. Enter the following dotnet CLI command
+
+ ```sh
+ dotnet mgcb-editor ./Content/Content.mgcb`
+ ```
+
+---
+
+> [!TIP]
+> If for any reason the MGCB editor fails to load or you are hit with MGCB errors when you build your project, it is likely the MGCB references from the `dotnet-tools.json` configuration located in your projects `.config` folder have not been loaded/initialized.
+>
+> To correct this, simply run the following from a terminal/command prompt in your projects directory (there the `.config` folder is located)
+>
+> ```sh
+> dotnet tool restore
+> ```
+>
+> This should restore the tool with the version that is configured. If at any time you update your `dotnet-tools.json` configuration, e.g. when upgrading to a newer version of MonoGame, **you will need to run this command again**
+
+|  |
+| :-------------------------------------------------------------------------------------------: |
+| **Figure 5-2: MonoGame Content Builder Editor (MGCB Editor) Window** |
+
+In *Figure 5-2* above, you can see the user interface for the MGCB Editor:
+
+- **Toolbar**: Contains icon buttons for common actions such as creating new items, opening files, saving changes, and building content.
+- **Project Panel**: Located on the left of the MGCB Editor, displays a hierarchical tree view of all content items added to the content project. The root node *Content* represents the root of the content project.
+- **Properties Panel**: Located on the bottom left of the MGCB Editor, shows the properties of the currently selected item in the project panel. The properties available are based on the item type selected.
+- **Build Output Panel**: The large area to the right side outputs build messages, warnings, and errors when content is processed.
+
+### Creating Folders to Organize Content
+
+Organizing your game assets into folders helps keep your content project manageable as it grows. For now, we will add a new folder that will hold the image assets we will add to the game throughout this tutorial series. In the MGCB Editor:
+
+1. In the Project Panel, select the root `Content` node.
+2. Right-click it and choose `Add > New Folder...` from the context menu.
+3. Type `images` for the folder name and click the `Ok` button.
+
+|  |
+| :-------------------------------------------------------------: |
+| **Figure 5-3: New folder pop-up** |
+
+You have now created a folder that will help organize the game's image assets. As we continue through this tutorial series, we will be adding additional folders for organization of content such as audio, fonts, and effects.
+
+> [!NOTE]
+> If you try to add a new folder that already exists in the file system but is not showing in the MGCB editor, you will get an error. Either remove the folder or use `Add Existing Folder` instead.
+
+### Adding Your First Asset
+
+Now that we have a folder structure, we can add our first image asset to the project. For this example, we will use the MonoGame logo. Perform the following
+
+1. First, download the MonoGame logo by right-clicking the following image and saving it as `logo.png` somewhere on your computer:
+
+ |  |
+ | :--------------------------------------------------------: |
+ | **Figure 5-4: MonoGame Horizontal Logo** |
+
+2. In the MGCB Editor, select the `images` folder you created earlier.
+3. Right-click it and choose `Add > Existing Item...` from the context menu.
+4. Navigate to the location of the `logo.png` file you just downloaded and select it.
+5. Click the `Open` button
+6. When prompted in the add existing file popup, choose `Copy the file to the directory.`
+
+ |  |
+ | :------------------------------------------------------------------: |
+ | **Figure 5-5: Add existing file pop-up** |
+
+7. Save the changes to the content project by selecting `File > Save` from the top menu or pressing `CTRL+S`.
+
+|  |
+| :---------------------------------------------------------------------------------------------------------: |
+| **Figure 5-6: The logo image added to the content project in the MGCB Editor** |
+
+> [!IMPORTANT]
+> After changes have been made in the MGCB Editor, ensure that you save the changes. They are not automatically saved, though you will be warned if you close the editor and have not saved changes. You can tell that changes have not been saved by looking at the title bar of the MGCB editor window. If it has an '*' at the end of the window title, this means changes have not been saved.
+
+## Understanding the Content Pipeline Workflow
+
+Now that we have added our first asset, we can take a moment to understand what happens to this asset in the Content Pipeline workflow:
+
+1. You create source files for your game assets such as images, audio, fonts, effects, and 3D models.
+2. Using the MGCB Editor, add these assets your content project (the `Content.mgcb` file).
+3. When you perform a build of your project, the `MonoGame.Content.Builder.Task` NuGet reference will:
+ 1. Compile the assets defined in the content project using the **MonoGame Content Builder (MGCB)** tool into `.xnb` files.
+ 2. Copy the compiled `.xnb` files from the content project's build folder to your game project's build folder.
+4. At runtime, you load the compiled assets using the [ContentManager](xref:Microsoft.Xna.Framework.Content.ContentManager).
+
+The following diagram demonstrates this workflow:
+
+|  |
+| :--------------------------------------------------------------------------------------------: |
+| **Figure 5-7: MonoGame Content Pipeline Workflow diagram showing the process flow from source files (Images, Audio, Fonts, Effects, Models) through the MGCB Editor to generate the Content.mgcb file, which is then processed by MonoGame.Content.Builder.Tasks to create compiled .xnb assets (Xnb formats for each type), which are finally loaded by the ContentManager at runtime** |
+
+The Content Pipeline offers significant advantages:
+
+- Assets are pre-processed and optimized for your target platform.
+- Image files can be compiled using formats like DXT compression, which GPU's understand natively.
+- Asset loading is simplified and consistent across platforms.
+
+## The ContentManager Class
+
+To load assets in your game code, MonoGame provides the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) class. The [**Game**](xref:Microsoft.Xna.Framework.Game) already has a [**Content**](xref:Microsoft.Xna.Framework.Game.Content) property which is a ready-to-ue instance of the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager)
+
+### ContentManager Methods
+
+They key methods for asset loading are:
+
+| Method | Returns | Description |
+| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [**Load<T>(string)**](xref:Microsoft.Xna.Framework.Content.ContentManager.Load``1(System.String)) | `T` | Loads the assets of type `T` that has been processed by the content pipeline. |
+| [**Unload**](xref:Microsoft.Xna.Framework.Content.ContentManager.Unload) | `void` | Unloads all assets that have been loaded by that content manager instance. |
+
+> [!TIP]
+> When an asset is loaded for the first time, the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) internally caches it. Loading the same asset again will return the cached version, avoiding extra disk reads.
+
+## Understanding Content Paths
+
+When loading content, you need to specify the path to the asset, minus the extension. This path is relative to the ContentManager's [**RootDirectory**](xref:Microsoft.Xna.Framework.Content.ContentManager.RootDirectory) property, which is set to **"Content"** by default in the `Game1` constructor.
+
+For example, with our newly added logo in the "images" folder,the path would be "images/logo" (without the file extension). The reason for this relates to the build process. When you build your project, the *MonoGame.Content.Builder.Tasks* NuGet reference [compiles your assets and copies them to the game's output folder](#understanding-the-content-pipeline-workflow).
+
+This creates a folder structure in your output directory similar to:
+
+```sh
+DungeonSlime/
+ └── bin/
+ └── Debug/
+ └── net8.0/
+ ├── DungeonSlime.exe
+ └── Content/
+ └── images/
+ └── logo.xnb
+```
+
+> [!NOTE]
+> Notice that the compile asset has an .xnb extension, but when loading the asset in code, you refer to it without any extension.
+
+## Loading and Displaying Your First Asset
+
+Now that we have the MonoGame logo added as an asset in the content project, we can modify the game to display the logo. In the *DungeonSlime* project open the `Game1.cs` file and perform the following:
+
+1. Add a field to store the logo texture by inserting this line after the class declaration:
+
+ ```cs
+ // The MonoGame logo texture
+ private Texture2D _logo;
+ ```
+
+2. In the [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent) method, add this line to load the logo texture:
+
+ ```cs
+ _logo = Content.Load("images/logo");
+ ```
+
+3. Finally, in the [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) method, add these lines before the `base.Draw(gameTime);` call:
+
+ ```cs
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw the logo texture
+ SpriteBatch.Draw(_logo, Vector2.Zero, Color.White);
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+ ```
+
+ > [!NOTE]
+ > We will go more into detail about the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) in the next chapter.
+
+The complete updated `Game1.cs` file should now look like this
+
+[!code-csharp[](./snippets/game1.cs?highlight=10-11,27,44-51)]
+
+Running the game now will show the MonoGame logo displayed in the upper-left corner of the game window.
+
+|  |
+| :--------------------------------------------------------------------------------: |
+| **Figure 5-8: The MonoGame logo drawn to the game window** |
+
+## Adding Build-In Asset Types
+
+The MGCB Editor can also create certain built-in asset types. In this section we will explore these types and this functionality. If not already open, [open the MGCB Editor](#the-mgcb-editor) and perform the following:
+
+1. Select the `Content` node.
+2. Right-click it and choose `Add > New Item...` from the context menu.
+3. In the dialog that appears, you will see the available built-in types.
+
+ |  |
+ | :---------------------------------------------------------: |
+ | **Figure 5-9: New file pop-up** |
+
+The available built-in types include:
+
+- **Effect (.fx)**: A shader file that creates custom visual effects by controlling how graphics are rendered on the GPU.
+- **SpriteFont Description (.spritefont)**: A configuration file that defines how text will be displayed in your game, including character set and font properties.
+- **Sprite Effect (.fx)**: A shader specifically designed for use with 2D sprites to create special visual effects.
+- **Xml Content (.xml)**: A structured data file for storing game information like levels, dialogues, or configuration settings.
+- **LocalizedSpriteFont Description (.spritefont)**: A configuration file for creating fonts with support for multiple languages.
+
+> [!NOTE]
+> Each built-in asset type comes with a template that includes the minimum required structure and settings.
+
+For now, click the `Cancel` button on the new file dialog. We will explore these built-in types further in later chapters when we need them.
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+- You learned about the advantages of loading assets using the **Content Pipeline**.
+- You opened the MGCB Editor and explored its interface.
+- You created a folder structure to organize your game assets.
+- You added an image file asset to the content project.
+- You understood the Content Pipeline workflow and how MonoGame automates the process.
+- You loaded and displayed your first asset using the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager).
+
+In the next chapter, we will explore working with textures in more detail and learning about different rendering options.
+
+## Test Your Knowledge
+
+1. What are the two main ways of loading a texture, and what are the pros and cons of each approach?
+
+ :::question-answer
+ The two main ways to load a texture in MonoGame are:
+
+ 1. Directly from file using [**Texture2D.FromFile**](xref:Microsoft.Xna.Framework.Graphics.Texture2D.FromFile(Microsoft.Xna.Framework.Graphics.GraphicsDevice,System.String)). This method requires manually setting up file copying, offers no pre-processing benefits, and can have a higher memory footprint.
+
+ 2. Using the content pipeline with [**Content.Load\**](xref:Microsoft.Xna.Framework.Content.ContentManager.Load``1(System.String)). Using the content pipeline optimizes textures into formats for the target platform(s), automatically handles compiling and copying assets during build, and reduces memory footprint, but requires additional setup using the MGCB Editor.
+ :::
+
+2. During the MonoGame content pipeline workflow, assets are compiled and then copied to the project output folder. What is responsible for performing this task?
+
+ :::question-answer
+ The *MonoGame.Content.Builder.Tasks* NuGet reference.
+ :::
diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/snippets/game1.cs b/articles/tutorials/building_2d_games/05_content_pipeline/snippets/game1.cs
new file mode 100644
index 00000000..46ed5c3f
--- /dev/null
+++ b/articles/tutorials/building_2d_games/05_content_pipeline/snippets/game1.cs
@@ -0,0 +1,55 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary;
+
+namespace DungeonSlime;
+
+public class Game1 : Core
+{
+ // The MonoGame logo texture
+ private Texture2D _logo;
+
+ public Game1() : base("Dungeon Slime", 1280, 720, false)
+ {
+
+ }
+
+ protected override void Initialize()
+ {
+ // TODO: Add your initialization logic here
+
+ base.Initialize();
+ }
+
+ protected override void LoadContent()
+ {
+ _logo = Content.Load("images/logo");
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
+ Exit();
+
+ // TODO: Add your update logic here
+
+ base.Update(gameTime);
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw the logo texture
+ SpriteBatch.Draw(_logo, Vector2.Zero, Color.White);
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+ }
+}
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/icon-on-top-of-wordmark.png b/articles/tutorials/building_2d_games/06_working_with_textures/images/icon-on-top-of-wordmark.png
new file mode 100644
index 00000000..b96f0f67
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/images/icon-on-top-of-wordmark.png differ
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/icon-wordmark-centered.png b/articles/tutorials/building_2d_games/06_working_with_textures/images/icon-wordmark-centered.png
new file mode 100644
index 00000000..a7f6f397
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/images/icon-wordmark-centered.png differ
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-centered.png b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-centered.png
new file mode 100644
index 00000000..a69272ff
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-centered.png differ
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-drawn.png b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-drawn.png
new file mode 100644
index 00000000..3e330c62
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-drawn.png differ
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-flipped-horizontally-and-vertically.png b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-flipped-horizontally-and-vertically.png
new file mode 100644
index 00000000..f7dcd41b
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-flipped-horizontally-and-vertically.png differ
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-flipped-horizontally.png b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-flipped-horizontally.png
new file mode 100644
index 00000000..b5c74375
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-flipped-horizontally.png differ
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-green-tint.png b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-green-tint.png
new file mode 100644
index 00000000..2d0dbfa8
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-green-tint.png differ
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-half-transparency.png b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-half-transparency.png
new file mode 100644
index 00000000..efce0118
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-half-transparency.png differ
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-off-center.png b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-off-center.png
new file mode 100644
index 00000000..a88dd70c
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-off-center.png differ
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-rotated-centered.png b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-rotated-centered.png
new file mode 100644
index 00000000..e93914b4
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-rotated-centered.png differ
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-rotated-offcenter.png b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-rotated-offcenter.png
new file mode 100644
index 00000000..f4e0c8c4
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-rotated-offcenter.png differ
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-scaled-1.5x-0.5x.png b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-scaled-1.5x-0.5x.png
new file mode 100644
index 00000000..f0a351ad
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-scaled-1.5x-0.5x.png differ
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-scaled-1.5x-zero-origin.png b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-scaled-1.5x-zero-origin.png
new file mode 100644
index 00000000..f4a46bf0
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-scaled-1.5x-zero-origin.png differ
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-scaled-1.5x.png b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-scaled-1.5x.png
new file mode 100644
index 00000000..8bd7c01a
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-scaled-1.5x.png differ
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-texture-regions.drawio b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-texture-regions.drawio
new file mode 100644
index 00000000..f34ad94b
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-texture-regions.drawio
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-texture-regions.png b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-texture-regions.png
new file mode 100644
index 00000000..09eb566c
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/images/logo-texture-regions.png differ
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/index.md b/articles/tutorials/building_2d_games/06_working_with_textures/index.md
new file mode 100644
index 00000000..ebf0d902
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/index.md
@@ -0,0 +1,353 @@
+---
+title: "Chapter 06: Working with Textures"
+description: Learn how to load and render textures using the MonoGame content pipeline and SpriteBatch.
+---
+
+Textures are images that are used in your game to represent the visual graphics to the player, commonly referred to as *Sprites*. In [Chapter 05](../05_content_pipeline/index.md#loading-assets), you went through the steps of using the **Content Pipeline** to load the MonoGame *logo.png* texture and rendering it to the screen.
+
+In this chapter, you will:
+
+- Learn how to render a texture with the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch).
+- Explorer how to manipulate the way the texture is rendered using the parameters of the [**SpriteBatch.Draw**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.Draw(Microsoft.Xna.Framework.Graphics.Texture2D,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Color)) method.
+
+## Drawing a Texture
+
+When rendering in MonoGame, *render states*, properties of the [**GraphicsDevice**](xref:Microsoft.Xna.Framework.Graphics.GraphicsDevice) that affect how rendering is performed, need to be set. When rendering 2D sprites, the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) class simplifies rendering by managing these render states for you.
+
+> [!IMPORTANT]
+> Although the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) makes it easier to manage the render states for the [**GraphicsDevice**](xref:Microsoft.Xna.Framework.Graphics.GraphicsDevice), it can also change states that you may have set manually, such as when you are performing 3D rendering. Keep this in mind when mixing 2D and 3D rendering.
+
+Three methods are are used when rendering with the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch):
+
+1. [**SpriteBatch.Begin**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.Begin(Microsoft.Xna.Framework.Graphics.SpriteSortMode,Microsoft.Xna.Framework.Graphics.BlendState,Microsoft.Xna.Framework.Graphics.SamplerState,Microsoft.Xna.Framework.Graphics.DepthStencilState,Microsoft.Xna.Framework.Graphics.RasterizerState,Microsoft.Xna.Framework.Graphics.Effect,System.Nullable{Microsoft.Xna.Framework.Matrix})) prepares the Graphics Device for rendering, including the render states.
+2. [**SpriteBatch.Draw**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.Draw(Microsoft.Xna.Framework.Graphics.Texture2D,Microsoft.Xna.Framework.Rectangle,Microsoft.Xna.Framework.Color)) tells the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) what to render. This is usually called multiple times before [**SpriteBatch.End**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.End) and batches the draw calls for efficiency.
+3. [**SpriteBatch.End**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.End) submits the draw calls that were batched to the graphics device to be rendered.
+
+> [!NOTE]
+> The order of method calls when rendering using the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) is important. [**SpriteBatch.Begin**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.Begin(Microsoft.Xna.Framework.Graphics.SpriteSortMode,Microsoft.Xna.Framework.Graphics.BlendState,Microsoft.Xna.Framework.Graphics.SamplerState,Microsoft.Xna.Framework.Graphics.DepthStencilState,Microsoft.Xna.Framework.Graphics.RasterizerState,Microsoft.Xna.Framework.Graphics.Effect,System.Nullable{Microsoft.Xna.Framework.Matrix})) must be called before any [**SpriteBatch.Draw**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.Draw(Microsoft.Xna.Framework.Graphics.Texture2D,Microsoft.Xna.Framework.Rectangle,Microsoft.Xna.Framework.Color)) calls are made. When finished, [**SpriteBatch.End**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.End) must be called before another [**SpriteBatch.Begin**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.Begin(Microsoft.Xna.Framework.Graphics.SpriteSortMode,Microsoft.Xna.Framework.Graphics.BlendState,Microsoft.Xna.Framework.Graphics.SamplerState,Microsoft.Xna.Framework.Graphics.DepthStencilState,Microsoft.Xna.Framework.Graphics.RasterizerState,Microsoft.Xna.Framework.Graphics.Effect,System.Nullable{Microsoft.Xna.Framework.Matrix})) can be called. If these methods are called out of order, an exception will be thrown.
+
+As mentioned in [Chapter 03](../03_the_game1_file/index.md#the-game-loop), all rendering should be done inside the [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) method. The [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) method's responsibility is to render the game state that was calculated in [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)); it should not contain any game logic or complex calculations.
+
+At the end of [Chapter 05](../05_content_pipeline/index.md#loading-assets), you added the following code to [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) in the `Game1.cs` file:
+
+[!code-csharp[](./snippets/draw.cs?highlight=6-7,9-10,12-13)]
+
+These lines initialize the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch), draw the logo at [**Vector2.Zero**](xref:Microsoft.Xna.Framework.Vector2.Zero) (0, 0), and complete the batch. When you ran the game and the logo appeared in the window's upper-left corner:
+
+|  |
+| :--------------------------------------------------------------------------------: |
+| **Figure 6-1: The MonoGame logo drawn to the game window** |
+
+The [**SpriteBatch.Draw**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.Draw(Microsoft.Xna.Framework.Graphics.Texture2D,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Color)) method we just used can be given the following parameters:
+
+| Parameter | Type | Description |
+| ---------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| *texture* | [**Texture2D**](xref:Microsoft.Xna.Framework.Graphics.Texture2D) | The [**Texture2D**](xref:Microsoft.Xna.Framework.Graphics.Texture2D) to draw. |
+| *position* | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | The X and Y coordinates at which the texture will be rendered, with the texture's origin being the upper-left corner of the image. |
+| *color* | [**Color**](xref:Microsoft.Xna.Framework.Color) | The color mask (tint) to apply to the image drawn. Specifying [**Color.White**](xref:Microsoft.Xna.Framework.Color.White) will render the texture with no tint. |
+
+> [!TIP]
+> Try adjusting the position and color parameters and see how they can affect the image being drawn.
+
+MonoGame uses a coordinate system where (0, 0) is at the screen's upper-left corner. X values increase moving right, and Y values increase moving down. Understanding this, we wil try to center the logo on the game window.
+
+To center content on the screen, we need to find the window's center point. We can access this using the [**Window.ClientBounds**](xref:Microsoft.Xna.Framework.GameWindow.ClientBounds) property from the [**Game**](xref:Microsoft.Xna.Framework.Game) class, which represents the rectangular bounds of the game window. [**Window.ClientBounds**](xref:Microsoft.Xna.Framework.GameWindow.ClientBounds) exposes both [**Width**](xref:Microsoft.Xna.Framework.Rectangle.Width) and [**Height**](xref:Microsoft.Xna.Framework.Rectangle.Height) properties for the window's dimensions in pixels. By dividing these dimensions in half, we can can calculate the window's center coordinates. We can update our [**Draw**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.Draw(Microsoft.Xna.Framework.Graphics.Texture2D,Microsoft.Xna.Framework.Rectangle,Microsoft.Xna.Framework.Color)) method to use this:
+
+[!code-csharp[](./snippets/draw_center_wrong.cs?highlight=9-16)]
+
+> [!TIP]
+> In the example above, we multiply the [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) created by `0.5f` to halve the value instead of dividing it by `2.0f`. If you are not used to seeing this, it might seem strange at first, but it is actually an optimization technique. CPUs are able to perform multiplication operations much faster than division operations and reading `* 0.5f` is easily understood to be the same thing as `/ 2.0f` when reading.
+
+We have now set the position to half the window's dimensions, which should center the logo. Run the game to see the result.
+
+|  |
+| :-----------------------------------------------------------------------------------------------------------: |
+| **Figure 6-2: Attempting to draw the MonoGame logo centered on the game window** |
+
+The logo is not centered as we expected it to be. Even though we set the *position* parameter to the center of the game window, the texture starts drawing from its *origin*, which is the upper-left corner in this example. So when we set the position to the screen's center, we are actually placing the logo's upper-left corner at that point, not the center of the texture.
+
+One way to correct this is to subtract half the width and height of the texture from the game window's center position like so:
+
+[!code-csharp[](./snippets/draw_center.cs?highlight=12-14)]
+
+This offsets the position so that it correctly centers the image to the game window.
+
+|  |
+| :--------------------------------------------------------------------------------------------: |
+| **Figure 6-3: The MonoGame logo drawn centered on the game window** |
+
+While this works, there is a better approach. There is a different overload of the [**SpriteBatch.Draw**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.Draw(Microsoft.Xna.Framework.Graphics.Texture2D,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Color)) method that provides additional parameters for complete control over the draw operation, which we will use in the upcoming sections.
+
+Update your code to:
+
+[!code-csharp[](./snippets/draw_all_params.cs?highlight=10-22)]
+
+This overload produces the same centered result but exposes all parameters that control rendering for a draw operation. Unlike engines that abstract much of these details away, MonoGame provides explicit control for a flexible custom rendering pipeline. Here is what each parameter does:
+
+| Parameter | Type | Description |
+| ----------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| *texture* | [**Texture2D**](xref:Microsoft.Xna.Framework.Graphics.Texture2D) | The [**Texture2D**](xref:Microsoft.Xna.Framework.Graphics.Texture2D) to draw. |
+| *position* | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | The X and Y coordinate position at which the texture will be rendered, relative to the *origin* parameter. |
+| *sourceRectangle* | [**Rectangle**](xref:Microsoft.Xna.Framework.Rectangle) | An optional region within the texture to be rendered in order to draw only a portion of the texture. Specifying `null` will render the entire texture. |
+| *color* | [**Color**](xref:Microsoft.Xna.Framework.Color) | The color mask (tint) to apply to the image drawn. Specifying [**Color.White**](xref:Microsoft.Xna.Framework.Color.White) will render the texture with no tint. |
+| *rotation* | `float` | The amount of rotation, in radians, to apply to the texture when rendering. Specifying `0.0f` will render the image with no rotation. |
+| *origin* | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | The X and Y coordinate origin point of the texture when rendering. This will affect the offset of the texture when rendered as well being the origin in which the texture is rotated around and scaled from. |
+| *scale* | `float` | The amount to scale the image across the x- and y-axes. Specifying `1.0f` will render the image at its default size with no scaling. |
+| *effects* | [**SpriteEffects**](xref:Microsoft.Xna.Framework.Graphics.SpriteEffects) | A [**SpriteEffects**](xref:Microsoft.Xna.Framework.Graphics.SpriteEffects) enum value to that specifies if the texture should be rendered flipped across the horizontal axis, the vertical axis, or both axes. |
+| *layerDepth* | `float` | Specifies the depth at which the texture is rendered. Textures with a higher layer depth value are drawn on top of those with a lower layer depth value. **Note: This value will only apply when using `SpriteSortMode.FrontToBack` or `SpriteSortMode.BackToFront`. We will cover this in a moment.** |
+
+### Rotation
+
+First we will explore the `rotation` parameter. This value is the amount of rotation to apply to the sprite when rendering it. We will rotate the texture 90° to make it vertical. Since rotation is measured in radians, not degrees, we can use the built-in math library in MonoGame to make the conversion for us by calling [**MathHelper.ToRadians**](xref:Microsoft.Xna.Framework.MathHelper.ToRadians(System.Single)). Update the code to:
+
+[!code-csharp[](./snippets/rotation.cs?highlight=17)]
+
+Running the code now shows the rotated image, but not in the expected position:
+
+|  |
+| :----------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 6-4: Attempting to draw the MonoGame logo rotated 90° and centered on the game window** |
+
+The reason the sprite did not rotate as expected is because of the `origin` parameter.
+
+### Origin
+
+The `origin` parameter specifies the point of origin in which the sprite is rendered from, rotated from, and scaled from. By default, if no origin is set, it will be [**Vector2.Zero**](xref:Microsoft.Xna.Framework.Vector2.Zero), the upper-left corner of the sprite. To visualize this, see *Figure 6-5* below. The red square represents where the origin is for the sprite, and we can see how it is rotated around this origin point.
+
+|  |
+| :-----------------------------------------------------------------------------------------------------------------------: |
+| **Figure 6-5: Demonstration of how a sprite is rotated around its origin** |
+
+To resolve the rotation issue we had, we need to need to change two things:
+
+1. Set the `origin` parameter to the center of the sprite instead of defaulting to the upper-left corner.
+2. Change the `position` parameter back to the center of the screen.
+
+Update the code to:
+
+[!code-csharp[](./snippets/origin.cs?highlight=12-14,18-20)]
+
+By moving the sprite's origin point to its center, this not only corrects the point of rotation, but also allows us to use the screen center position directly without needing additional position offset calculations. Running the game now shows the log properly centered and rotated 90°.
+
+> [!NOTE]
+> When setting the `origin` parameter, it is based on the sprites width and height, so the center origin will be half the width and half the height of the sprite.
+
+|  |
+| :--------------------------------------------------------------------------------------------------------------------: |
+| **Figure 6-6: The MonoGame logo drawn rotated 90° and centered on the game window** |
+
+### Scale
+
+The `scale` parameter specifies the amount of scaling to apply to the sprite when it is rendered. The default value is `1.0f`, which can be read as "rendering the sprite at 1x the size". Increasing this will scale up the size of the sprite and decreasing it will scale down the sprite.
+
+For this example, we will first reset the rotation back to `0.0f` (removing the 90° rotation we applied above) so we can clearly see the scaling effect. Then we will set the scale of the logo sprite to `1.5f`
+
+[!code-csharp[](./snippets/scale.cs?highlight=17,21)]
+
+|  |
+| :-------------------------------------------------------------------------------------------: |
+| **Figure 6-7: The MonoGame logo drawn scaled at 1.5x the size** |
+
+Note that the sprite scaled up from the center. This is because we still have the `origin` parameter set as the center of the sprite. If we instead adjusted the code so the `origin` parameter was back in the upper-left corner like so:
+
+[!code-csharp[](./snippets/scale_no_origin.cs?highlight=18-19)]
+
+Then the scaling is applied from the origin in the upper-left corner producing the following result:
+
+|  |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 6-8: The MonoGame logo drawn scaled at 1.5x the size with the origin set in the upper-left corner** |
+
+Scaling can also be applied to the x- and y-axes independently by providing it with a [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) value instead of a float value. For instance, we can scale the x-axis of the sprite by 1.5x and reduce the scale of the y-axis to 0.5x:
+
+[!code-csharp[](./snippets/scale_vector2.cs?highlight=21)]
+
+Which will produce the following result:
+
+|  |
+| :-------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 6-9: The MonoGame logo drawn scaled at 1.5x the size on the x-axis and 0.5x on the y-axis** |
+
+### SpriteEffects
+
+The `effects` parameter is used to flip the sprite when rendered on either the horizontal or vertical axis, or both. This value for this parameter will be one of the [**SpriteEffects**](xref:Microsoft.Xna.Framework.Graphics.SpriteEffects) enum values.
+
+| SpriteEffect | Description |
+| ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
+| [**SpriteEffects.None**](xref:Microsoft.Xna.Framework.Graphics.SpriteEffects.None) | No effect is applied and the sprite is rendered normally. |
+| [**SpriteEffects.FlipHorizontally**](xref:Microsoft.Xna.Framework.Graphics.SpriteEffects.FlipHorizontally) | The sprite is rendered flipped along the horizontal axis. |
+| [**SpriteEffects.FlipVertically**](xref:Microsoft.Xna.Framework.Graphics.SpriteEffects.FlipVertically) | The sprite is rendered flipped along the vertical axis. |
+
+For this example, we will reset the scale back to `1.0f` and apply the [**SpriteEffects.FlipHorizontally**](xref:Microsoft.Xna.Framework.Graphics.SpriteEffects.FlipHorizontally) value to the sprite:
+
+[!code-csharp[](./snippets/spriteeffects.cs?highlight=21,22)]
+
+Which will produce the following result:
+
+|  |
+| :--------------------------------------------------------------------------------------------: |
+| **Figure 6-10: The MonoGame logo flipped horizontally** |
+
+The [**SpriteEffects**](xref:Microsoft.Xna.Framework.Graphics.SpriteEffects) enum value also uses the [`[Flag]`](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-flagsattribute) attribute, which means we can combine both horizontal and vertical flipping together. To do this, we use the [bitwise OR operator](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/bitwise-and-shift-operators#logical-or-operator-) `|`. Update the `effect` parameter value to the following:
+
+[!code-csharp[](./snippets/spriteeffects_flags.cs?highlight=22-23)]
+
+Now the sprite is flipped both horizontally and vertically
+
+|  |
+| :--------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 6-11: The MonoGame logo flipped horizontally and vertically** |
+
+### Color and Opacity
+
+The `color` parameter applies a color mask to the sprite when it is rendered. Note that this is not setting the actual color of the image, just a mask that is applied, like a tint. The default value is [**Color.White**](xref:Microsoft.Xna.Framework.Color.White). So if we are setting it to [**Color.White**](xref:Microsoft.Xna.Framework.Color.White), why does this not affect the tinting of the sprite drawn?
+
+When the `color` parameter is applied, each color channel (Red, Green, Blue) of the sprite is multiplied by the corresponding channel in the `color` parameter, where each channel is represented as a value between `0.0f` and `1.0f`. For [**Color.White**](xref:Microsoft.Xna.Framework.Color.White), all color channels are set to `1.0f` (255 in byte form), so the multiplication looks like this:
+
+```sh
+Final Red = Sprite Red * 1.0f
+Final Green = Sprite Green * 1.0f
+Final Blue = Sprite Blue * 1.0f;
+```
+
+Since multiplying by `1.0f` does not change the value, [**Color.White**](xref:Microsoft.Xna.Framework.Color.White) essentially preserves the original colors of the sprite.
+
+For this example, we will reset the `effects` parameter back to [**SpriteEffects.None**](xref:Microsoft.Xna.Framework.Graphics.SpriteEffects.None) and update the `color` parameter to use [**Color.Green**](xref:Microsoft.Xna.Framework.Color.Green):
+
+[!code-csharp[](./snippets/color.cs?highlight=16,22)]
+
+This produces the following result:
+
+|  |
+| :---------------------------------------------------------------------------------------------: |
+| **Figure 6-12: The MonoGame logo with a green color tint applied** |
+
+> [!NOTE]
+> The icon and the word "GAME" in the logo look black after using a [**Color.Green**](xref:Microsoft.Xna.Framework.Color.Green) because the Red, Blue Green components of that color are (`0.0f`, `0.5f`, `0.0f`). The Orange color used in the logo is [**Color.MonoGameOrange**](xref:Microsoft.Xna.Framework.Color.MonoGameOrange), which has the component values of (`0.9f`, `0.23f`, `0.0f`). When multiplying the component values, the result is (`0.0f`, `0.125f`, `0.0f`) which would be Red 0, Green 31, Blue 0 in byte values. So it is not quite fully black, but it is very close.
+>
+> This is why it is important to understand how the `color` parameter values are applied to the sprite when it is rendered.
+
+To adjust the opacity of a sprite, we can multiply the `color` parameter value by a value between `0.0f` (fully transparent) and `1.0f` (fully opaque). For instance, if we wanted to render the logo with 50% transparency we can multiply the `color` parameter by `0.5f` like this:
+
+[!code-csharp[](./snippets/opacity.cs?highlight=16)]
+
+Which will produce the following result:
+
+|  |
+| :-------------------------------------------------------------------------------------------: |
+| **Figure 6-13: The MonoGame logo with half transparency** |
+
+### Source Rectangle
+
+The `sourceRectangle` parameter specifies a specific boundary within the texture that should be rendered. So far, we have just set this parameter to `null`, which specifies that the full texture should be rendered. If we only wanted to render a portion of the texture as the sprite, we can set this parameter value.
+
+For instance, take the logo image we have been using. We can break it down into two distinct regions; the MonoGame icon and the MonoGame wordmark.
+
+|  |
+| :-----------------------------------------------------------------------------------------------------------------: |
+| **Figure 6-14: The MonoGame logo broken down into the icon and wordmark regions** |
+
+We can see from *Figure 6-14* above that the actual icon starts at position (0, 0) and is 128px wide and 128px tall. Likewise, the wordmark starts at position (150, 34) and is 458px wide and 58px tall. Knowing the starting position and the width and height of the region gives us a defined rectangle that we can use as the `sourceRectangle`.
+
+We can see this in action by drawing the icon and the wordmark separately from the same texture. Update the code to the following:
+
+[!code-csharp[](./snippets/sourcerectangle.cs?highlight=6-7,9-10,15-30,32-47)]
+
+The following changes were made:
+
+- Two new [**Rectangle**](xref:Microsoft.Xna.Framework.Rectangle) values called `iconSourceRect` and `wordmarkSourceRect` that represent the boundaries of the MonoGame icon and wordmark regions within the logo texture were added.
+- The *sourceRectangle* parameter of the `_spriteBatch.Draw` was updated to use the new `iconSourceRect` value. **Notice that we are still telling it to draw the `_logo` for the *texture*, we have just supplied it with a source rectangle this time.**
+- The *origin* parameter was updated to use the width and height of the `iconSourceRect`. Since the overall dimensions of what we will be rendering has changed due to supplying a source rectangle, the origin needs to be adjusted to those dimensions as well.
+- Finally, a second `_spriteBatch.Draw` call is made, this time using the `wordmarkSourceRect` as the source rectangle so that the wordmark is drawn.
+
+If you run the game now, you should see the following:
+
+|  |
+| :-------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 6-15: The MonoGame icon and wordmark, from the logo texture, centered in the game window** |
+
+> [!NOTE]
+> Making use of the `sourceRectangle` parameter to draw different sprites from the same texture is optimization technique that we will explore further in the next chapter.
+
+### Layer Depth
+
+The final parameter to discuss is the `layerDepth` parameter. Notice that in *Figure 6-15* above, the word mark is rendered on top of the icon. This is because of the order the draw calls were made; first the icon was rendered, then the word mark was rendered.
+
+The [**SpriteBatch.Begin**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.Begin(Microsoft.Xna.Framework.Graphics.SpriteSortMode,Microsoft.Xna.Framework.Graphics.BlendState,Microsoft.Xna.Framework.Graphics.SamplerState,Microsoft.Xna.Framework.Graphics.DepthStencilState,Microsoft.Xna.Framework.Graphics.RasterizerState,Microsoft.Xna.Framework.Graphics.Effect,System.Nullable{Microsoft.Xna.Framework.Matrix})) method contains several optional parameters, one of which is the `sortMode` parameter. By default, this value is [**SpriteSortMode.Deferred**](xref:Microsoft.Xna.Framework.Graphics.SpriteSortMode.Deferred), which means what is drawn is done so in the order of the [**SpriteBatch.Draw**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.DrawString(Microsoft.Xna.Framework.Graphics.SpriteFont,System.Text.StringBuilder,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Color,System.Single,Microsoft.Xna.Framework.Vector2,System.Single,Microsoft.Xna.Framework.Graphics.SpriteEffects,System.Single)) calls. Each subsequent call will be drawn visually on top of the previous call.
+
+When [**SpriteSortMode.Deferred**](xref:Microsoft.Xna.Framework.Graphics.SpriteSortMode.Deferred) is used, then the `layerDepth` parameter in the [**SpriteBatch.Draw**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.DrawString(Microsoft.Xna.Framework.Graphics.SpriteFont,System.Text.StringBuilder,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Color,System.Single,Microsoft.Xna.Framework.Vector2,System.Single,Microsoft.Xna.Framework.Graphics.SpriteEffects,System.Single)) call is essentially ignored. For instance, in the first `_spriteBatch.Draw` method call, update the `layerDepth` parameter to `1.0f`.
+
+[!code-csharp[](./snippets/layerdepth.cs?highlight=29)]
+
+Doing this should tell it to render on a layer above the wordmark since the icon is at `1.0f` and the wordmark is at `0.0f` for the `layerDepth`. However, if you run the game now, you will see that no change actually happens; the wordmark is still drawn on top of the icon.
+
+To make use of the `layerDepth` parameter, you need to set the `sortMode` to either [**SpriteSortMode.BackToFront**](xref:Microsoft.Xna.Framework.Graphics.SpriteSortMode.BackToFront) or [**SpriteSortMode.FrontToBack**](xref:Microsoft.Xna.Framework.Graphics.SpriteSortMode.FrontToBack).
+
+| Sort Mode | Description |
+| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
+| [**SpriteSortMode.BackToFront**](xref:Microsoft.Xna.Framework.Graphics.SpriteSortMode.BackToFront) | Sprites are sorted by depth in back-to-front order prior to drawing. |
+| [**SpriteSortMode.FrontToBack**](xref:Microsoft.Xna.Framework.Graphics.SpriteSortMode.FrontToBack) | Sprites are sorted by depth in front-to-back order prior to drawing. |
+
+Now we can see this in action. We have already set the `layerDepth` parameter of the icon to `1.0f`. Find the `_spriteBatch.Begin()` method call and update it to the following:
+
+[!code-csharp[](./snippets/sortmode.cs?highlight=13)]
+
+Now we are telling it to use the [**SpriteSortMode.FrontToBack**](xref:Microsoft.Xna.Framework.Graphics.SpriteSortMode.FrontToBack) sort mode, which will sort the draw calls so that those with a higher `layerDepth` will be drawn on top of those with a lower one. Even though we did not change the order of the `_spriteBatch.Draw` calls, if you run the game now, you will see the following:
+
+|  |
+| :--------------------------------------------------------------------------------------------------: |
+| **Figure 6-16: The MonoGame icon drawn on top of the wordmark** |
+
+There are also two additional [**SpriteSortMode**](xref:Microsoft.Xna.Framework.Graphics.SpriteSortMode) values that can be used. These, however, are situational and can have draw backs when using them, so understanding what they are for is important.
+
+The first is [**SpriteSortMode.Texture**](xref:Microsoft.Xna.Framework.Graphics.SpriteSortMode.Texture). This works similar to [**SpriteSortMode.Deferred**](xref:Microsoft.Xna.Framework.Graphics.SpriteSortMode.Deferred) in that draw calls happen in the order they are made. However, before the draw calls are made, they are sorted by texture. This can be helpful when using multiple textures to reduce texture swapping, however it can have unintended results with layering if you are not careful.
+
+The second is [**SpriteSortMode.Immediate**](xref:Microsoft.Xna.Framework.Graphics.SpriteSortMode.Immediate). When using this sort mode, when a draw call is made, it is immediately flushed to the GPU and rendered to the screen, ignoring the layer depth, instead of batched and drawn when [**SpriteBatch.End**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.End) is called. Using this can cause performance issues and should only be used when necessary. We will discuss an example of using this in a later chapter when we discuss shaders, since with [**SpriteSortMode.Immediate**](xref:Microsoft.Xna.Framework.Graphics.SpriteSortMode.Immediate) you can adjust shader parameters for each individual draw call.
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+- You learned about the different parameters of the [**SpriteBatch.Draw**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.Draw(Microsoft.Xna.Framework.Graphics.Texture2D,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Color)) method and how they affect sprite rendering.
+- You learned how the `rotation` parameter works and how to convert between degrees and radians using [**MathHelper.ToRadians**](xref:Microsoft.Xna.Framework.MathHelper.ToRadians(System.Single)).
+- You learned how the `origin` parameter affects sprite positioning, rotation, and scaling.
+- You learned how to use the `scale` parameter to resize sprites uniformly or along individual axes.
+- You explored the [**SpriteEffects**](xref:Microsoft.Xna.Framework.Graphics.SpriteEffects) enum to flip sprites horizontally and vertically.
+- You learned how the `color` parameter can be used to tint sprites and adjust their opacity.
+- You used the `sourceRectangle` parameter to draw specific regions from a texture.
+- You explored sprite layering using the `layerDepth` parameter and different [**SpriteSortMode**](xref:Microsoft.Xna.Framework.Graphics.SpriteSortMode) options.
+
+In the next chapter, we will take what we have learned about working with textures and learn techniques to optimize rendering to reduce texture swapping.
+
+## Test Your Knowledge
+
+1. What is the purpose of the `origin` parameter in SpriteBatch.Draw, and how does it affect position, rotation and scaling?
+
+ :::question-answer
+ The `origin` parameter determines the reference point for the sprite's position, rotation, and scaling. When set to [**Vector2.Zero**](xref:Microsoft.Xna.Framework.Vector2.Zero), the sprite rotates and scales from its upper-left corner. When set to the center of the sprite, the sprite rotates and scales from its center. The origin point also affects where the sprite is positioned relative to the `position` parameter.
+ :::
+
+2. How can you adjust a sprite's opacity using [**SpriteBatch.Draw**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.DrawString(Microsoft.Xna.Framework.Graphics.SpriteFont,System.Text.StringBuilder,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Color,System.Single,Microsoft.Xna.Framework.Vector2,System.Single,Microsoft.Xna.Framework.Graphics.SpriteEffects,System.Single))?
+
+ :::question-answer
+ A sprite's opacity can be adjusted by multiplying the `color` parameter by a value between `0.0f` (fully transparent) and `1.0f` (fully opaque). For example, `Color.White * 0.5f` will render the sprite at 50% opacity.
+ :::
+
+3. How can you flip a sprite horizontally and vertically at the same time using SpriteEffects?
+
+ :::question-answer
+ To flip a sprite both horizontally and vertically, you can combine the SpriteEffects values using the bitwise OR operator (`|`):
+
+ ```cs
+ SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically
+ ```
+
+ :::
+
+4. When using the `sourceRectangle` parameter, what information do you need to specify, and what is its purpose?
+
+ :::question-answer
+ The `sourceRectangle` parameter requires a [**Rectangle**](xref:Microsoft.Xna.Framework.Rectangle) value where the x- and y-coordinates specify the upper-left corner of the region within the texture and the width and height, in pixels, of the region.
+
+ Its purpose is to specify a specific region within a texture to draw, allowing multiple sprites to be drawn from different parts of the same texture.
+ :::
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/color.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/color.cs
new file mode 100644
index 00000000..7ba80984
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/color.cs
@@ -0,0 +1,30 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw the texture
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ Window.ClientBounds.Width,
+ Window.ClientBounds.Height) * 0.5f,
+ null, // sourceRectangle
+ Color.Green, // color
+ 0.0f, // rotation
+ new Vector2( // origin
+ _logo.Width,
+ _logo.Height) * 0.5f,
+ 1.0f, // scale
+ SpriteEffects.None, // effects
+ 0.0f
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/draw.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/draw.cs
new file mode 100644
index 00000000..1cb3b5c8
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/draw.cs
@@ -0,0 +1,16 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw the texture
+ SpriteBatch.Draw(_logo, Vector2.Zero, Color.White);
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/draw_all_params.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/draw_all_params.cs
new file mode 100644
index 00000000..5bb7d74b
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/draw_all_params.cs
@@ -0,0 +1,28 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw the texture
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ (Window.ClientBounds.Width * 0.5f) - (_logo.Width * 0.5f),
+ (Window.ClientBounds.Height * 0.5f) - (_logo.Height * 0.5f)),
+ null, // sourceRectangle
+ Color.White, // color
+ 0.0f, // rotation
+ Vector2.Zero, // origin
+ 1.0f, // scale
+ SpriteEffects.None, // effects
+ 0.0f // layerDepth
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/draw_center.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/draw_center.cs
new file mode 100644
index 00000000..447d7795
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/draw_center.cs
@@ -0,0 +1,22 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw the logo texture
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ (Window.ClientBounds.Width * 0.5f) - (_logo.Width * 0.5f),
+ (Window.ClientBounds.Height * 0.5f) - (_logo.Height * 0.5f)),
+ Color.White // color
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/draw_center_wrong.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/draw_center_wrong.cs
new file mode 100644
index 00000000..553702f6
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/draw_center_wrong.cs
@@ -0,0 +1,23 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw the logo texture
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ Window.ClientBounds.Width,
+ Window.ClientBounds.Height)
+ * 0.5f,
+ Color.White // color
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/layerdepth.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/layerdepth.cs
new file mode 100644
index 00000000..79e57f76
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/layerdepth.cs
@@ -0,0 +1,53 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // The bounds of the icon within the texture.
+ Rectangle iconSourceRect = new Rectangle(0, 0, 128, 128);
+
+ // The bounds of the word mark within the texture.
+ Rectangle wordmarkSourceRect = new Rectangle(150, 34, 458, 58);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw only the icon portion of the texture.
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ Window.ClientBounds.Width,
+ Window.ClientBounds.Height) * 0.5f,
+ iconSourceRect, // sourceRectangle
+ Color.White, // color
+ 0.0f, // rotation
+ new Vector2( // origin
+ iconSourceRect.Width,
+ iconSourceRect.Height) * 0.5f,
+ 1.0f, // scale
+ SpriteEffects.None, // effects
+ 1.0f // layerDepth
+ );
+
+ // Draw only the word mark portion of the texture.
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ Window.ClientBounds.Width,
+ Window.ClientBounds.Height) * 0.5f,
+ wordmarkSourceRect, // sourceRectangle
+ Color.White, // color
+ 0.0f, // rotation
+ new Vector2( // origin
+ wordmarkSourceRect.Width,
+ wordmarkSourceRect.Height) * 0.5f,
+ 1.0f, // scale
+ SpriteEffects.None, // effects
+ 0.0f // layerDepth
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/opacity.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/opacity.cs
new file mode 100644
index 00000000..f204d296
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/opacity.cs
@@ -0,0 +1,30 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw the texture
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ Window.ClientBounds.Width,
+ Window.ClientBounds.Height) * 0.5f,
+ null, // sourceRectangle
+ Color.White * 0.5f, // color
+ 0.0f, // rotation
+ new Vector2( // origin
+ _logo.Width,
+ _logo.Height) * 0.5f,
+ 1.0f, // scale
+ SpriteEffects.None, // effects
+ 0.0f
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/origin.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/origin.cs
new file mode 100644
index 00000000..646e2039
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/origin.cs
@@ -0,0 +1,30 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw the texture
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ Window.ClientBounds.Width,
+ Window.ClientBounds.Height) * 0.5f,
+ null, // sourceRectangle
+ Color.White, // color
+ MathHelper.ToRadians(90), // rotation
+ new Vector2( // origin
+ _logo.Width,
+ _logo.Height) * 0.5f,
+ 1.0f, // scale
+ SpriteEffects.None, // effects
+ 0.0f // layerDepth
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/rotation.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/rotation.cs
new file mode 100644
index 00000000..af26ea6f
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/rotation.cs
@@ -0,0 +1,28 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw the texture
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ (Window.ClientBounds.Width * 0.5f) - (_logo.Width * 0.5f),
+ (Window.ClientBounds.Height * 0.5f) - (_logo.Height * 0.5f)),
+ null, // sourceRectangle
+ Color.White, // color
+ MathHelper.ToRadians(90), // rotation
+ Vector2.Zero, // origin
+ 1.0f, // scale
+ SpriteEffects.None, // effects
+ 0.0f // layerDepth
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/scale.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/scale.cs
new file mode 100644
index 00000000..790cc1ca
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/scale.cs
@@ -0,0 +1,30 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw the texture
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ Window.ClientBounds.Width,
+ Window.ClientBounds.Height) * 0.5f,
+ null, // sourceRectangle
+ Color.White, // color
+ 0.0f, // rotation
+ new Vector2( // origin
+ _logo.Width,
+ _logo.Height) * 0.5f,
+ 1.5f, // scale
+ SpriteEffects.None, // effects
+ 0.0f // layerDepth
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/scale_no_origin.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/scale_no_origin.cs
new file mode 100644
index 00000000..3f0b7c9e
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/scale_no_origin.cs
@@ -0,0 +1,28 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw the texture
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ Window.ClientBounds.Width,
+ Window.ClientBounds.Height) * 0.5f,
+ null, // sourceRectangle
+ Color.White, // color
+ 0.0f, // rotation
+ Vector2.Zero, // origin
+ 1.5f, // scale
+ SpriteEffects.None, // effects
+ 0.0f //layerDepth
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/scale_vector2.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/scale_vector2.cs
new file mode 100644
index 00000000..d20c0d7c
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/scale_vector2.cs
@@ -0,0 +1,30 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw the texture
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ Window.ClientBounds.Width,
+ Window.ClientBounds.Height) * 0.5f,
+ null, // sourceRectangle
+ Color.White, // color
+ 0.0f, // rotation
+ new Vector2( // origin
+ _logo.Width,
+ _logo.Height) * 0.5f,
+ new Vector2(1.5f, 0.5f), // scale
+ SpriteEffects.None, // effects
+ 0.0f // layerDepth
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/sortmode.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/sortmode.cs
new file mode 100644
index 00000000..37518074
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/sortmode.cs
@@ -0,0 +1,53 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // The bounds of the icon within the texture.
+ Rectangle iconSourceRect = new Rectangle(0, 0, 128, 128);
+
+ // The bounds of the word mark within the texture.
+ Rectangle wordmarkSourceRect = new Rectangle(150, 34, 458, 58);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin(sortMode: SpriteSortMode.FrontToBack);
+
+ // Draw only the icon portion of the texture.
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ Window.ClientBounds.Width,
+ Window.ClientBounds.Height) * 0.5f,
+ iconSourceRect, // sourceRectangle
+ Color.White, // color
+ 0.0f, // rotation
+ new Vector2( // origin
+ iconSourceRect.Width,
+ iconSourceRect.Height) * 0.5f,
+ 1.0f, // scale
+ SpriteEffects.None, // effects
+ 1.0f // layerDepth
+ );
+
+ // Draw only the word mark portion of the texture.
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ Window.ClientBounds.Width,
+ Window.ClientBounds.Height) * 0.5f,
+ wordmarkSourceRect, // sourceRectangle
+ Color.White, // color
+ 0.0f, // rotation
+ new Vector2( // origin
+ wordmarkSourceRect.Width,
+ wordmarkSourceRect.Height) * 0.5f,
+ 1.0f, // scale
+ SpriteEffects.None, // effects
+ 0.0f // layerDepth
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/sourcerectangle.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/sourcerectangle.cs
new file mode 100644
index 00000000..8e2b2aae
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/sourcerectangle.cs
@@ -0,0 +1,53 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // The bounds of the icon within the texture.
+ Rectangle iconSourceRect = new Rectangle(0, 0, 128, 128);
+
+ // The bounds of the word mark within the texture.
+ Rectangle wordmarkSourceRect = new Rectangle(150, 34, 458, 58);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw only the icon portion of the texture.
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ Window.ClientBounds.Width,
+ Window.ClientBounds.Height) * 0.5f,
+ iconSourceRect, // sourceRectangle
+ Color.White, // color
+ 0.0f, // rotation
+ new Vector2( // origin
+ iconSourceRect.Width,
+ iconSourceRect.Height) * 0.5f,
+ 1.0f, // scale
+ SpriteEffects.None, // effects
+ 0.0f // layerDepth
+ );
+
+ // Draw only the word mark portion of the texture.
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ Window.ClientBounds.Width,
+ Window.ClientBounds.Height) * 0.5f,
+ wordmarkSourceRect, // sourceRectangle
+ Color.White, // color
+ 0.0f, // rotation
+ new Vector2( // origin
+ wordmarkSourceRect.Width,
+ wordmarkSourceRect.Height) * 0.5f,
+ 1.0f, // scale
+ SpriteEffects.None, // effects
+ 0.0f // layerDepth
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/spriteeffects.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/spriteeffects.cs
new file mode 100644
index 00000000..234cd235
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/spriteeffects.cs
@@ -0,0 +1,30 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw the texture
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ Window.ClientBounds.Width,
+ Window.ClientBounds.Height) * 0.5f,
+ null, // sourceRectangle
+ Color.White, // color
+ 0.0f, // rotation
+ new Vector2( // origin
+ _logo.Width,
+ _logo.Height) * 0.5f,
+ 1.0f, // scale
+ SpriteEffects.FlipHorizontally, // effects
+ 0.0f // layerDepth
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/snippets/spriteeffects_flags.cs b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/spriteeffects_flags.cs
new file mode 100644
index 00000000..8f2d4f43
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/spriteeffects_flags.cs
@@ -0,0 +1,31 @@
+protected override void Draw(GameTime gameTime)
+{
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin();
+
+ // Draw the texture
+ SpriteBatch.Draw(
+ _logo, // texture
+ new Vector2( // position
+ Window.ClientBounds.Width,
+ Window.ClientBounds.Height) * 0.5f,
+ null, // sourceRectangle
+ Color.White, // color
+ 0.0f, // rotation
+ new Vector2( // origin
+ _logo.Width,
+ _logo.Height) * 0.5f,
+ 1.0f, // scale
+ SpriteEffects.FlipHorizontally | // effects
+ SpriteEffects.FlipVertically,
+ 0.0f // layerDepth
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/06_working_with_textures/videos/top-left-origin-rotation-example.webm b/articles/tutorials/building_2d_games/06_working_with_textures/videos/top-left-origin-rotation-example.webm
new file mode 100644
index 00000000..48849ae5
Binary files /dev/null and b/articles/tutorials/building_2d_games/06_working_with_textures/videos/top-left-origin-rotation-example.webm differ
diff --git a/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/images/atlas.png b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/images/atlas.png
new file mode 100644
index 00000000..7238c847
Binary files /dev/null and b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/images/atlas.png differ
diff --git a/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/images/logo-texture-regions.png b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/images/logo-texture-regions.png
new file mode 100644
index 00000000..09eb566c
Binary files /dev/null and b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/images/logo-texture-regions.png differ
diff --git a/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/images/mgcb-editor-copy.png b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/images/mgcb-editor-copy.png
new file mode 100644
index 00000000..54c41e75
Binary files /dev/null and b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/images/mgcb-editor-copy.png differ
diff --git a/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/images/pong-atlas.png b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/images/pong-atlas.png
new file mode 100644
index 00000000..7ca80388
Binary files /dev/null and b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/images/pong-atlas.png differ
diff --git a/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/images/slime-and-bat-rendered.png b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/images/slime-and-bat-rendered.png
new file mode 100644
index 00000000..e30dcbae
Binary files /dev/null and b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/images/slime-and-bat-rendered.png differ
diff --git a/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/index.md b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/index.md
new file mode 100644
index 00000000..6d575134
--- /dev/null
+++ b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/index.md
@@ -0,0 +1,279 @@
+---
+title: "Chapter 07: Optimizing Texture Rendering"
+description: Explore optimization techniques when rendering textures using a texture atlas.
+---
+
+In [Chapter 06](../06_working_with_textures/index.md), you learned how to load and render textures using [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch). While rendering individual textures works well for simple games, it can lead to performance issues as your game grows more complex. In this chapter, we will explore how to optimize texture rendering by reducing texture swaps and creating reusable components for better organization.
+
+In this chapter, you will:
+
+- Learn about texture swapping and its impact on performance.
+- Explore texture atlases as a solution for optimizing texture rendering.
+- Create reusable classes to optimize and simplify texture management and rendering.
+
+By the end of this chapter, you will understand how to organize your game's textures for optimal performance and have a flexible texture atlas management system for your future game projects.
+
+## Texture Swapping
+
+Every time the [**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,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Graphics.SpriteEffects,System.Single)) method is executed with a different *texture* parameter than the previous [**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,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Graphics.SpriteEffects,System.Single)) method call, a *texture swap* occurs, unbinding the current texture on the GPU and binding the new texture.
+
+> [!NOTE]
+> A texture swap occurs when the GPU needs to switch between different textures during rendering. While each individual swap may seem trivial, the cumulative effect in a complex game can significantly impact performance.
+
+For example, here is are the simplified draw call for an example Pong game:
+
+[!code-csharp[](./snippets/pong_example.cs)]
+
+In the above example:
+
+1. The paddle texture is bound to the GPU so the left player paddle can be drawn.
+2. The paddle texture is unbound from the GPU and the ball texture is bound so that the ball can be drawn (Texture Swap #1).
+3. The ball texture is unbound from the GPU and the paddle texture is bound again so the right player paddle can be drawn (Texture Swap #2).
+
+These texture swaps, while negligible in this example, can become a performance issue in a full game where you might be drawing hundreds or thousands of sprites per frame.
+
+### Attempting to Optimize Draw Order
+
+One approach to get around this could be to optimize the order of the draw calls to minimize texture swaps For example, if we reorder the draw calls from the previous example so that both paddles are drawn first and then the ball, the number of texture swaps is reduced from two to one:
+
+[!code-csharp[](./snippets/draw_order.cs)]
+
+However this is not a scalable solution. In a real game with dozens of different textures and complex draw orders for layered sprites, UI elements, particles, etc., managing draw order by texture becomes impractical and will conflict with desired visual layering.
+
+## What is a Texture Atlas
+
+A texture atlas (also known as a sprite sheet) is a large image file that contains multiple smaller images packed together. Instead of loading separate textures for each sprite, you load the single texture file with all the images combined like a scrapbook where all your photos are arranged on the same page.
+
+> [!NOTE]
+> Using a texture atlas not only eliminates texture swaps but also reduces memory usage and simplifies asset management since you are loading and tracking a single texture instead of many individual ones.
+
+In the Pong example, imagine taking the paddle and ball image and combining them into a single image file like in *Figure 7-1* below:
+
+|  |
+|:------------------------------------------------------------------:|
+| **Figure 7-1: Pong Texture Atlas Example** |
+
+Now when we draw these images, we would be using the same texture and just specify the source rectangles for the paddle or ball when needed, completely eliminating texture swaps.
+
+[!code-csharp[](./snippets/pong_texture_atlas_example.cs)]
+
+While using the single texture with source rectangles solves the potential performance issues, managing multiple source rectangles in variables can become complex as your game grows. In the Pong example above, we are already tracking the source rectangles for both the paddle and ball sprites. Imagine scaling this up to a game with dozens of different images, each potentially needing their own position, rotation, scale, and other rendering properties.
+
+To better organize this complexity, we can apply object-oriented design principles to create classes that encapsulates the information needed.
+
+## The TextureRegion Class
+
+In [Chapter 06](../06_working_with_textures/index.md#source-rectangle), we learned about using the `sourceRectangle` parameter to reuse the same texture when rendering sprites but specifying different regions within the texture to render. For our next step, we will build on this and create a class called `TextureRegion`.
+
+We are going to add this class to the class library we created in [Chapter 04](../04_creating_a_class_library/index.md). Perform the following:
+
+1. Add new folder in the *MonoGameLibrary* project named `Graphics`
+2. Create a new file named `TextureRegion.cs` inside the *Graphics* folder you just created.
+3. Add the following code for the foundation of the `TextureRegion` class to the file:
+
+[!code-csharp[](./snippets/textureregion.cs#declaration)]
+
+> [!NOTE]
+> The `TextureRegion.cs` class file is placed in the *MonoGame/Graphics* folder and the class uses the `MonoGameLibrary.Graphics` [namespace](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/namespaces#namespaces-overview) to keep graphics-related classes organized together. As we add more functionality to the library, we will continue to use directories and namespaces to maintain a clean structure.
+
+We will add several components to this class in sequence. Each section below should be added to the `TextureRegion` class in the order presented between the brackets ` { } ` of the class definition. As we go through each part, the class will gradually take shape to handle all the texture handling behavior we need.
+
+### TextureRegion Members
+
+The `TextureRegion` class will utilize four properties to define and manage a region within a texture. Add the following properties:
+
+[!code-csharp[](./snippets/textureregion.cs#properties)]
+
+The `Texture` and `SourceRectangle` properties work together to define where the region is located: `Texture` specifies which texture contains the region, while `SourceRectangle` defines its exact location and size within that texture. The `Width` and `Height` properties provide convenient access to the region's dimensions without having to access the SourceRectangle property directly.
+
+### TextureRegion Constructor
+
+The `TextureRegion` class provides two ways to create a new texture region.
+
+Add the following constructors:
+
+[!code-csharp[](./snippets/textureregion.cs#ctors)]
+
+* The default constructor creates an empty texture region that can be configured later.
+* The parameterized constructor allows you to define the region's source texture and boundary in a single step.
+
+The second constructor provides a convenient way to create texture regions when you know the exact location and dimensions within the source texture upfront.
+
+### TextureRegion Methods
+
+Finally, the `TextureRegion` class will provide three overloaded Draw methods to render the texture region. Add the following methods:
+
+[!code-csharp[](./snippets/textureregion.cs#methods)]
+
+These methods provide flexible options for rendering the texture region, similar to what the [**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)) method does:
+
+- The simplest overload requires only position and color.
+- A second overload exposes all rendering parameters while allowing for a single float value to be applied to both axes for scaling.
+- The third overload is the most flexible, offering all rendering parameters and independent x- and y-axis scaling.
+
+Only the last `Draw` method actually uses the `Texture` specified in the class, as the other two methods both depend on this final implementation, simplifying the code needed to actually do the drawing.
+
+## The TextureAtlas Class
+
+In the [What is a Texture Atlas](#what-is-a-texture-atlas) section above, a texture atlas was described as a scrap book that holds all of the individual sprites for the game. These individual sprites can now be represented by the `TextureRegion` class we just created. Now, we will create the `TextureAtlas` class to represent the collection of the regions that make up all of our sprites.
+
+Just like the `TextureRegion` class, we are going to add this to the class library. In the *Graphics* folder within the *MonoGameLibrary* project, add a new file named `TextureAtlas.cs`, then add the following code for the foundation of the `TextureAtlas` class:
+
+[!code-csharp[](./snippets/textureatlas.cs#declaration)]
+
+As before, each section below adds more functionality to this class, one after the other within the `TextureAtlas` class.
+
+### TextureAtlas Members
+
+The `TextureAtlas` class needs two key members to manage texture regions. Add the following:
+
+[!code-csharp[](./snippets/textureatlas.cs#members)]
+
+The private `_regions` dictionary stores named texture regions, allowing us to retrieve specific regions by name, while the `Texture` property holds the source texture that contains all the regions. Together, these members enable the atlas to manage multiple texture regions from a single source texture.
+
+### TextureAtlas Constructors
+
+The `TextureAtlas` class provides two ways to create a new atlas.
+
+Add the following constructors:
+
+[!code-csharp[](./snippets/textureatlas.cs#ctors)]
+
+* The default constructor creates an empty atlas that can be configured later.
+* The parameterized constructor allows you to specify the source texture immediately.
+
+Both constructors initialize the `_regions` dictionary so that it is ready to be used either way.
+
+### TextureAtlas Methods
+
+Finally, The `TextureAtlas` class will provide methods for managing texture regions and creating atlases from configuration files. Add the following methods:
+
+[!code-csharp[](./snippets/textureatlas.cs#methods)]
+
+These methods serve different purposes in managing the texture atlas:
+
+1. Region Management
+ - `AddRegion`: Creates a new `TextureRegion` at the specified location in the atlas.
+ - `GetRegion`: Retrieves a previously added region by its name.
+ - `RemoveRegion`: Removes a specific region by its name.
+ - `Clear`: Removes all regions from the atlas.
+2. Atlas Creation
+ - `FromFile`: creates a new `TextureAtlas` from an XML configuration file. This method will load the source texture then create and add the regions based on the XML configuration. We will look more into using the XML configuration in a moment.
+
+## Using the TextureAtlas Class
+
+No we can put our new `TextureAtlas` class to use by exploring two approaches; creating an atlas manually and using XML configuration. So far, we have been practicing using textures with the MonoGame logo. Now we will use a new texture atlas that contains various sprites we will need for our game.
+
+Download the texture atlas by right-clicking the following image and saving it as atlas.png:
+
+|  |
+|:-----------------------------------------------------------------:|
+| **Figure 7-2: The texture atlas for our game** |
+
+> [!TIP]
+> You may notice that our texture atlas image has some empty areas, which seems like a waste. Its dimensions are 256x256 pixels when it could have just been 240x160 pixels.
+>
+> **This is intentional.**
+>
+> Game graphics often use texture dimensions that are powers of 2 (128, 256, 512, 1024, etc.) for technical reasons. While modern graphics hardware can handle any texture size, power-of-2 dimensions provide better memory efficiency and more precise rendering. When pixel coordinates are converted to texture coordinates during rendering, powers of 2 can be represented more precisely in floating-point calculations, helping prevent visual artifacts like texture seams.
+>
+> This is not critical for simple 2D games, but adopting this practice early will serve you well as your games become more complex.
+
+Add this texture atlas to your content project using the MGCB Editor:
+
+1. Open the *Content.mgcb* file in the MGCB Editor
+2. In the editor, right-click the *images* folder and choose *Add > Existing item...*.
+3. Navigate to and choose the *atlas.png* file you downloaded to add it.
+4. Save the changes and close the MGCB Editor.
+
+> [!TIP]
+> If you need a refresher on adding content using the MGCB Editor, you can revisit the [Chapter 05: The Content Pipeline](../05_content_pipeline/index.md).
+
+First, we will explore creating the texture atlas and defining the texture regions directly in code. Replace the contents of `Game1.cs` with the following:
+
+[!code-csharp[](./snippets/game1/textureatlas_usage.cs?highlight=5,11-15,31-47,65-75)]
+
+The key changes in this implementation are:
+
+1. The `_logo` field was removed.
+2. Added `TextureRegion` members for the slime and bat sprites.
+3. In [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent):
+ - Removed loading the logo texture.
+ - Created a `TextureAtlas` with the atlas texture.
+ - Added regions for both the slime and the bat.
+ - Retrieved the regions using their names.
+4. Updated [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) to:
+ - Draw the slime at a scale factor of 4.
+ - Draw the bat 10 pixels to the right of the bat based on the slime's `Width` property, at a scale of 4
+
+Running the game now shows both sprites in the upper-left corner:
+
+|  |
+|:------------------------------------------------------------------------------------------------------------------------------------------------:|
+| **Figure 7-3: The slime and bat texture regions being rendered in the upper-left corner of the game window** |
+
+While manual creation works for a few sprites, managing many regions becomes cumbersome. Now we will explore the `TextureAtlas.FromFile` method to load our atlas configuration from XML instead. Perform the following:
+
+1. Open the *Content.mgcb* file in the MGCB Editor
+1. Create a new file named `atlas-definition` in the *Content/images* folder using "Add -> New Item -> XML Content".
+1. In the properties panel at the bottom for the `atlas-definition.xml` file, change the *`Build Action`* property from `Build` to *`Copy`*.
+1. Save the changes and close the MGCB Editor
+1. Open the new `atlas-definition.xml` file in your code editor (The MGCB editor cannot edit files, only manage the what content it builds)
+1. Replace the contents of the XML file with the following:
+
+ [!code-xml[](./snippets/atlas_definition.xml)]
+
+
+ |  |
+ |:---------------------------------------------------------------------------------------------------------------------------------------------------:|
+ | **Figure 7-4: The atlas-definition.xml file added to the content project with the Build Action property set to Copy** |
+
+
+ > [!TIP]
+ > Using the content pipeline to copy files ensures they are placed in the correct location alongside other game content. While there are other methods (like editing the .csproj), this approach keeps asset management centralized
+
+1. Update the contents of `Game1.cs` with the following code:
+
+ [!code-csharp[](./snippets/game1/textureatlas_xml_usage.cs?highlight=31-32)]
+
+The key improvements here is in [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent), where we now:
+
+- Create an atlas from the XML configuration file.
+- Let the `TextureAtlas.FromFile` method handle texture loading and region creation.
+- Removed the manual creation of regions in code.
+
+This configuration based approached is advantageous because we can now add new and modify existing regions within the atlas without having to change code and/or recompile. This also keeps the sprite definitions separate from the game logic.
+
+Running the game now will show the same results as *Figure 7-4* above, with the slime and bat texture regions rendered in the upper-left corner of the game window.
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+- Learned about texture swapping and its impact on performance
+- Explored texture atlases as a solution for optimizing texture rendering
+- Learned what a class library is and the benefits of using one.
+- Created reusable `TextureRegion` and `TextureAtlas` classes to optimize and simplify texture management.
+- Learned how to include assets in the content pipeline that should only be copied and not processed.
+
+In the next chapter, we will build on the concepts of the `TextureAtlas` and explore creating the `Sprite` and `AnimatedSprite` classes to further simplify managing and rendering sprites.
+
+## Test Your Knowledge
+
+1. What is a texture swap and why can it impact performance?
+
+ :::question-answer
+ A texture swap occurs when the GPU needs to unbind one texture and bind another between draw calls. While individual swaps may seem trivial, they can significantly impact performance in games with many sprites as each swap is an expensive GPU operation.
+ :::
+
+2. Name a benefit of using a texture atlas.
+
+ :::question-answer
+ Any of the following are benefits of using a texture atlas:
+
+ - Eliminates texture swaps by using a single texture
+ - Reduces memory usage
+ - Simplifies asset management
+ - Improves rendering performance
+
+ :::
diff --git a/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/atlas_definition.xml b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/atlas_definition.xml
new file mode 100644
index 00000000..b5e8c3f8
--- /dev/null
+++ b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/atlas_definition.xml
@@ -0,0 +1,8 @@
+
+
+ images/atlas
+
+
+
+
+
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/draw_order.cs b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/draw_order.cs
new file mode 100644
index 00000000..7d48c3dd
--- /dev/null
+++ b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/draw_order.cs
@@ -0,0 +1,5 @@
+// Render the left and right paddles first.
+// This reduces the number of texture swaps needed from two to one.
+_spriteBatch.Draw(paddleTexture, _leftPaddlePosition, Color.White);
+_spriteBatch.Draw(paddleTexture, _rightPaddlePosition, Color.White);
+_spriteBatch.Draw(ballTexture, _ballPosition, Color.White);
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/game1/textureatlas_usage.cs b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/game1/textureatlas_usage.cs
new file mode 100644
index 00000000..b461b107
--- /dev/null
+++ b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/game1/textureatlas_usage.cs
@@ -0,0 +1,79 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary;
+using MonoGameLibrary.Graphics;
+
+namespace DungeonSlime;
+
+public class Game1 : Core
+{
+ // texture region that defines the slime sprite in the atlas.
+ private TextureRegion _slime;
+
+ // texture region that defines the bat sprite in the atlas.
+ private TextureRegion _bat;
+
+ public Game1() : base("Dungeon Slime", 1280, 720, false)
+ {
+
+ }
+
+ protected override void Initialize()
+ {
+ // TODO: Add your initialization logic here
+
+ base.Initialize();
+ }
+
+ protected override void LoadContent()
+ {
+ // Load the atlas texture using the content manager
+ Texture2D atlasTexture = Content.Load("images/atlas");
+
+ // Create a TextureAtlas instance from the atlas
+ TextureAtlas atlas = new TextureAtlas(atlasTexture);
+
+ // add the slime region to the atlas.
+ atlas.AddRegion("slime", 0, 0, 20, 20);
+
+ // add the bat region to the atlas.
+ atlas.AddRegion("bat", 20, 0, 20, 20);
+
+ // retrieve the slime region from the atlas.
+ _slime = atlas.GetRegion("slime");
+
+ // retrieve the bat region from the atlas.
+ _bat = atlas.GetRegion("bat");
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
+ Exit();
+
+ // TODO: Add your update logic here
+
+ base.Update(gameTime);
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
+
+ // Draw the slime texture region at a scale of 4.0
+ _slime.Draw(SpriteBatch, Vector2.Zero, Color.White, 0.0f, Vector2.One, 4.0f, SpriteEffects.None, 0.0f);
+
+ // Draw the bat texture region 10px to the right of the slime at a scale of 4.0
+ _bat.Draw(SpriteBatch, new Vector2(_slime.Width * 4.0f + 10, 0), Color.White, 0.0f, Vector2.One, 4.0f, SpriteEffects.None, 1.0f);
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/game1/textureatlas_xml_usage.cs b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/game1/textureatlas_xml_usage.cs
new file mode 100644
index 00000000..6b3850ce
--- /dev/null
+++ b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/game1/textureatlas_xml_usage.cs
@@ -0,0 +1,70 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary;
+using MonoGameLibrary.Graphics;
+
+namespace DungeonSlime;
+
+public class Game1 : Core
+{
+ // texture region that defines the slime sprite in the atlas.
+ private TextureRegion _slime;
+
+ // texture region that defines the bat sprite in the atlas.
+ private TextureRegion _bat;
+
+ public Game1() : base("Dungeon Slime", 1280, 720, false)
+ {
+
+ }
+
+ protected override void Initialize()
+ {
+ // TODO: Add your initialization logic here
+
+ base.Initialize();
+ }
+
+ protected override void LoadContent()
+ {
+ // Create the texture atlas from the XML configuration file
+ TextureAtlas atlas = TextureAtlas.FromFile(Content, "images/atlas-definition.xml");
+
+ // retrieve the slime region from the atlas.
+ _slime = atlas.GetRegion("slime");
+
+ // retrieve the bat region from the atlas.
+ _bat = atlas.GetRegion("bat");
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
+ Exit();
+
+ // TODO: Add your update logic here
+
+ base.Update(gameTime);
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
+
+ // Draw the slime texture region at a scale of 4.0
+ _slime.Draw(SpriteBatch, Vector2.Zero, Color.White, 0.0f, Vector2.One, 4.0f, SpriteEffects.None, 0.0f);
+
+ // Draw the bat texture region 10px to the right of the slime at a scale of 4.0
+ _bat.Draw(SpriteBatch, new Vector2(_slime.Width * 4.0f + 10, 0), Color.White, 0.0f, Vector2.One, 4.0f, SpriteEffects.None, 1.0f);
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/pong_example.cs b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/pong_example.cs
new file mode 100644
index 00000000..728506f0
--- /dev/null
+++ b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/pong_example.cs
@@ -0,0 +1,11 @@
+// Using the paddle texture to render the left player paddle.
+// The paddle texture is bound to the GPU.
+_spriteBatch.Draw(paddleTexture, leftPaddlePosition, Color.White);
+
+// Using the ball texture to render the ball
+// A texture swap occurs, unbinding the paddle texture to bind the ball texture.
+_spriteBatch.Draw(ballTexture, ballPosition, Color.White);
+
+// Reusing the paddle texture to draw the right player paddle.
+// A texture swap occurs again, unbinding the ball texture to bind the paddle texture.
+_spriteBatch.Draw(paddleTexture, rightPaddlePosition, Color.White);
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/pong_texture_atlas_example.cs b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/pong_texture_atlas_example.cs
new file mode 100644
index 00000000..242e8d30
--- /dev/null
+++ b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/pong_texture_atlas_example.cs
@@ -0,0 +1,24 @@
+private Texture2D _textureAtlas;
+private Rectangle _paddleSourceRect;
+private Rectangle _ballSourceRect;
+
+protected override void LoadContent()
+{
+ _textureAtlas = Content.Load("pong-atlas");
+ _paddleSourceRect = new Rectangle(0, 0, 32, 32);
+ _ballSourceRect = new Rectangle(32, 0, 32, 32);
+}
+
+protected override void Draw(GameTime gameTime)
+{
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ _spriteBatch.Begin();
+
+ // All draw calls use the same texture, so there is no texture swapping!
+ _spriteBatch.Draw(_textureAtlas, _leftPaddlePosition, _paddleSourceRect, Color.White);
+ _spriteBatch.Draw(_textureAtlas, _rightPaddlePosition, _paddleSourceRect, Color.White);
+ _spriteBatch.Draw(_textureAtlas, _ballPosition, _ballSourceRect, Color.White);
+
+ _spriteBatch.End();
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/textureatlas.cs b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/textureatlas.cs
new file mode 100644
index 00000000..61d1c6e3
--- /dev/null
+++ b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/textureatlas.cs
@@ -0,0 +1,149 @@
+#region declaration
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+using System.Xml.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace MonoGameLibrary.Graphics;
+
+public class TextureAtlas
+{
+
+}
+#endregion
+{
+ #region members
+ private Dictionary _regions;
+
+ ///
+ /// Gets or Sets the source texture represented by this texture atlas.
+ ///
+ public Texture2D Texture { get; set; }
+ #endregion
+
+ #region ctors
+ ///
+ /// Creates a new texture atlas.
+ ///
+ public TextureAtlas()
+ {
+ _regions = 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();
+ }
+ #endregion
+
+ #region methods
+ ///
+ /// Creates a new region and adds it to this texture atlas.
+ ///
+ /// The name to give the texture region.
+ /// The top-left x-coordinate position of the region boundary relative to the top-left corner of the source texture boundary.
+ /// The top-left y-coordinate position of the region boundary relative to the top-left corner of the source texture boundary.
+ /// The width, in pixels, of the region.
+ /// The height, in pixels, of the region.
+ public void AddRegion(string name, int x, int y, int width, int height)
+ {
+ TextureRegion region = new TextureRegion(Texture, x, y, width, height);
+ _regions.Add(name, region);
+ }
+
+ ///
+ /// Gets the region from this texture atlas with the specified name.
+ ///
+ /// The name of the region to retrieve.
+ /// The TextureRegion with the specified name.
+ public TextureRegion GetRegion(string name)
+ {
+ return _regions[name];
+ }
+
+ ///
+ /// Removes the region from this texture atlas with the specified name.
+ ///
+ /// The name of the region to remove.
+ ///
+ public bool RemoveRegion(string name)
+ {
+ return _regions.Remove(name);
+ }
+
+ ///
+ /// Removes all regions from this texture atlas.
+ ///
+ public void Clear()
+ {
+ _regions.Clear();
+ }
+
+ ///
+ /// 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 will 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);
+ }
+ }
+ }
+
+ return atlas;
+ }
+ }
+ }
+ #endregion
+}
diff --git a/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/textureregion.cs b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/textureregion.cs
new file mode 100644
index 00000000..407bb946
--- /dev/null
+++ b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/textureregion.cs
@@ -0,0 +1,122 @@
+#region declaration
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace MonoGameLibrary.Graphics;
+
+///
+/// Represents a rectangular region within a texture.
+///
+public class TextureRegion
+{
+
+}
+#endregion
+{
+ #region properties
+ ///
+ /// Gets or Sets the source texture this texture region is part of.
+ ///
+ public Texture2D Texture { get; set; }
+
+ ///
+ /// Gets or Sets the source rectangle boundary of this texture region within the source texture.
+ ///
+ public Rectangle SourceRectangle { get; set; }
+
+ ///
+ /// Gets the width, in pixels, of this texture region.
+ ///
+ public int Width => SourceRectangle.Width;
+
+ ///
+ /// Gets the height, in pixels, of this texture region.
+ ///
+ public int Height => SourceRectangle.Height;
+ #endregion
+
+ #region ctors
+ ///
+ /// Creates a new texture region.
+ ///
+ public TextureRegion() { }
+
+ ///
+ /// Creates a new texture region using the specified source texture.
+ ///
+ /// The texture to use as the source texture for this texture region.
+ /// The x-coordinate position of the upper-left corner of this texture region relative to the upper-left corner of the source texture.
+ ///
+ /// The width, in pixels, of this texture region.
+ /// The height, in pixels, of this texture region.
+ public TextureRegion(Texture2D texture, int x, int y, int width, int height)
+ {
+ Texture = texture;
+ SourceRectangle = new Rectangle(x, y, width, height);
+ }
+ #endregion
+
+ #region methods
+ ///
+ /// Submit this texture region for drawing in the current batch.
+ ///
+ /// The spritebatch instance used for batching draw calls.
+ /// The xy-coordinate location to draw this texture region on the screen.
+ /// The color mask to apply when drawing this texture region on screen.
+ public void Draw(SpriteBatch spriteBatch, Vector2 position, Color color)
+ {
+ Draw(spriteBatch, position, color, 0.0f, Vector2.Zero, Vector2.One, SpriteEffects.None, 0.0f);
+ }
+
+ ///
+ /// Submit this texture region for drawing in the current batch.
+ ///
+ /// The spritebatch instance used for batching draw calls.
+ /// The xy-coordinate location to draw this texture region on the screen.
+ /// The color mask to apply when drawing this texture region on screen.
+ /// The amount of rotation, in radians, to apply when drawing this texture region on screen.
+ /// The center of rotation, scaling, and position when drawing this texture region on screen.
+ /// The scale factor to apply when drawing this texture region on screen.
+ /// Specifies if this texture region should be flipped horizontally, vertically, or both when drawing on screen.
+ /// The depth of the layer to use when drawing this texture region on screen.
+ public void Draw(SpriteBatch spriteBatch, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth)
+ {
+ Draw(
+ spriteBatch,
+ position,
+ color,
+ rotation,
+ origin,
+ new Vector2(scale, scale),
+ effects,
+ layerDepth
+ );
+ }
+
+ ///
+ /// Submit this texture region for drawing in the current batch.
+ ///
+ /// The spritebatch instance used for batching draw calls.
+ /// The xy-coordinate location to draw this texture region on the screen.
+ /// The color mask to apply when drawing this texture region on screen.
+ /// The amount of rotation, in radians, to apply when drawing this texture region on screen.
+ /// The center of rotation, scaling, and position when drawing this texture region on screen.
+ /// The amount of scaling to apply to the x- and y-axes when drawing this texture region on screen.
+ /// Specifies if this texture region should be flipped horizontally, vertically, or both when drawing on screen.
+ /// The depth of the layer to use when drawing this texture region on screen.
+ public void Draw(SpriteBatch spriteBatch, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth)
+ {
+ spriteBatch.Draw(
+ Texture,
+ position,
+ SourceRectangle,
+ color,
+ rotation,
+ origin,
+ scale,
+ effects,
+ layerDepth
+ );
+ }
+ #endregion
+}
diff --git a/articles/tutorials/building_2d_games/08_the_sprite_class/images/slime-and-bat-rendered.png b/articles/tutorials/building_2d_games/08_the_sprite_class/images/slime-and-bat-rendered.png
new file mode 100644
index 00000000..e30dcbae
Binary files /dev/null and b/articles/tutorials/building_2d_games/08_the_sprite_class/images/slime-and-bat-rendered.png differ
diff --git a/articles/tutorials/building_2d_games/08_the_sprite_class/index.md b/articles/tutorials/building_2d_games/08_the_sprite_class/index.md
new file mode 100644
index 00000000..272ed59e
--- /dev/null
+++ b/articles/tutorials/building_2d_games/08_the_sprite_class/index.md
@@ -0,0 +1,129 @@
+---
+title: "Chapter 08: The Sprite Class"
+description: "Explore creating a reusable Sprite class to efficiently sprites and their rendering properties, including position, rotation, scale, and more."
+---
+
+In [Chapter 07](../07_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:
+
+- Color mask for tinting.
+- Origin for rotation and scale.
+- Scale for size adjustments.
+- Rotation for orientation.
+- Sprite effects to flip horizontally and/or vertically.
+- Layer depth for draw order layering.
+
+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 will solve this by creating a class that encapsulates sprite information and handles rendering.
+
+## The Sprite Class
+
+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.
+
+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.
+
+In the *Graphics* folder within the *MonoGameLibrary* project, add a new file named `Sprite.cs` and add the following code for the foundation of the `Sprite` class to the file:
+
+[!code-csharp[](./snippets/sprite.cs#declaration)]
+
+### Properties
+
+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:
+
+[!code-csharp[](./snippets/sprite.cs#members)]
+
+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.
+
+> [!TIP]
+> The calculated `Width` and `Height` properties make it easier to position sprites relative to each other without manually applying scale factors.
+
+### Constructors
+
+The `Sprite` class provides two ways to create a new sprite.
+
+Add the following constructors:
+
+[!code-csharp[](./snippets/sprite.cs#ctors)]
+
+* The default constructor creates an empty sprite that can be configured later.
+* The parameterized constructor allows you to specify the source texture region for the sprite.
+
+### Methods
+
+Finally, add the following two method to the `Sprite` class:
+
+[!code-csharp[](./snippets/sprite.cs#methods)]
+
+- `CenterOrigin`: Sets the origin point of the sprite to its center.
+
+ > [!NOTE]
+ > 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.
+
+- `Draw`: Uses the `TextureRegion` property to submit the sprite for rendering using the properties of the sprite itself.
+
+## Create Sprites With The TextureAtlas Class
+
+While the `GetRegion` method of the `TextureAtlas` class we created in [Chapter 07](../07_optimizing_texture_rendering/index.md#the-textureatlas-class) works well for retrieving regions, creating sprites requires multiple steps:
+
+1. Get the region by name.
+2. Store it in a variable.
+3. Create a new sprite with that region.
+
+We can simplify this process by adding a sprite creation method to the `TextureAtlas` class. Open the `TextureAtlas.cs` and add the following method:
+
+[!code-csharp[](./snippets/createsprite.cs)]
+
+## Using the Sprite Class
+
+Now we can adjust our game now to use the `Sprite` class instead of just the texture regions. Update the contents of `Game1.cs` with the following:
+
+[!code-csharp[](./snippets/game1.cs?highlight=11-15,34-40,61-65)]
+
+The key changes in this implementation are:
+
+- The `_slime` and `_bat` members were changed from `TextureRegion` to `Sprite`.
+- In [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent)
+ - The `_slime` and `_bat` sprites are now created using the new `TextureAtlas.CreateSprite` method.
+ - Both the `_slime` and `_bat` sprites are given a scale of 4.0f.
+- In [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)), the draw calls were updated to use the `Sprite.Draw` method.
+
+Running the game now will produce the same result as in the previous chapter.
+
+|  |
+|:----------------------------------------------------------------------------------------------------------------------------------------:|
+| **Figure 8-1: The slime and bat sprites being rendered in the upper-left corner of the game window** |
+
+> [!NOTE]
+> Notice how even though we increased the scale of both sprites, the bat sprite is still only 10px to the right of the bat. This is because the `Width` property we created for the `Sprite` class takes into account the scale factor of the sprite as well.
+
+Try adjusting the various properties available for the slime and the bat sprites to see how they affect the rendering.
+
+## Conclusion
+
+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.
+
+In the next chapter, we will build upon the `Sprite` class to create an `AnimatedSprite` class that will allow us to bring our sprites to life through animation.
+
+## Test Your Knowledge
+
+1. What is the benefit of using a Sprite class instead of managing texture regions directly?
+
+ :::question-answer
+ 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.
+ :::
+
+2. Why do the `Width` and `Height` properties of a Sprite take the Scale property into account?
+
+ :::question-answer
+ 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.
+ :::
+
+3. When using the `CenterOrigin` method, why is the origin calculated using the region's dimensions rather than the sprite's scaled dimensions?
+
+ :::question-answer
+ 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.
+ :::
+
+4. What advantage does the `TextureAtlas.CreateSprite` method provide over using `GetRegion`?
+
+ :::question-answer
+ 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.
+ :::
diff --git a/articles/tutorials/building_2d_games/08_the_sprite_class/snippets/createsprite.cs b/articles/tutorials/building_2d_games/08_the_sprite_class/snippets/createsprite.cs
new file mode 100644
index 00000000..0933af68
--- /dev/null
+++ b/articles/tutorials/building_2d_games/08_the_sprite_class/snippets/createsprite.cs
@@ -0,0 +1,10 @@
+///
+/// Creates a new sprite using the region from this texture atlas with the specified name.
+///
+/// The name of the region to create the sprite with.
+/// A new Sprite using the texture region with the specified name.
+public Sprite CreateSprite(string regionName)
+{
+ TextureRegion region = GetRegion(regionName);
+ return new Sprite(region);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/08_the_sprite_class/snippets/game1.cs b/articles/tutorials/building_2d_games/08_the_sprite_class/snippets/game1.cs
new file mode 100644
index 00000000..b2dff791
--- /dev/null
+++ b/articles/tutorials/building_2d_games/08_the_sprite_class/snippets/game1.cs
@@ -0,0 +1,72 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary;
+using MonoGameLibrary.Graphics;
+
+namespace DungeonSlime;
+
+public class Game1 : Core
+{
+ // Defines the slime sprite.
+ private Sprite _slime;
+
+ // Defines the bat sprite.
+ private Sprite _bat;
+
+ public Game1() : base("Dungeon Slime", 1280, 720, false)
+ {
+
+ }
+
+ protected override void Initialize()
+ {
+ // TODO: Add your initialization logic here
+
+ base.Initialize();
+ }
+
+ protected override void LoadContent()
+ {
+ // Create the texture atlas from the XML configuration file
+ TextureAtlas atlas = TextureAtlas.FromFile(Content, "images/atlas-definition.xml");
+
+ // Create the slime sprite from the atlas.
+ _slime = atlas.CreateSprite("slime");
+ _slime.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the bat sprite from the atlas.
+ _bat = atlas.CreateSprite("bat");
+ _bat.Scale = new Vector2(4.0f, 4.0f);
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
+ Exit();
+
+ // TODO: Add your update logic here
+
+ base.Update(gameTime);
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
+
+ // Draw the slime sprite.
+ _slime.Draw(SpriteBatch, Vector2.One);
+
+ // Draw the bat sprite 10px to the right of the slime.
+ _bat.Draw(SpriteBatch, new Vector2(_slime.Width + 10, 0));
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/08_the_sprite_class/snippets/sprite.cs b/articles/tutorials/building_2d_games/08_the_sprite_class/snippets/sprite.cs
new file mode 100644
index 00000000..13e8bd04
--- /dev/null
+++ b/articles/tutorials/building_2d_games/08_the_sprite_class/snippets/sprite.cs
@@ -0,0 +1,119 @@
+#region declaration
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace MonoGameLibrary.Graphics;
+
+public class Sprite
+{
+
+}
+#endregion
+{
+ #region members
+ ///
+ /// Gets or Sets the source texture region represented by this sprite.
+ ///
+ public TextureRegion Region { get; set; }
+
+ ///
+ /// Gets or Sets the color mask to apply when rendering this sprite.
+ ///
+ ///
+ /// Default value is Color.White
+ ///
+ public Color Color { get; set; } = Color.White;
+
+ ///
+ /// Gets or Sets the amount of rotation, in radians, to apply when rendering this sprite.
+ ///
+ ///
+ /// Default value is 0.0f
+ ///
+ public float Rotation { get; set; } = 0.0f;
+
+ ///
+ /// Gets or Sets the scale factor to apply to the x- and y-axes when rendering this sprite.
+ ///
+ ///
+ /// Default value is Vector2.One
+ ///
+ public Vector2 Scale { get; set; } = Vector2.One;
+
+ ///
+ /// Gets or Sets the xy-coordinate origin point, relative to the top-left corner, of this sprite.
+ ///
+ ///
+ /// Default value is Vector2.Zero
+ ///
+ public Vector2 Origin { get; set; } = Vector2.Zero;
+
+ ///
+ /// Gets or Sets the sprite effects to apply when rendering this sprite.
+ ///
+ ///
+ /// Default value is SpriteEffects.None
+ ///
+ public SpriteEffects Effects { get; set; } = SpriteEffects.None;
+
+ ///
+ /// Gets or Sets the layer depth to apply when rendering this sprite.
+ ///
+ ///
+ /// Default value is 0.0f
+ ///
+ public float LayerDepth { get; set; } = 0.0f;
+
+ ///
+ /// Gets the width, in pixels, of this sprite.
+ ///
+ ///
+ /// Width is calculated by multiplying the width of the source texture region by the x-axis scale factor.
+ ///
+ public float Width => Region.Width * Scale.X;
+
+ ///
+ /// Gets the height, in pixels, of this sprite.
+ ///
+ ///
+ /// Height is calculated by multiplying the height of the source texture region by the y-axis scale factor.
+ ///
+ public float Height => Region.Height * Scale.Y;
+ #endregion
+
+ #region ctors
+ ///
+ /// Creates a new sprite.
+ ///
+ public Sprite() { }
+
+ ///
+ /// Creates a new sprite using the specified source texture region.
+ ///
+ /// The texture region to use as the source texture region for this sprite.
+ public Sprite(TextureRegion region)
+ {
+ Region = region;
+ }
+ #endregion
+
+ #region methods
+ ///
+ /// Sets the origin of this sprite to the center
+ ///
+ public void CenterOrigin()
+ {
+ Origin = new Vector2(Region.Width, Region.Height) * 0.5f;
+ }
+
+ ///
+ /// Submit this sprite for drawing to the current batch.
+ ///
+ /// The SpriteBatch instance used for batching draw calls.
+ /// The xy-coordinate position to render this sprite at.
+ public void Draw(SpriteBatch spriteBatch, Vector2 position)
+ {
+ Region.Draw(spriteBatch, position, Color, Rotation, Origin, Scale, Effects, LayerDepth);
+ }
+ #endregion
+}
diff --git a/articles/tutorials/building_2d_games/09_the_animatedsprite_class/images/bat-animation-example.gif b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/images/bat-animation-example.gif
new file mode 100644
index 00000000..c23d07a3
Binary files /dev/null and b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/images/bat-animation-example.gif differ
diff --git a/articles/tutorials/building_2d_games/09_the_animatedsprite_class/index.md b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/index.md
new file mode 100644
index 00000000..caa011ce
--- /dev/null
+++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/index.md
@@ -0,0 +1,234 @@
+---
+title: "Chapter 09: 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 9-1* below shows three frames that make up a bat's wing-flapping animation:
+
+|  |
+| :-----------------------------------------------------------------------------------------------: |
+| **Figure 9-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 will build off of the `Sprite` class we created in [Chapter 08](../08_the_sprite_class/index.md) 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. We will create an `Animation` class to encapsulate this information. In the *Graphics* folder within the *MonoGameLibrary* project, add a new file named `Animation.cs` with this initial structure:
+
+[!code-csharp[](./snippets/animation.cs#declaration)]
+
+### Animation Properties
+
+An animation requires two key pieces of information: the sequence of frames to display and the timing between them. Add these properties to the `Animation` class:
+
+[!code-csharp[](./snippets/animation.cs#members)]
+
+* 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 will be displayed in the same sequence they are 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 just use `float` values instead.
+
+### Animation Constructors
+
+The `Animation` class provides two ways to create an animation.
+
+Add the following constructors:
+
+[!code-csharp[](./snippets/animation.cs#ctors)]
+
+* 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 07](../07_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. We will 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 in your code editor and replace the contents with the following:
+
+[!code-xml[](./snippets/atlas_definition.xml)]
+
+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 the following using statement so we can reference the `TimeSpan` struct:
+
+ [!code-csharp[](./snippets/textureatlas/usings.cs?highlight=1)]
+
+2. Add storage for animations after the `Texture` property:
+
+ [!code-csharp[](./snippets/textureatlas/add_animation_storage.cs)]
+
+3. Update the constructors so that the animations dictionary is initialized:
+
+ [!code-csharp[](./snippets//textureatlas/update_ctors.cs?highlight=7,18)]
+
+4. Add methods to manage animations, similar to those that we use to manage regions:
+
+ [!code-csharp[](./snippets/textureatlas/add_animation_management.cs)]
+
+5. Update the `FromFile` method to parse the new `` animation definitions from the XML configuration file
+
+ [!code-csharp[](./snippets//textureatlas/update_from_file.cs?highlight=55-95)]
+
+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](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/tutorials/inheritance).
+
+> [!NOTE]
+> By inheriting from `Sprite`, our `AnimatedSprite` class automatically gets all the rendering properties (position, rotation, scale, etc.) while adding new 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.
+
+We will now create the initial structure for our `AnimatedSprite` class. In the *Graphics* folder within the *MonoGameLibrary* project, add a new file named `AnimatedSprite.cs`:
+
+[!code-csharp[](./snippets/animatedsprite.cs#declaration)]
+
+### AnimatedSprite Members
+
+An animated sprite needs to track both its current animation state and timing information. Add the following members to the `AnimatedSprite` class:
+
+[!code-csharp[](./snippets/animatedsprite.cs#members)]
+
+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 provides two ways to create an animated sprite.
+
+Add the following constructors:
+
+[!code-csharp[](./snippets/animatedsprite.cs#ctors)]
+
+* 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 adding an `Update` method:
+
+[!code-csharp[](./snippets/animatedsprite.cs#methods)]
+
+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 have 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 08](../08_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 sprite creation method to the `TextureAtlas` class. Open `TextureAtlas.cs` and add the following method:
+
+[!code-csharp[](./snippets/textureatlas/create_animated_sprite.cs)]
+
+## Using the AnimatedSprite Class
+
+We can now adjust our game now to use the `AnimatedSprite` class to see our sprites come to life. Update the contents of `Game1.cs` with the following:
+
+[!code-csharp[](./snippets/game1.cs?highlight=11-15,34-40,48-52)]
+
+Here are 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 the `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 9-2: The slime and bat sprite animating** |
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+- 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 will 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-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-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 does not?
+
+ :::question-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-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.
+ :::
diff --git a/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/animatedsprite.cs b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/animatedsprite.cs
new file mode 100644
index 00000000..73d745e2
--- /dev/null
+++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/animatedsprite.cs
@@ -0,0 +1,71 @@
+#region declaration
+using System;
+using Microsoft.Xna.Framework;
+
+namespace MonoGameLibrary.Graphics;
+
+public class AnimatedSprite : Sprite
+{
+
+}
+#endregion
+{
+ #region members
+ 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];
+ }
+ }
+ #endregion
+
+ #region ctors
+ ///
+ /// 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;
+ }
+ #endregion
+
+ #region methods
+ ///
+ /// 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];
+ }
+ }
+ #endregion
+}
diff --git a/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/animation.cs b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/animation.cs
new file mode 100644
index 00000000..637ea5cf
--- /dev/null
+++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/animation.cs
@@ -0,0 +1,47 @@
+#region declaration
+using System;
+using System.Collections.Generic;
+
+namespace MonoGameLibrary.Graphics;
+
+public class Animation
+{
+
+}
+#endregion
+{
+ #region members
+ ///
+ /// 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; }
+ #endregion
+
+ #region ctors
+ ///
+ /// 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;
+ }
+ #endregion
+}
diff --git a/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/atlas_definition.xml b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/atlas_definition.xml
new file mode 100644
index 00000000..f611c91e
--- /dev/null
+++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/atlas_definition.xml
@@ -0,0 +1,23 @@
+
+
+ images/atlas
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/game1.cs b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/game1.cs
new file mode 100644
index 00000000..a790893a
--- /dev/null
+++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/game1.cs
@@ -0,0 +1,76 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary;
+using MonoGameLibrary.Graphics;
+
+namespace DungeonSlime;
+
+public class Game1 : Core
+{
+ // Defines the slime animated sprite.
+ private AnimatedSprite _slime;
+
+ // Defines the bat animated sprite.
+ private AnimatedSprite _bat;
+
+ public Game1() : base("Dungeon Slime", 1280, 720, false)
+ {
+
+ }
+
+ protected override void Initialize()
+ {
+ // TODO: Add your initialization logic here
+
+ base.Initialize();
+ }
+
+ protected override void LoadContent()
+ {
+ // Create the texture atlas from the XML configuration file
+ TextureAtlas atlas = TextureAtlas.FromFile(Content, "images/atlas-definition.xml");
+
+ // Create the slime animated sprite from the atlas.
+ _slime = atlas.CreateAnimatedSprite("slime-animation");
+ _slime.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+ _bat.Scale = new Vector2(4.0f, 4.0f);
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
+ Exit();
+
+ // Update the slime animated sprite.
+ _slime.Update(gameTime);
+
+ // Update the bat animated sprite.
+ _bat.Update(gameTime);
+
+ base.Update(gameTime);
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
+
+ // Draw the slime sprite.
+ _slime.Draw(SpriteBatch, Vector2.One);
+
+ // Draw the bat sprite 10px to the right of the slime.
+ _bat.Draw(SpriteBatch, new Vector2(_slime.Width + 10, 0));
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+ }
+}
diff --git a/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/tetureatlas.cs b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/tetureatlas.cs
new file mode 100644
index 00000000..c89ab028
--- /dev/null
+++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/tetureatlas.cs
@@ -0,0 +1,225 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+using System.Xml.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace MonoGameLibrary.Graphics;
+
+public class TextureAtlas
+{
+ private Dictionary _regions;
+ private Dictionary _animations;
+
+ ///
+ /// Gets or Sets the source texture represented by this texture atlas.
+ ///
+ public Texture2D Texture { get; set; }
+
+ ///
+ /// 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();
+ }
+
+ ///
+ /// Creates a new region and adds it to this texture atlas.
+ ///
+ /// The name to give the texture region.
+ /// The top-left x-coordinate position of the region boundary relative to the top-left corner of the source texture boundary.
+ /// The top-left y-coordinate position of the region boundary relative to the top-left corner of the source texture boundary.
+ /// The width, in pixels, of the region.
+ /// The height, in pixels, of the region.
+ public void AddRegion(string name, int x, int y, int width, int height)
+ {
+ TextureRegion region = new TextureRegion(Texture, x, y, width, height);
+ _regions.Add(name, region);
+ }
+
+ ///
+ /// Gets the region from this texture atlas with the specified name.
+ ///
+ /// The name of the region to retrieve.
+ /// The TextureRegion with the specified name.
+ public TextureRegion GetRegion(string name)
+ {
+ return _regions[name];
+ }
+
+ ///
+ /// Removes the region from this texture atlas with the specified name.
+ ///
+ /// The name of the region to remove.
+ ///
+ public bool RemoveRegion(string name)
+ {
+ return _regions.Remove(name);
+ }
+
+ ///
+ /// Removes all regions from this texture atlas.
+ ///
+ public void Clear()
+ {
+ _regions.Clear();
+ }
+
+ ///
+ /// Creates a new sprite using the region from this texture atlas with the specified name.
+ ///
+ /// The name of the region to create the sprite with.
+ /// A new Sprite using the texture region with the specified name.
+ public Sprite CreateSprite(string regionName)
+ {
+ TextureRegion region = GetRegion(regionName);
+ return new Sprite(region);
+ }
+
+ ///
+ /// 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);
+ }
+
+ ///
+ /// 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 will 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;
+ }
+ }
+ }
+}
diff --git a/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/add_animation_management.cs b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/add_animation_management.cs
new file mode 100644
index 00000000..b8e3d63b
--- /dev/null
+++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/add_animation_management.cs
@@ -0,0 +1,29 @@
+///
+/// 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);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/add_animation_storage.cs b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/add_animation_storage.cs
new file mode 100644
index 00000000..8b2fe7e9
--- /dev/null
+++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/add_animation_storage.cs
@@ -0,0 +1,2 @@
+// Stores animations added to this atlas.
+private Dictionary _animations;
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/create_animated_sprite.cs b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/create_animated_sprite.cs
new file mode 100644
index 00000000..0dac322a
--- /dev/null
+++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/create_animated_sprite.cs
@@ -0,0 +1,10 @@
+///
+/// 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);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/update_ctors.cs b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/update_ctors.cs
new file mode 100644
index 00000000..ece920a3
--- /dev/null
+++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/update_ctors.cs
@@ -0,0 +1,19 @@
+///
+/// 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();
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/update_from_file.cs b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/update_from_file.cs
new file mode 100644
index 00000000..1a1592ba
--- /dev/null
+++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/update_from_file.cs
@@ -0,0 +1,100 @@
+///
+/// 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 will 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/usings.cs b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/usings.cs
new file mode 100644
index 00000000..372496f2
--- /dev/null
+++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/textureatlas/usings.cs
@@ -0,0 +1,8 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+using System.Xml.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
diff --git a/articles/tutorials/building_2d_games/09_the_animatedsprite_class/videos/slime-bat-animated.webm b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/videos/slime-bat-animated.webm
new file mode 100644
index 00000000..7637f565
Binary files /dev/null and b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/videos/slime-bat-animated.webm differ
diff --git a/articles/tutorials/building_2d_games/10_handling_input/images/ps-controller-back.svg b/articles/tutorials/building_2d_games/10_handling_input/images/ps-controller-back.svg
new file mode 100644
index 00000000..4a9f39ba
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/images/ps-controller-back.svg
@@ -0,0 +1,206 @@
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/10_handling_input/images/ps-controller-front.svg b/articles/tutorials/building_2d_games/10_handling_input/images/ps-controller-front.svg
new file mode 100644
index 00000000..cb6b268f
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/images/ps-controller-front.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/articles/tutorials/building_2d_games/10_handling_input/images/xbox-controller-back.svg b/articles/tutorials/building_2d_games/10_handling_input/images/xbox-controller-back.svg
new file mode 100644
index 00000000..d00541b2
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/images/xbox-controller-back.svg
@@ -0,0 +1,129 @@
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/10_handling_input/images/xbox-controller-front.svg b/articles/tutorials/building_2d_games/10_handling_input/images/xbox-controller-front.svg
new file mode 100644
index 00000000..338ce85f
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/images/xbox-controller-front.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/articles/tutorials/building_2d_games/10_handling_input/index.md b/articles/tutorials/building_2d_games/10_handling_input/index.md
new file mode 100644
index 00000000..fb358e86
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/index.md
@@ -0,0 +1,473 @@
+---
+title: "Chapter 10: Handling Input"
+description: "Learn how to handle keyboard, mouse, and gamepad input in MonoGame."
+---
+
+When you play a game, you need ways to control what is happening; using a keyboard or gamepad to control a character or clicking the mouse to navigate a menu, MonoGame helps us handle all these different types of controls through dedicated input classes:
+
+- [**Keyboard**](xref:Microsoft.Xna.Framework.Input.Keyboard): Detects which keys are being pressed.
+- [**Mouse**](xref:Microsoft.Xna.Framework.Input.Mouse): Tracks mouse movement, button clicks, and scroll wheel use.
+- [**GamePad**](xref:Microsoft.Xna.Framework.Input.GamePad): Manages controller input like button presses and thumbstick movement.
+- [**TouchPanel**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel): Manages touch input on devices with a touch panel such as mobile phones and tablets.
+
+Each of these input types has a `GetState` method that, when called, checks what is happening with that device at that moment. Think of it like taking a snapshot; when you call `GetState`, MonoGame looks at that exact moment to see which buttons are pressed, where the mouse is, or how the controller is being used.
+
+In this chapter you will, we will learn how to use each of these dedicated input classes to handle player input.
+
+## Keyboard Input
+
+The keyboard is often the primary input device for PC games, used for everything from character movement to menu navigation. MonoGame provides the [**Keyboard**](xref:Microsoft.Xna.Framework.Input.Keyboard) class to handle keyboard input, making it easy to detect which keys are being pressed at any time. Calling [**Keyboard.GetState**](xref:Microsoft.Xna.Framework.Input.Keyboard.GetState) will retrieve the current state of the keyboard as a [**KeyboardState**](xref:Microsoft.Xna.Framework.Input.KeyboardState) struct.
+
+### KeyboardState Struct
+
+The [**KeyboardState**](xref:Microsoft.Xna.Framework.Input.KeyboardState) struct contains methods that can be used to determine if a keyboard key is currently down or up:
+
+| Method | Description |
+|-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
+| [**IsKeyDown(Keys)**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys)) | Returns `true` if the specified key is down; otherwise, returns `false`. |
+| [**IsKeyUp(Keys)**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyUp(Microsoft.Xna.Framework.Input.Keys)) | Returns `true` if the specified key is up; otherwise, returns `false`. |
+
+For example, if we wanted to see if the Space key is down, you could use the following:
+
+[!code-csharp[](./snippets/keyboardstate.cs)]
+
+> [!TIP]
+> Notice we store the keyboard state in a variable instead of calling [**Keyboard.GetState**](xref:Microsoft.Xna.Framework.Input.Keyboard.GetState) multiple times. This is more efficient and ensures consistent input checking within a single frame.
+
+## Mouse Input
+
+The mouse is often the secondary input device for PC games, used for various actions from camera movement to interacting with menus and objects. MonoGame provides the [**Mouse**](xref:Microsoft.Xna.Framework.Input.Mouse) class to handle mouse input, making it easy to detect which buttons are pressed, the position of the mouse cursor, and the value of the scroll wheel. Calling [**Mouse.GetState**](xref:Microsoft.Xna.Framework.Input.Mouse.GetState) will retrieve the current state of the mouse as a [**MouseState**](xref:Microsoft.Xna.Framework.Input.MouseState) struct.
+
+### MouseState Struct
+
+The [**MouseState**](xref:Microsoft.Xna.Framework.Input.MouseState) struct contains properties that can be used to determine the state of the mouse buttons, the mouse position, and the scroll wheel value:
+
+| Property | Type | Description |
+|----------------------------------------------------------------------------------------|-------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|
+| [**LeftButton**](xref:Microsoft.Xna.Framework.Input.MouseState.LeftButton) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the left mouse button. |
+| [**MiddleButton**](xref:Microsoft.Xna.Framework.Input.MouseState.MiddleButton) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the middle mouse button. This is often the button when pressing the scroll wheel down as a button |
+| [**Position**](xref:Microsoft.Xna.Framework.Input.MouseState.Position) | [**Point**](xref:Microsoft.Xna.Framework.Point) | Returns the position of the mouse cursor relative to the bounds of the game window. |
+| [**RightButton**](xref:Microsoft.Xna.Framework.Input.MouseState.RightButton) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the right mouse button. |
+| [**ScrollWheelValue**](xref:Microsoft.Xna.Framework.Input.MouseState.ScrollWheelValue) | `int` | Returns the **cumulative** scroll wheel value since the start of the game |
+| [**XButton1**](xref:Microsoft.Xna.Framework.Input.MouseState.XButton1) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the first extended button on the mouse. |
+| [**XButton2**](xref:Microsoft.Xna.Framework.Input.MouseState.XButton2) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the second extended button on the mouse. |
+
+> [!NOTE]
+> [**ScrollWheelValue**](xref:Microsoft.Xna.Framework.Input.MouseState.ScrollWheelValue) returns the cumulative value of the scroll wheel since the start of the game, not how much it moved since the last update. To determine how much it moved between one update and the next, you would need to compare it with the previous frame's value. We will discuss comparing previous and current frame values for inputs in the next chapter.
+
+Unlike keyboard input which uses [**IsKeyDown(Keys)**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys))/[**IsKeyUp(Keys)**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyUp(Microsoft.Xna.Framework.Input.Keys)) methods mouse buttons return a [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState):
+
+- [**ButtonState.Pressed**](xref:Microsoft.Xna.Framework.Input.ButtonState): The button is being held down.
+- [**ButtonState.Released**](xref:Microsoft.Xna.Framework.Input.ButtonState): The button is not being pressed.
+
+For example, if we wanted to see if the left mouse button is down, you could use the following
+
+[!code-csharp[](./snippets/mousestate.cs)]
+
+## Gamepad Input
+
+Gamepads are often used as a primary input for a game or an alternative for keyboard and mouse controls. MonoGame provides the [**GamePad**](xref:Microsoft.Xna.Framework.Input.GamePad) class to handle gamepad input, making it easy to detect which buttons are pressed and the value of the thumbsticks. Calling [**GamePad.GetState**](xref:Microsoft.Xna.Framework.Input.GamePad.GetState(Microsoft.Xna.Framework.PlayerIndex)) will retrieve the state of the gamepad as a [**GamePadState**](xref:Microsoft.Xna.Framework.Input.GamePadState) struct. Since multiple gamepads can be connected, you will need to supply a [**PlayerIndex**](xref:Microsoft.Xna.Framework.PlayerIndex) value to specify which gamepad state to retrieve.
+
+### GamePadState Struct
+
+The [**GamePadState**](xref:Microsoft.Xna.Framework.Input.GamePadState) struct and properties that can be used to get the state of the buttons, dpad, triggers, and thumbsticks:
+
+| Property | Type | Description |
+|--------------------------------------------------------------------------------|---------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [**Buttons**](xref:Microsoft.Xna.Framework.Input.GamePadState.Buttons) | [**GamePadButtons**](xref:Microsoft.Xna.Framework.Input.GamePadButtons) | Returns a struct that identifies which buttons on the controller are pressed. |
+| [**DPad**](xref:Microsoft.Xna.Framework.Input.GamePadState.DPad) | [**GamePadDPad**](xref:Microsoft.Xna.Framework.Input.GamePadDPad) | Returns a struct that identifies which directions on the DPad are pressed. |
+| [**IsConnected**](xref:Microsoft.Xna.Framework.Input.GamePadState.IsConnected) | `bool` | Returns a value that indicates whether the controller is connected. |
+| [**ThumbSticks**](xref:Microsoft.Xna.Framework.Input.GamePadState.ThumbSticks) | [**GamePadThumbSticks**](xref:Microsoft.Xna.Framework.Input.GamePadThumbSticks) | Returns a struct that contains the direction of each thumbstick. Each thumbstick (left and right) are represented as a [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) value between `-1.0f` and `1.0` for the x- and y-axes. |
+| [**Triggers**](xref:Microsoft.Xna.Framework.Input.GamePadState.Triggers) | [**GamePadTriggers**](xref:Microsoft.Xna.Framework.Input.GamePadTriggers) | Returns a struct that contains the value of each trigger. Each trigger (left and right) are represented as a `float` value between `0.0f`, meaning not pressed, and `1.0f`, meaning fully pressed. |
+
+#### Buttons
+
+The [**GamePadState.Buttons**](xref:Microsoft.Xna.Framework.Input.GamePadState.Buttons) property returns a [**GamePadButtons**](xref:Microsoft.Xna.Framework.Input.GamePadButtons) struct that can be used to identify which buttons on the controller are pressed. This struct contains the following properties:
+
+| Property | Type | Description |
+|--------------------------------------------------------------------------------------|-------------------------------------------------------------------|-----------------------------------------------|
+| [**A**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.A) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the A button |
+| [**B**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.B) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the B button |
+| [**Back**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.Back) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the Back button |
+| [**BigButton**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.BigButton) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the BigButton button |
+| [**LeftShoulder**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.LeftShoulder) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the LeftShoulder button |
+| [**LeftStick**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.LeftStick) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the LeftStick button |
+| [**RightShoulder**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.RightShoulder) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the RightShoulder button |
+| [**RightStick**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.RightStick) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the RightStick button |
+| [**Start**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.Start) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the Start button |
+| [**X**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.X) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the X button |
+| [**Y**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.Y) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the Y button |
+
+> [!NOTE]
+> Recall from [Chapter 01](../01_what_is_monogame/index.md) that MonoGame is a implementation the XNA API. Since XNA was originally created for making games on Windows PC and Xbox 360, the names of the gamepad buttons match those of an Xbox 360 controller.
+>
+> The [**BigButton**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.BigButton) refers to the large, centrally located button on special Xbox 360 controllers created for games like "Scene It?" - this button is not present on standard controllers and is not mapped to any button on modern controllers. It remains in the API for backward compatibility with XNA.
+>
+> | Front | Back |
+> | :--------------------------------------------------------- | :------------------------------------------------------- |
+> | Xbox | |
+> |  |  |
+> | Playstation | |
+> |  |  |
+
+Like with the [mouse input](#mousestate-struct), each of these buttons are represented by a [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) enum value. For instance, if you wanted to check if the A button is being pressed you could do the following:
+
+[!code-csharp[](./snippets/gamepadstate.cs)]
+
+> [!NOTE]
+> You may notice however, that the GamePadState also requires a controller index, as more than one can be connected at the same time. The latest Xbox console for instance can support up to 8 controllers at a time, for this reason you need to specify which controller you are listening for. Additionally, if you want ANY controller to start your game, you will need to loop through all possible controllers each frame until the first one "picks up".
+
+#### DPad
+
+The [**DPad**](xref:Microsoft.Xna.Framework.Input.GamePadState.DPad) property returns a [**GamePadDPad**](xref:Microsoft.Xna.Framework.Input.GamePadDPad) struct that can be used to identify which DPad buttons on the controller are pressed. This struct contains the following properties:
+
+| Property | Type | Description |
+|------------------------------------------------------------------|-------------------------------------------------------------------|---------------------------------------------|
+| [**Down**](xref:Microsoft.Xna.Framework.Input.GamePadDPad.Down) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the DPad Down button. |
+| [**Left**](xref:Microsoft.Xna.Framework.Input.GamePadDPad.Down) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the DPad Left button. |
+| [**Right**](xref:Microsoft.Xna.Framework.Input.GamePadDPad.Down) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the DPad Right button. |
+| [**Up**](xref:Microsoft.Xna.Framework.Input.GamePadDPad.Down) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the DPad Up Button. |
+
+Like with the [Buttons](#buttons), these also return a [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) enum value to represent the state of the DPad button. For instance, if you wanted to check if the DPad up button is being pressed, you could do the following:
+
+[!code-csharp[](./snippets/buttonstate.cs)]
+
+#### Thumbsticks
+
+The [**ThumbSticks**](xref:Microsoft.Xna.Framework.Input.GamePadState.ThumbSticks) property returns a [**GamePadThumbSticks**](xref:Microsoft.Xna.Framework.Input.GamePadThumbSticks) struct that can be used to retrieve the values of the left and right thumbsticks. This struct contains the following properties:
+
+| Property | Type | Description |
+|--------------------------------------------------------------------------|-----------------------------------------------------|------------------------------------------------|
+| [**Left**](xref:Microsoft.Xna.Framework.Input.GamePadThumbSticks.Left) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | The direction the left thumbstick is pressed. |
+| [**Right**](xref:Microsoft.Xna.Framework.Input.GamePadThumbSticks.Right) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | The direction the right thumbstick is pressed. |
+
+The thumbstick values are represented as a [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) value:
+
+- X-axis: A value between `-1.0f` (pushed fully to the left) and `1.0f` (pushed fully to the right).
+- Y-axis: A value between `-1.0f` (pushed fully downward) and `1.0f` (pushed fully upward).
+
+For example, if you wanted to move a sprite using the left thumbstick, you could do the following:
+
+[!code-csharp[](./snippets/thumbstick.cs)]
+
+> [!IMPORTANT]
+> Notice that we inverted the y-axis value of the thumbstick by multiplying it by `-1.0f`. This is necessary because the thumbstick y-axis values range from `-1.0f` (down) to `1.0f` (up). The y-axis of the screen coordinates in MonoGame **increases** downward, as we saw in [Chapter 06](../06_working_with_textures/index.md#drawing-a-texture).
+>
+> This inversion aligns the thumbstick's y-axis value with the screen movement.
+
+#### Triggers
+
+The [**Triggers**](xref:Microsoft.Xna.Framework.Input.GamePadState.Triggers) property returns a [**GamePadTriggers**](xref:Microsoft.Xna.Framework.Input.GamePadTriggers) struct that can be used to retrieve the values of the left and right triggers. This struct contains the following properties:
+
+| Property | Type | Description |
+|-----------------------------------------------------------------------|---------|--------------------------------|
+| [**Left**](xref:Microsoft.Xna.Framework.Input.GamePadTriggers.Left) | `float` | The value of the left trigger. |
+| [**Right**](xref:Microsoft.Xna.Framework.Input.GamePadTriggers.Right) | `float` | The value of the left trigger. |
+
+The trigger values are represented as a float value between `0.0f` (not pressed) to `1.0f` (fully pressed). The triggers on a gamepad, however, can be either *analog* or *digital* depending the gamepad manufacturer. For gamepads with *digital* triggers, the value will always be either `0.0f` or `1.0f`, as a digital trigger does not register values in between based on the amount of pressure applied to the trigger.
+
+For example, if we were creating a racing game, the right trigger could be used for acceleration like the following:
+
+[!code-csharp[](./snippets/triggers.cs)]
+
+### GamePadState Methods
+
+The [**GamePadState**](xref:Microsoft.Xna.Framework.Input.GamePadState) struct also contains two methods that can be used to get information about the device's inputs as either being up or down:
+
+| Method | Description |
+|----------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [**IsButtonDown(Buttons)**](xref:Microsoft.Xna.Framework.Input.GamePadState.IsButtonDown(Microsoft.Xna.Framework.Input.Buttons)) | Returns a value that indicates whether the specified button is down. Multiple [**Buttons**](xref:Microsoft.Xna.Framework.Input.Buttons) values can be given using the bitwise OR `|` operator. When multiple buttons are given, the return value indicates if all buttons specified are down, not just one of them. |
+| [**IsButtonUp(Buttons)**](xref:Microsoft.Xna.Framework.Input.GamePadState.IsButtonUp(Microsoft.Xna.Framework.Input.Buttons)) | Returns a value that indicates whether the specified button is up. Multiple [**Buttons**](xref:Microsoft.Xna.Framework.Input.Buttons) values can be given using the bitwise OR `|` operator. When multiple buttons are given, the return value indicates if all buttons specified are up, not just one of them. |
+
+You can use the [**IsButtonDown(Buttons)**](xref:Microsoft.Xna.Framework.Input.GamePadState.IsButtonDown(Microsoft.Xna.Framework.Input.Buttons)) and [**IsButtonUp(Buttons)**](xref:Microsoft.Xna.Framework.Input.GamePadState.IsButtonUp(Microsoft.Xna.Framework.Input.Buttons)) methods to get the state of all buttons, including the DPad. The following is a complete list of all of the [**Buttons**](xref:Microsoft.Xna.Framework.Input.Buttons) enum values:
+
+- [**Buttons.A**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.B**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.Back**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.BigButton**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.DPadDown**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.DPadLeft**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.DPadRight**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.DPadUp**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.LeftShoulder**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.LeftStick**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.LeftThumbstickDown**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.LeftThumbstickLeft**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.LeftThumbstickRight**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.LeftThumbstickUp**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.LeftTrigger**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.None**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.RightShoulder**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.RightStick**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.RightStickDown**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.RightStickLeft**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.RightStickRight**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.RightStickUp**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.RightTrigger**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.Start**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.X**](xref:Microsoft.Xna.Framework.Input.Buttons)
+- [**Buttons.Y**](xref:Microsoft.Xna.Framework.Input.Buttons)
+
+> [!CAUTION]
+> While you can use these methods to get the state of any of these button inputs, the state will only tell you if it is being pressed or released. For the actual thumbstick values and trigger values, you would need to use the properties instead.
+
+For example, if we wanted to check if the A button on the the first gamepad is pressed, you could use the following:
+
+[!code-csharp[](./snippets/isbuttondown.cs)]
+
+### GamePad Vibration
+
+Another capability of gamepads is haptic feedback through vibration motors. MonoGame allows you to control this feature using the [**GamePad.SetVibration**](xref:Microsoft.Xna.Framework.Input.GamePad.SetVibration(Microsoft.Xna.Framework.PlayerIndex,System.Single,System.Single)) method. This method takes three parameters:
+
+1. The [**PlayerIndex**](xref:Microsoft.Xna.Framework.PlayerIndex) of the gamepad to vibrate.
+2. The intensity of the left motor (from `0.0f` for no vibration to `1.0f` for maximum vibration).
+3. The intensity of the right motor (using the same scale).
+
+Most modern gamepads have two vibration motors, a larger one (usually the left motor) for low-frequency rumble and a smaller one (usually the right motor) for high-frequency feedback. By controlling these independently, you can create various haptic effects:
+
+[!code-csharp[](./snippets/vibration.cs)]
+
+## TouchPanel Input
+
+For mobile devices such as Android/iOS phones and tablets, the primary input device is the touch panel screen. Touching a location on the screen is similar to clicking a location on your computer with a mouse. MonoGame provides the [**TouchPanel**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel) class to handle touch input.
+
+The [**TouchPanel**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel) class offers two ways of retrieving information about touch input:
+
+- [**TouchPanel.GetState**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.GetState) retrieves a [**TouchCollection**](xref:Microsoft.Xna.Framework.Input.Touch.TouchCollection) struct that contains [**TouchLocation**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation) values for each point of touch on the touch panel.
+- [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture) retrieves a [**GestureSample**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample) struct that contains information about recent gestures that have been performed like a vertical or horizontal drag across the screen.
+
+### TouchCollection
+
+When calling [**TouchPanel.GetState**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.GetState) a [**TouchCollection**](xref:Microsoft.Xna.Framework.Input.Touch.TouchCollection) struct is returned. This collection contains a [**TouchLocation**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation) value for each point of touch.
+
+#### TouchLocation
+
+Each [**TouchLocation**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation) value in a touch collection contains the following properties:
+
+| Property | Type | Description |
+|---------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|
+| [**Id**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.Id) | `int` | The id of the touch location. |
+| [**Position**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.Position) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | The position of the touch location. |
+| [**Pressure**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.Pressure) | `float` | The amount of pressure applied at the touch location. **(Only available for Android devices.)** |
+| [**State**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) | [**TouchLocationState**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState) | The current state of the touch location. |
+
+The important properties of the location are the [**Position**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.Position) and the [**State**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) The position property will tell us the location of the touch event, and the state can be one of the following values:
+
+| State | Description |
+|------------------------------------------------------------------------------|---------------------------------------------------------------------------|
+| [**Invalid**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) | This touch location position is invalid. |
+| [**Moved**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) | This touch location position was updated or pressed at the same position. |
+| [**Pressed**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) | This touch location was pressed. |
+| [**Released**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) | This touch location was released. |
+
+When the state is moved or pressed, then we know that location on the touch panel is being touched. So we can capture it and use it like the following:
+
+[!code-csharp[](./snippets/touchstate.cs)]
+
+> [!NOTE]
+> Unlike mouse input which only tracks a single point, [**TouchPanel**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel) supports multiple simultaneous touch points. The [**TouchCollection**](xref:Microsoft.Xna.Framework.Input.Touch.TouchCollection) contains all active touch points, which is why we loop through them in the sample above.
+
+The state of a touch location progresses through the states typically in order of:
+
+- [**Pressed**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State): Initial contact with the screen.
+- [**Moved**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) : Touch point moved while maintaining contact.
+- [**Released**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State): Contact with screen ended.
+- [**Invalid**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) : Touch data is invalid (using when tracking data is lost).
+
+### GestureSample
+
+When calling [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture) a [**GestureSample**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample) struct containing the information about recent gestures that have been performed is returned. The [**GestureSample**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample) struct contains the following properties:
+
+| Property | Type | Description |
+|---------------------------------------------------------------------------------------|-------------------------------------------------------------------------|--------------------------------------------------------------------------------|
+| [**Delta**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.Delta) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | Gets the delta information about the first touch-point in the gesture sample. |
+| [**Delta2**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.Delta2) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | Gets the delta information about the second touch-point in the gesture sample. |
+| [**GestureType**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.GestureType) | [**GestureType**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | Gets the type of the gesture. |
+| [**Position**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.Position) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | Gets the position of the first touch-point in the gesture sample. |
+| [**Position2**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.Position2) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | Gets the position of the second touch-point in the gesture sample. |
+
+> [!NOTE]
+> Gestures have two delta properties and two position properties. This is because some gestures require multiple touch inputs to perform, such as performing a pinch to zoom in or out. You would need the location of both touch points to determine the correct zoom to apply during the gesture.
+
+To determine what type of gesture is performed, we can get that from the [**GestureType**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.GestureType) property which will be one of the following values:
+
+| Gesture Type | Description |
+|----------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|
+| [**DoubleTap**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user double tapped the device twice which is always preceded by a Tap gesture. |
+| [**DragComplete**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | States completion of a drag gesture (VerticalDrag, HorizontalDrag, or FreeDrag). |
+| [**Flick**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | States that a touch was combined with a quick swipe. |
+| [**FreeDrag**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user touched a point and the performed a free-form drag. |
+| [**Hold**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user touched a single point for approximately one second. |
+| [**HorizontalDrag**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user touched the screen and performed either a left-to-right or right-to-left drag gesture. |
+| [**None**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | No gesture. |
+| [**Pinch**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user converged or diverged two touch-points on the screen which is like a two-finger drag. |
+| [**PinchComplete**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | An in-progress pinch gesture was completed. |
+| [**Tap**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user touched a single point. |
+| [**VerticalDrag**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user touched the screen and performed either a top-to-bottom or bottom-to-top drag gesture. |
+
+> [!IMPORTANT]
+> Before gestures can be detected, they have to be enabled using [**TouchPanel.EnabledGestures**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.EnabledGestures). This can be done in [**Game.Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize) like the following:
+>
+> [!code-csharp[](./snippets/enablegestures.cs)]
+
+The following is an example of using a gesture to detect horizontal and vertical drags:
+
+[!code-csharp[](./snippets/gestures.cs)]
+
+> [!IMPORTANT]
+> Notice above that we use a `while` loop with [**TouchPanel.IsGestureAvailable**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.IsGestureAvailable) as the condition for the loop. The reason we do this is because when a user performs a gesture, such as a horizontal drag across the screen, very quickly, what can often occurs is a series of multiple small drag gestures are registered and queued.
+>
+> Each time [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture) is called, it will dequeue the next gesture. So to ensure that we handle the complete gesture, we loop the gesture queue until there are none left.
+
+## Implementing Input in Our Game
+
+For our game, we are going to implement keyboard and gamepad controls based on the following criteria:
+
+| Keyboard Input | Gamepad Input | Description |
+|---------------------------|---------------------------------------------|--------------------------------------|
+| [Keys.W] and [Keys.Up] | [Thumbstick.Left.Y] and [Buttons.DPadUp] | Moves the slime up the screen. |
+| [Keys.S] and [Keys.Down] | [Thumbstick.Left.Y] and [Buttons.DPadDown] | Moves the slime down the screen |
+| [Keys.A] and [Keys.Left] | [Thumbstick.Left.X] and [Buttons.DPadLeft] | Moves the slime left on the screen. |
+| [Keys.D] and [Keys.Right] | [Thumbstick.Left.X] and [Buttons.DPadRight] | Moves the slime right on the screen. |
+| [Keys.Space] | [Buttons.A] | Increased the speed of the slime. |
+
+Open `Game1.cs` the *DungeonSlime* project and update it with the following:
+
+[!code-csharp[](./snippets/game1.cs?highlight=17-21,60-64,69-157,168)]
+
+The key changes made here are:
+
+1. The `_slimePosition` field was added to track the position of the slime as it moves.
+2. The `MOVEMENT_SPEED` constant was added to use as the base multiplier for the movement speed.
+3. The `CheckKeyboardInput` method was added which checks for input from the keyboard based on the input table above and moves the slime based on the keyboard input detected.
+4. The `CheckGamePadInput` method was added which checks for input from the gamepad based on the input table above and moves the slime based the gamepad input detected.
+
+ > [!NOTE]
+ > The gamepad implementation includes a priority system for directional input. The code prioritizes the analog thumbstick values over the digital DPad buttons. This design choice provides players with more nuanced control, as analog inputs allow for a variable movements speed based on how far the thumbstick is pushed, while DPad buttons only provide on/off input states. The code first checks if either thumbstick axis has a non-zero value, and only falls back to DPad input when the thumbstick is centered.
+ >
+ > To enhance player experience, the gamepad implementation also includes gamepad vibration when the speed boost is activated. Haptic feedback like this creates a more immersive experience by engaging additional senses for the player beyond just visual and auditory feedback.
+
+5. In [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)) `CheckKeyboardInput` and `CheckGamePadInput` methods are called.
+6. In [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)), the slime is now drawn using `_slimePosition` as the position.
+
+Running the game now, you can move the slime around using the keyboard with the arrow keys or WASD keys. If you have a gamepad plugged in you can also use the DPad and left thumbstick.
+
+|  |
+|:-----------------------------------------------------------------------------------------------:|
+| **Figure 10-1: The slime moving around based on device input** |
+
+> [!NOTE]
+> You may notice that the slime is capable of moving completely off the screen, this is completely normal as we have not yet implemented any logic to prevent it from doing so, it only doing what we currently tell it to do.
+
+## Input Buffering
+
+While checking for input every frame works well for continuous actions like movement, many games benefit from more sophisticated input handling techniques. One such technique is **input buffering**, which can significantly improve how responsive controls feel to players.
+
+### Understanding Input Buffering
+
+Input buffering is a technique where the game temporarily stores player inputs that cannot be immediately processed. Instead of discarding these inputs, they are placed in a queue and processed in order when the game is ready to handle them.
+
+Input buffering is particularly valuable in games where:
+
+- Actions occur at fixed intervals rather than continuously (like turn-based games or grid movement).
+- Precise timing is required for complex input sequences (like fighting games).
+- Multiple rapid inputs need to be remembered in order (like quick directional changes).
+
+Without input buffering, players must time their inputs perfectly to align with the game's update cycle. With buffering, the game becomes more forgiving and responsive by:
+
+1. Storing inputs that arrive between action updates.
+2. Preserving the order of inputs for more predictable behavior.
+3. Creating a sense that the game is actually listening to the player.
+
+### Implementing a Simple Input Buffer
+
+A basic input buffer can be implemented using a queue data structure, which follows a First-In-First-Out (FIFO) pattern:
+
+[!code-csharp[](./snippets/inputbuffer.cs)]
+
+> [!NOTE]
+> The [`Queue`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.queue-1?view=net-9.0>) is a First In, First Out (FIFO) collection in C#. When you add items with `Enqueue()`, they join the end of the line, and when you retrieve items with `Dequeue()`, you always get the oldest item (the one at the front of the line). Think of it like people waiting in line - the first person to arrive is the first one served.
+>
+> This contrasts with a [`Stack`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.stack-1?view=net-9.0>), which follows Last In, First Out (LIFO) behavior, where the most recently added item is the first one retrieved.
+
+The size of an input buffer is an important design decision. If it is too small, players might still feel the game is not responsive enough. If it is too large, the game might feel like it is playing itself by working through a backlog of commands.
+
+### When to Use Input Buffering
+
+Consider implementing input buffering in your game when:
+
+- Players complain about the game feeling "unresponsive".
+- Your game uses fixed-interval updates for certain mechanics.
+- Actions require precise timing that is difficult for players to hit consistently.
+- You want to allow players to "queue up" their next few moves.
+
+We will see a practical implementation of input buffering in [Chapter 23](../23_completing_the_game/index.md) when we finalize our snake-like game mechanics, where timing and direction changes are critical to gameplay.
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+- Handle keyboard input to detect key presses.
+- Handle mouse input including button clicks and cursor position.
+- Work with gamepad controls including buttons, thumbsticks, and vibration.
+- Understand touch input for mobile devices including touch points and gestures.
+- Implement movement controls using different input methods.
+- Consider controller-specific details like coordinate systems and analog vs digital input.
+
+In the next chapter, we will learn how to track previous input states to handle single-press events and implement an input management system to simplify some of the complexity of handling input.
+
+## Test Your Knowledge
+
+1. Why do we store the result of `GetState` in a variable instead of calling it multiple times?
+
+ :::question-answer
+ Storing the state in a variable is more efficient and ensures consistent input checking within a frame. Each `GetState` call polls the device, which can impact performance if called repeatedly.
+ :::
+
+2. What is the main difference between how keyboard and mouse/gamepad button states are checked?
+
+ :::question-answer
+ Keyboard input uses [**IsKeyUp**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyUp(Microsoft.Xna.Framework.Input.Keys))/[**IsKeyDown**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys)) methods, while mouse and gamepad buttons return a [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) enum value (Pressed or Released).
+ :::
+
+3. When using thumbstick values for movement, why do we multiply the Y value by -1?
+
+ :::question-answer
+ The thumbstick Y-axis values (-1.0f down to 1.0f up) are inverted compared to MonoGame's screen coordinate system (Y increases downward). Multiplying by -1 aligns the thumbstick direction with screen movement.
+ :::
+
+4. What is the difference between analog and digital trigger input on a gamepad?
+
+ :::question-answer
+ Analog triggers provide values between 0.0f and 1.0f based on how far they are pressed, while digital triggers only report 0.0f (not pressed) or 1.0f (pressed). This affects how you handle trigger input in your game.
+ :::
+
+5. What is the key difference between [**TouchPanel.GetState**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.GetState) and [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture)?
+
+ :::question-answer
+ [**TouchPanel.GetState**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.GetState) returns information about current touch points on the screen, while [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture) provides information about specific gesture patterns like taps, drags, and pinches that have been performed.
+ :::
+
+6. Why do we use a while loop with [**TouchPanel.IsGestureAvailable**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.IsGestureAvailable) when reading gestures?
+
+ :::question-answer
+ Quick gestures can generate multiple gesture events that are queued. Using a while loop with [**TouchPanel.IsGestureAvailable**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.IsGestureAvailable) ensures we process all queued gestures, as [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture) only returns one gesture at a time.
+ :::
+
+7. How does touch input differ from mouse input in terms of handling multiple input points?
+
+ :::question-answer
+ Touch input can handle multiple simultaneous touch points through the [**TouchCollection**](xref:Microsoft.Xna.Framework.Input.Touch.TouchCollection), while mouse input only tracks a single cursor position. This allows touch input to support features like multi-touch gestures that are not possible with a mouse.
+ :::
+
+8. What are the different states a [**TouchLocation**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation) can have and what do they indicate?
+
+ :::question-answer
+ A [**TouchLocation**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation) can have four states:
+
+ - [**Pressed**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState): Initial contact with the screen
+ - [**Moved**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState): Touch point moved while maintaining contact
+ - [**Released**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState): Contact with the screen ended
+ - [**Invalid**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState): Touch data is not valid or tracking was lost
+
+ :::
diff --git a/articles/tutorials/building_2d_games/10_handling_input/snippets/buttonstate.cs b/articles/tutorials/building_2d_games/10_handling_input/snippets/buttonstate.cs
new file mode 100644
index 00000000..c68d5132
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/snippets/buttonstate.cs
@@ -0,0 +1,8 @@
+// Get the current state of player one's gamepad
+GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
+
+// Check if the down on the DPad is pressed.
+if(gamePadState.DPad.Down == ButtonState.Pressed)
+{
+ // DPad down is pressed, do something.
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/10_handling_input/snippets/enablegestures.cs b/articles/tutorials/building_2d_games/10_handling_input/snippets/enablegestures.cs
new file mode 100644
index 00000000..75b25268
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/snippets/enablegestures.cs
@@ -0,0 +1,10 @@
+protected override void Initialize()
+{
+ base.Initialize();
+
+ // Enable gestures we want to handle
+ TouchPanel.EnabledGestures =
+ GestureType.Tap |
+ GestureType.HorizontalDrag |
+ GestureType.VerticalDrag;
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/10_handling_input/snippets/game1.cs b/articles/tutorials/building_2d_games/10_handling_input/snippets/game1.cs
new file mode 100644
index 00000000..a48e5d3d
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/snippets/game1.cs
@@ -0,0 +1,178 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary;
+using MonoGameLibrary.Graphics;
+
+namespace DungeonSlime;
+
+public class Game1 : Core
+{
+ // Defines the slime animated sprite.
+ private AnimatedSprite _slime;
+
+ // Defines the bat animated sprite.
+ private AnimatedSprite _bat;
+
+ // Tracks the position of the slime.
+ private Vector2 _slimePosition;
+
+ // Speed multiplier when moving.
+ private const float MOVEMENT_SPEED = 5.0f;
+
+ public Game1() : base("Dungeon Slime", 1280, 720, false)
+ {
+
+ }
+
+ protected override void Initialize()
+ {
+ // TODO: Add your initialization logic here
+
+ base.Initialize();
+ }
+
+ protected override void LoadContent()
+ {
+ // Create the texture atlas from the XML configuration file
+ TextureAtlas atlas = TextureAtlas.FromFile(Content, "images/atlas-definition.xml");
+
+ // Create the slime animated sprite from the atlas.
+ _slime = atlas.CreateAnimatedSprite("slime-animation");
+ _slime.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+ _bat.Scale = new Vector2(4.0f, 4.0f);
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
+ Exit();
+
+ // Update the slime animated sprite.
+ _slime.Update(gameTime);
+
+ // Update the bat animated sprite.
+ _bat.Update(gameTime);
+
+ // Check for keyboard input and handle it.
+ CheckKeyboardInput();
+
+ // Check for gamepad input and handle it.
+ CheckGamePadInput();
+
+ base.Update(gameTime);
+ }
+
+ private void CheckKeyboardInput()
+ {
+ // Get the state of keyboard input
+ KeyboardState keyboardState = Keyboard.GetState();
+
+ // If the space key is held down, the movement speed increases by 1.5
+ float speed = MOVEMENT_SPEED;
+ if (keyboardState.IsKeyDown(Keys.Space))
+ {
+ speed *= 1.5f;
+ }
+
+ // If the W or Up keys are down, move the slime up on the screen.
+ if (keyboardState.IsKeyDown(Keys.W) || keyboardState.IsKeyDown(Keys.Up))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // if the S or Down keys are down, move the slime down on the screen.
+ if (keyboardState.IsKeyDown(Keys.S) || keyboardState.IsKeyDown(Keys.Down))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If the A or Left keys are down, move the slime left on the screen.
+ if (keyboardState.IsKeyDown(Keys.A) || keyboardState.IsKeyDown(Keys.Left))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If the D or Right keys are down, move the slime right on the screen.
+ if (keyboardState.IsKeyDown(Keys.D) || keyboardState.IsKeyDown(Keys.Right))
+ {
+ _slimePosition.X += speed;
+ }
+ }
+
+ private void CheckGamePadInput()
+ {
+ GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
+
+ // If the A button is held down, the movement speed increases by 1.5
+ // and the gamepad vibrates as feedback to the player.
+ float speed = MOVEMENT_SPEED;
+ if (gamePadState.IsButtonDown(Buttons.A))
+ {
+ speed *= 1.5f;
+ GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f);
+ }
+ else
+ {
+ GamePad.SetVibration(PlayerIndex.One, 0.0f, 0.0f);
+ }
+
+ // Check thumbstick first since it has priority over which gamepad input
+ // is movement. It has priority since the thumbstick values provide a
+ // more granular analog value that can be used for movement.
+ if (gamePadState.ThumbSticks.Left != Vector2.Zero)
+ {
+ _slimePosition.X += gamePadState.ThumbSticks.Left.X * speed;
+ _slimePosition.Y -= gamePadState.ThumbSticks.Left.Y * speed;
+ }
+ else
+ {
+ // If DPadUp is down, move the slime up on the screen.
+ if (gamePadState.IsButtonDown(Buttons.DPadUp))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // If DPadDown is down, move the slime down on the screen.
+ if (gamePadState.IsButtonDown(Buttons.DPadDown))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If DPapLeft is down, move the slime left on the screen.
+ if (gamePadState.IsButtonDown(Buttons.DPadLeft))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If DPadRight is down, move the slime right on the screen.
+ if (gamePadState.IsButtonDown(Buttons.DPadRight))
+ {
+ _slimePosition.X += speed;
+ }
+ }
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
+
+ // Draw the slime sprite.
+ _slime.Draw(SpriteBatch, _slimePosition);
+
+ // Draw the bat sprite 10px to the right of the slime.
+ _bat.Draw(SpriteBatch, new Vector2(_slime.Width + 10, 0));
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/10_handling_input/snippets/gamepadstate.cs b/articles/tutorials/building_2d_games/10_handling_input/snippets/gamepadstate.cs
new file mode 100644
index 00000000..ab65dc08
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/snippets/gamepadstate.cs
@@ -0,0 +1,8 @@
+// Get the current state of player one's gamepad
+GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
+
+// Check if the A button is pressed down.
+if(gamePadState.Buttons.A == ButtonState.Pressed)
+{
+ // Button A is pressed, do something.
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/10_handling_input/snippets/gestures.cs b/articles/tutorials/building_2d_games/10_handling_input/snippets/gestures.cs
new file mode 100644
index 00000000..3c8b125d
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/snippets/gestures.cs
@@ -0,0 +1,24 @@
+while(TouchPanel.IsGestureAvailable)
+{
+ GestureSample gesture = TouchPanel.ReadGesture();
+
+ if(gesture.GestureType == GestureType.HorizontalDrag)
+ {
+ // A horizontal drag from left-to-right or right-to-left occurred.
+ // You can use the Delta property to determine how much movement
+ // occurred during the swipe.
+ float xDragAmount = gesture.Delta.X;
+
+ // Now do something with that information.
+ }
+
+ if(gesture.GestureType == GestureType.VerticalDrag)
+ {
+ // A vertical drag from top-to-bottom or bottom-to-top occurred.
+ // You can use the Delta property to determine how much movement
+ // occurred during the swipe.
+ float yDragAmount = gesture.Delta.Y;
+
+ // Now do something with that information.
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/10_handling_input/snippets/inputbuffer.cs b/articles/tutorials/building_2d_games/10_handling_input/snippets/inputbuffer.cs
new file mode 100644
index 00000000..dec0fa1f
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/snippets/inputbuffer.cs
@@ -0,0 +1,40 @@
+// Use a queue directly for input buffering
+private Queue _inputBuffer;
+private const int MAX_BUFFER_SIZE = 2;
+
+// In initialization code:
+_inputBuffer = new Queue(MAX_BUFFER_SIZE);
+
+// In the input handling code:
+KeyboardState keyboard = Keyboard.GetState();
+Vector2 newDirection = Vector2.Zero;
+
+if(keyboard.IsKeyDown(Keys.Up))
+{
+ newDirection = -Vector2.UnitY;
+}
+else if(keyboard.IsKeyDown(Keys.Down))
+{
+ newDirection = Vector2.UnitY;
+}
+else if(keyboard.IsKeyDown(Keys.Left))
+{
+ newDirection = -Vector2.UnitX;
+}
+else if(keyboard.IsKeyDown(Keys.Right))
+{
+ newDirection = Vector2.UnitX;
+}
+
+// Only add if a valid direction and does not exceed the buffer size
+if(newDirection != Vector2.Zero && _inputBuffer.Count < MAX_BUFFER_SIZE)
+{
+ _inputBuffer.Enqueue(newDirection);
+}
+
+// In movement update code
+if(_inputBuffer.COunt > 0)
+{
+ Vector2 nextDirection = _inputBuffer.Dequeue();
+ _position += nextDirection * _speed;
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/10_handling_input/snippets/isbuttondown.cs b/articles/tutorials/building_2d_games/10_handling_input/snippets/isbuttondown.cs
new file mode 100644
index 00000000..85526ae6
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/snippets/isbuttondown.cs
@@ -0,0 +1,8 @@
+// Get the current state of player one's gamepad
+GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
+
+// Check if the A button is down.
+if(gamePadState.IsButtonDown(Buttons.A))
+{
+ // The A button is pressed, do something.
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/10_handling_input/snippets/keyboardstate.cs b/articles/tutorials/building_2d_games/10_handling_input/snippets/keyboardstate.cs
new file mode 100644
index 00000000..7243305a
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/snippets/keyboardstate.cs
@@ -0,0 +1,8 @@
+// Get the current state of keyboard input.
+KeyboardState keyboardState = Keyboard.GetState();
+
+// Check if the space key is down.
+if(keyboardState.IsKeyDown(Keys.Space))
+{
+ // The space key is down, so do something.
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/10_handling_input/snippets/mousestate.cs b/articles/tutorials/building_2d_games/10_handling_input/snippets/mousestate.cs
new file mode 100644
index 00000000..e794f95a
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/snippets/mousestate.cs
@@ -0,0 +1,8 @@
+// Get the current state of mouse input.
+MouseState mouseState = Mouse.GetState();
+
+// Check if the left mouse button is pressed down.
+if(mouseState.LeftButton == ButtonState.Pressed)
+{
+ // The left button is down, so do something.
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/10_handling_input/snippets/thumbstick.cs b/articles/tutorials/building_2d_games/10_handling_input/snippets/thumbstick.cs
new file mode 100644
index 00000000..d2525cac
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/snippets/thumbstick.cs
@@ -0,0 +1,11 @@
+// Get the current state of player one's gamepad
+GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
+
+// Get the value of the left thumbstick.
+Vector2 leftStick = gamePadState.Thumbsticks.Left;
+
+// Invert the y-axis value
+leftStick.Y *= -1.0f;
+
+// Apply the value to the position of the sprite.
+sprite.Position += leftStick;
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/10_handling_input/snippets/touchstate.cs b/articles/tutorials/building_2d_games/10_handling_input/snippets/touchstate.cs
new file mode 100644
index 00000000..d0cb223b
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/snippets/touchstate.cs
@@ -0,0 +1,11 @@
+// Get the current state of touch input.
+TouchCollection touchCollection = TouchPanel.GetState();
+
+foreach(TouchLocation touchLocation in touchCollection)
+{
+ if(touchLocation.State == TouchLocationState.Pressed || touchLocation.State == TouchLocationState.Moved)
+ {
+ // The the location at touchLocation.Position is currently being pressed,
+ // so we can act on that information.
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/10_handling_input/snippets/triggers.cs b/articles/tutorials/building_2d_games/10_handling_input/snippets/triggers.cs
new file mode 100644
index 00000000..fdde9d06
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/snippets/triggers.cs
@@ -0,0 +1,5 @@
+// Get the current state of player one's gamepad
+GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
+
+// Get the acceleration based on how far the right trigger is pushed down.
+float acceleration = gamePadState.Triggers.Right;
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/10_handling_input/snippets/vibration.cs b/articles/tutorials/building_2d_games/10_handling_input/snippets/vibration.cs
new file mode 100644
index 00000000..06805dec
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/snippets/vibration.cs
@@ -0,0 +1,8 @@
+// Make the gamepad vibrate at full intensity
+GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f);
+
+// Stop all vibration
+GamePad.SetVibration(PlayerIndex.One, 0.0f, 0.0f);
+
+// Create a subtle, low-intensity vibration
+GamePad.SetVibration(PlayerIndex.One, 0.3f, 0.1f);
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/10_handling_input/videos/input-moving-slime.webm b/articles/tutorials/building_2d_games/10_handling_input/videos/input-moving-slime.webm
new file mode 100644
index 00000000..a4d21c4a
Binary files /dev/null and b/articles/tutorials/building_2d_games/10_handling_input/videos/input-moving-slime.webm differ
diff --git a/articles/tutorials/building_2d_games/11_input_management/index.md b/articles/tutorials/building_2d_games/11_input_management/index.md
new file mode 100644
index 00000000..84f3f590
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/index.md
@@ -0,0 +1,429 @@
+---
+title: "Chapter 11: Input Management"
+description: "Learn how to create an input management system to handle keyboard, mouse, and gamepad input, including state tracking between frames and creating a reusable framework for handling player input."
+---
+
+In [Chapter 10](../10_handling_input/index.md), you learned how to handle input from various devices like keyboard, mouse, and gamepad. While checking if an input is currently down works well for continuous actions like movement, many game actions should only happen once when an input is first pressed; think firing a weapon or jumping. To handle these scenarios, we need to compare the current input state with the previous frame's state to detect when an input changes from up to down.
+
+In this chapter you will:
+
+- Learn the difference between an input being down versus being pressed
+- Track input states between frames
+- Create a reusable input management system
+- Simplify handling input across multiple devices
+
+We will start by understanding the concept of input state changes and how we can detect them.
+
+## Understanding Input States
+
+When handling input in games, there are two key scenarios we need to consider:
+
+- An input is being held down (like holding a movement key).
+- An input was just pressed for one frame (like pressing a jump button).
+
+Now, we will look at the difference using keyboard input as an example. With our current implementation, we can check if a key is down using [**KeyboardState.IsKeyDown**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys)):
+
+[!code-csharp[](./snippets/key_down_every_frame.cs)]
+
+However, many game actions should not repeat while a key is held. For instance, if the Space key makes your character jump, you probably do not want them to jump repeatedly just because the player is holding the key down. Instead, you want the jump to happen only on the first frame when Space is pressed.
+
+To detect this "just pressed" state, we need to compare two states:
+
+1. Is the key down in the current frame?
+2. Was the key up in the previous frame?
+
+If both conditions are true, we know the key was just pressed. If we were to modify the above code to track the previous keyboard state it would look something like this:
+
+[!code-csharp[](./snippets/compare_previous_state.cs)]
+
+If you need to know the inverse state, when the key was just released, then it is simply a matter of swiching the checking of the states, for example, is the key up this frame and was it down in the previous frame.
+
+This same concept applies to mouse buttons and gamepad input as well. Any time you need to detect a "just pressed" or "just released" state, you will need to compare the current input state with the previous frame's state.
+
+So far, we have only been working with our game within the `Game1.cs` file. This has been fine for the examples given. Overtime, as the game grows, we are going to have a more complex system setup with different scenes, and each scene will need a way to track the state of input over time. We could do this by creating a lot of variables in each scene to track this information, or we can use object-oriented design concepts to create a reusable `InputManager` class to simplify this for us.
+
+Before we create the `InputManager` class, we should first create classes for the keyboard, mouse, and gamepad that encapsulates the information about those inputs which will then be exposed through the `InputManager`.
+
+To get started, create a new folder called `Input` in the *MonoGameLibrary* project. We will put all of our input related classes here.
+
+## The KeyboardInfo Class
+
+We will start our input management system by creating a class to handle keyboard input. The `KeyboardInfo` class will encapsulate all keyboard-related functionality, making it easier to:
+
+- Track current and previous keyboard states
+- Detect when keys are pressed or released
+- Check if keys are being held down
+
+In the `Input` folder of the *MonoGameLibrary* project, add a new file named `KeyboardInfo.cs` with this initial structure:
+
+[!code-csharp[](./snippets/keyboardinfo.cs#declaration)]
+
+### KeyboardInfo Properties
+
+To detect changes in keyboard input between frames, we need to track both the previous and current keyboard states. Add these properties to the `KeyboardInfo` class:
+
+[!code-csharp[](./snippets/keyboardinfo.cs#members)]
+
+> [!NOTE]
+> These properties use a public getter but private setter pattern. This allows other parts of the game to read the keyboard states if needed, while ensuring only the `KeyboardInfo` class can update them.
+
+### KeyboardInfo Constructor
+
+The `KeyboardInfo` class constructor needs to initialize the keyboard states.
+
+Add this constructor:
+
+[!code-csharp[](./snippets/keyboardinfo.cs#ctors)]
+
+The constructor:
+
+- Creates an empty state for `PreviousState` since there is no previous input yet
+- Gets the current keyboard state as our starting point for `CurrentState`
+
+This initialization ensures we have valid states to compare against in the first frame of our game, preventing any potential null reference issues when checking for input changes.
+
+### KeyboardInfo Methods
+
+The `KeyboardInfo` class needs methods both for updating states and checking key states. First, we will start with our update method:
+
+[!code-csharp[](./snippets/keyboardinfo.cs#methods_update)]
+
+> [!NOTE]
+> Each time `Update` is called, the current state becomes the previous state, and we get a fresh current state. This creates our frame-to-frame comparison chain.
+
+Next, we will add methods to check various key states:
+
+[!code-csharp[](./snippets/keyboardinfo.cs#methods_keystate)]
+
+These methods serve two distinct purposes. For checking continuous states:
+
+- `IsKeyDown`: Returns true as long as the specified key is being held down.
+- `IsKeyUp`: Returns true as long as the specified key is not being pressed.
+
+And for detecting state changes:
+
+- `WasKeyJustPressed`: Returns true only on the frame when the specified key changes from up-to-down.
+- `WasKeyJustReleased`: Returns true only on the frame when the specified key changes from down-to-up.
+
+> [!TIP]
+> Use continuous state checks (`IsKeyDown`/`IsKeyUp`) for actions that should repeat while a key is held, like movement. Use single-frame checks (`WasKeyJustPressed`/`WasKeyJustReleased`) for actions that should happen once per key press, like jumping or shooting.
+
+That's it for the `KeyboardInfo` class, now we can move on to mouse input next.
+
+## MouseButton Enum
+
+Recall from the [Mouse Input](../10_handling_input/index.md#mouse-input) section of the previous chapter that the [**MouseState**](xref:Microsoft.Xna.Framework.Input.MouseState) struct provides button states through properties rather than methods like `IsButtonDown`/`IsButtonUp`. To keep our input management API consistent across devices, we will create a `MouseButton` enum that lets us reference mouse buttons in a similar way to how we use [**Keys**](xref:Microsoft.Xna.Framework.Input.Keys) for keyboard input and [**Buttons**](xref:Microsoft.Xna.Framework.Input.Buttons) for gamepad input.
+
+In the `Input` folder of the *MonoGameLibrary* project, add a new file named `MouseButton.cs` with the following code:
+
+[!code-csharp[](./snippets/mousebutton.cs)]
+
+> [!NOTE]
+> Each enum value corresponds directly to a button property in MouseState:
+>
+> - `Left`: Maps to [**MouseState.LeftButton**](xref:Microsoft.Xna.Framework.Input.MouseState.LeftButton).
+> - `Middle`: Maps to [**MouseState.MiddleButton**](xref:Microsoft.Xna.Framework.Input.MouseState.MiddleButton).
+> - `Right`: Maps to [**MouseState.RightButton**](xref:Microsoft.Xna.Framework.Input.MouseState.RightButton).
+> - `XButton1`: Maps to [**MouseState.XButton1**](xref:Microsoft.Xna.Framework.Input.MouseState.XButton1).
+> - `XButton2`: Maps to [**MouseState.XButton2**](xref:Microsoft.Xna.Framework.Input.MouseState.XButton2).
+
+## The MouseInfo Class
+
+To manage mouse input effectively, we need to track both current and previous states, as well as provide easy access to mouse position, scroll wheel values, and button states. The `MouseInfo` class will encapsulate all of this functionality, making it easier to:
+
+- Track current and previous mouse states.
+- Track the mouse position.
+- Check the change in mouse position between frames and if it was moved.
+- Track scroll wheel changes.
+- Detect when mouse buttons are pressed or released
+- Check if mouse buttons are being held down
+
+To get started, in the `Input` folder of the *MonoGameLibrary* project, create a new file named `MouseInfo.cs` with the following initial structure:
+
+[!code-csharp[](./snippets/mouseinfo.cs#declaration)]
+
+### MouseInfo Properties
+
+The `MouseInfo` class needs properties to track both mouse states and provide easy access to common mouse information. Add the following properties to the `MouseInfo` class:
+
+First, we need properties for tracking mouse states:
+
+[!code-csharp[](./snippets/mouseinfo.cs#properties_states)]
+
+Next, we will add properties for handling cursor position:
+
+[!code-csharp[](./snippets/mouseinfo.cs#properties_position)]
+
+> [!NOTE]
+> The position properties use a `SetPosition` method that we will implement later. This method will handle the actual cursor positioning on screen.
+
+These properties provide different ways to work with the cursor position:
+
+- `Position`: Gets/sets the cursor position as a [**Point**](xref:Microsoft.Xna.Framework.Point).
+- `X`: Gets/sets just the horizontal position.
+- `Y`: Gets/sets just the vertical position.
+
+Next, we will add properties for determining if the mouse cursor moved between game frames and if so how much:
+
+[!code-csharp[](./snippets/mouseinfo.cs#properties_position_delta)]
+
+The properties provide different ways of detecting mouse movement between frames:
+
+- `PositionDelta`: Gets how much the cursor moved between frames as a [**Point**](xref:Microsoft.Xna.Framework.Point).
+- `XDelta`: Gets how much the cursor moved horizontally between frames.
+- `YDelta`: Gets how much the cursor moved vertically between frames.
+- `WasMoved`: Indicates if the cursor moved between frames.
+
+Finally, we will add properties for handling the scroll wheel:
+
+[!code-csharp[](./snippets/mouseinfo.cs#properties_scrollwheel)]
+
+The scroll wheel properties serve different purposes:
+
+- `ScrollWheel`: Gets the total accumulated scroll value since game start.
+- `ScrollWheelDelta`: Gets the change in scroll value just in this frame.
+
+> [!TIP]
+> Use `ScrollWheelDelta` when you need to respond to how much the user just scrolled, rather than tracking the total scroll amount.
+
+### MouseInfo Constructor
+
+The `MouseInfo` class constructor needs to initialize the mouse states.
+
+Add this constructor:
+
+[!code-csharp[](./snippets/mouseinfo.cs#ctors)]
+
+The constructor:
+
+- Creates an empty state for `PreviousState` since there is no previous input yet.
+- Gets the current mouse state as our starting point for `CurrentState`.
+
+This initialization ensures we have valid states to compare against in the first frame of our game, preventing any potential null reference issues when checking for input changes.
+
+### MouseInfo Methods
+
+The `MouseInfo` class needs methods for updating states, checking button states, and setting the cursor position. Add the following method to the `MouseInfo` class:
+
+[!code-csharp[](./snippets/mouseinfo.cs#methods_update)]
+
+Next, we will add methods to check various button states:
+
+[!code-csharp[](./snippets/mouseinfo.cs#methods_buttonstate)]
+
+These methods serve two distinct purposes. For checking continuous states:
+
+- `IsButtonDown`: Returns true as long as the specified button is being held down.
+- `IsButtonUp`: Returns true as long as the specified button is not being pressed.
+
+And for detecting state changes:
+
+- `WasButtonJustPressed`: Returns true only on the frame when the specified button changes from up-to-down.
+- `WasButtonJustReleased`: Returns true only on the frame when the specified button changes from down-to-up.
+
+> [!NOTE]
+> Each method uses a switch statement to check the appropriate button property from the [**MouseState**](xref:Microsoft.Xna.Framework.Input.MouseState) based on which `MouseButton` enum value is provided. This provides a consistent API while handling the different button properties internally.
+
+Finally, we need a method to handle setting the cursor position:
+
+[!code-csharp[](./snippets/mouseinfo.cs#methods_setposition)]
+
+> [!TIP]
+> Notice that after setting the position, we immediately update the `CurrentState`. This ensures our state tracking remains accurate even when manually moving the cursor.
+
+That's it for the `MouseInfo` class, next we will move onto gamepad input.
+
+## The GamePadInfo Class
+
+To manage gamepad input effectively, we need to track both current and previous states, is the gamepad still connected, as well as provide easy access to the thumbstick values, trigger values, and button states. The `GamePadInfo` class will encapsulate all of this functionality, making it easier to:
+
+- Track current and previous gamepad states.
+- Check if the gamepad is still connected.
+- Track the position of the left and right thumbsticks.
+- Check the values of the left and right triggers.
+- Detect when gamepad buttons are pressed or released.
+- Check if gamepad buttons are being held down.
+- Start and Stop vibration of a gamepad.
+
+To get started, in the `Input` folder f the *MonoGameLibrary* project, create a new file name `GamePadInfo.cs` with the following initial structure:
+
+[!code-csharp[](./snippets/gamepadinfo.cs#declaration)]
+
+### GamePadInfo Properties
+
+We use vibration in gamepads to provide haptic feedback to the player. The [**GamePad**](xref:Microsoft.Xna.Framework.Input.GamePad) class provides the [**SetVibration**](xref:Microsoft.Xna.Framework.Input.GamePad.SetVibration(Microsoft.Xna.Framework.PlayerIndex,System.Single,System.Single)) method to tell the gamepad to vibrate, but it does not provide a timing mechanism for it if we wanted to only vibrate for a certain period of time. Add the following private field to the `GamePadInfo` class:
+
+[!code-csharp[](./snippets/gamepadinfo.cs#member_fields)]
+
+If you recall from the [previous chapter](../10_handling_input/index.md#gamepad-input), a [**PlayerIndex**](xref:Microsoft.Xna.Framework.PlayerIndex) value needs to be supplied when calling [**Gamepad.GetState**](xref:Microsoft.Xna.Framework.Input.GamePad.GetState(Microsoft.Xna.Framework.PlayerIndex)). Doing this returns the state of the gamepad connected at that player index. So we will need a property to track the player index this gamepad info is for.
+
+[!code-csharp[](./snippets/gamepadinfo.cs#properties_playerindex)]
+
+To detect changes in the gamepad input between frames, we need to track both the previous and current gamepad states. Add these properties to the `GamePadInfo` class:
+
+[!code-csharp[](./snippets/gamepadinfo.cs#properties_state)]
+
+There are times that a gamepad can disconnect for various reasons; being unplugged, bluetooth disconnection, or battery dying are just some examples. To track if the gamepad is connected, add the following property:
+
+[!code-csharp[](./snippets/gamepadinfo.cs#properties_connected)]
+
+The values of the thumbsticks and triggers can be accessed through the `CurrentState`. However, instead of having to navigate through multiple property chains to get this information, add the following properties to get direct access to the values:
+
+[!code-csharp[](./snippets/gamepadinfo.cs#properties_thumbsticks_triggers)]
+
+### GamePadInfo Constructor
+
+The `GamePadInfo` class constructor needs to initialize the gamepad states.
+
+Add this constructor:
+
+[!code-csharp[](./snippets/gamepadinfo.cs#ctors)]
+
+This constructor
+
+- Requires a [**PlayerIndex**](xref:Microsoft.Xna.Framework.PlayerIndex) value which is stored and will be used to get the states for the correct gamepad
+- Creates an empty state for `PreviousState` since there is no previous state yet.
+- Gets the current gamepad state as our starting `CurrentState`.
+
+This initialization ensures we have valid states to compare against in the first frame of our game, preventing any potential null reference issues when checking for input changes.
+
+### GamePadInfo Methods
+
+The `GamePadInfo` class needs methods for updating states, checking button states, and controlling vibration. Add the following method to the `GamePadInfo` class:
+
+[!code-csharp[](./snippets/gamepadinfo.cs#methods_update)]
+
+> [!NOTE]
+> Unlike keyboard and mouse input, the gamepad update method takes a [**GameTime**](xref:Microsoft.Xna.Framework.GameTime) parameter. This allows us to track and manage timed vibration effects.
+
+Next, we will add methods to check various button states:
+
+[!code-csharp[](./snippets/gamepadinfo.cs#methods_buttonstate)]
+
+These methods serve two distinct purposes. For checking continuous states:
+
+- `IsButtonDown`: Returns true as long as a button is being held down.
+- `IsButtonUp`: Returns true as long as a button is not being pressed.
+
+And for detecting state changes:
+
+- `WasButtonJustPressed`: Returns true only on the frame when a button changes from up-to-down.
+- `WasButtonJustReleased`: Returns true only on the frame when a button changes from down-to-up.
+
+Finally, we will add methods for controlling gamepad vibration:
+
+[!code-csharp[](./snippets/gamepadinfo.cs#methods_vibration)]
+
+The vibration methods provide control over the gamepad's haptic feedback:
+
+- `SetVibration`: Starts vibration at the specified strength for a set duration.
+- `StopVibration`: Immediately stops all vibration.
+
+> [!TIP]
+> When setting vibration, you can specify both the strength (`0.0f` to `1.0f`) and duration. The vibration will automatically stop after the specified time has elapsed, so you do not need to manage stopping it manually.
+
+That's it for the `GamePadInfo` class. Next, we can create the actual input manager.
+
+## The InputManager Class
+
+Now that we have classes to handle keyboard, mouse, and gamepad input individually, we can create a centralized manager class to coordinate all input handling.
+
+In the `Input` folder of the *MonoGameLibrary* project, add a new file named `InputManager.cs` with this initial structure:
+
+[!code-csharp[](./snippets/inputmanager.cs#declaration)]
+
+### InputManager Properties
+
+The `InputManager` class needs properties to access each type of input device. Add these properties:
+
+[!code-csharp[](./snippets/inputmanager.cs#properties)]
+
+> [!NOTE]
+> The `GamePads` property is an array because MonoGame supports up to four gamepads simultaneously. Each gamepad is associated with a PlayerIndex (0-3).
+
+### InputManager Constructor
+
+The constructor for the `InputManager` initializes the keybaord, mouse, and gamepad states.
+
+Add the following constructor:
+
+[!code-csharp[](./snippets/inputmanager.cs#ctors)]
+
+### InputManager Methods
+
+The `Update` method for the `InputManager` calls update for each device so that they can update their internal states.
+
+[!code-csharp[](./snippets/inputmanager.cs#methods)]
+
+## Implementing the InputManager Class
+
+Now that we have our input management system complete, we will update our game to use it. We will do this in two steps:
+
+1. First, update the `Core` class to add the `InputManager` globally.
+2. Update the `Game1` class to use the global input manager from `Core`.
+
+### Updating the Core Class
+
+The `Core` class serves as our base game class, so we will update it to add and expose the `InputManager` globally. Open the *Core.cs* file in the *MonoGameLibrary* project and update it to the following:
+
+[!code-csharp[](./snippets/core.cs?highlight=5-6,39-47,103-104,107-118)]
+
+The key changes to the `Core` class are:
+
+1. Added the `using MonoGameLibrary.Input;` directive to access the `InputManager` class.
+2. Added a static `Input` property to provide global access to the input manager.
+3. Added a static `ExitOnEscape` property to set whether the game should exit when the Escape key on the keyboard is pressed.
+4. In `Initialize` the input manager is created.
+5. Added an override for the `Update` method where:
+ 1. The input manager is updated
+ 2. A check is made to see if `ExitOnEscape` is true and if the Escape keyboard key is pressed.
+
+### Updating the Game1 Class
+
+Now we can update our `Game1` class to use the new input management system through the `Core` class. Open `Game1.cs` in the game project and update it to the following:
+
+[!code-csharp[](./snippets/game1.cs?highlight=6,74,80,86,92,98,106,111,114,118,124,126-127,132,138,144,150)]
+
+The key changes to the `Game1` class are:
+
+1. In [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)), the check for the gamepad back button or keyboard escape key being pressed was removed. This is now handled by the `ExitOnEscape` property and the `Update` method of the `Core` class.
+2. In `CheckKeyboardInput` and `CheckGamepadInput`, instead of getting the keyboard and gamepad states and then using the states, calls to check those devices are now done through the input.
+
+Running the game now, you will be able to control it the same as before, only now we are using our new `InputManager` class instead.
+
+|  |
+|:-----------------------------------------------------------------------------------------------:|
+| **Figure 11-1: The slime moving around based on device input** |
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+- Detect the difference between continuous and single-frame input states.
+- Create classes to manage different input devices.
+- Build a centralized `InputManager` to coordinate all input handling that is:
+ - Reusable across different game projects
+ - Easy to maintain and extend
+ - Consistent across different input devices
+- Integrate the input system into the `Core` class for global access.
+- Update the game to use the new input management system.
+
+## Test Your Knowledge
+
+1. What is the difference between checking if an input is "down" versus checking if it was "just pressed"?
+
+ :::question-answer
+ "Down" checks if an input is currently being held, returning true every frame while held. "Just pressed" only returns true on the first frame when the input changes from up to down, requiring comparison between current and previous states.
+ :::
+
+2. Why do we track both current and previous input states?
+
+ :::question-answer
+ Tracking both states allows us to detect when input changes occur by comparing the current frame's state with the previous frame's state. This is essential for implementing "just pressed" and "just released" checks.
+ :::
+
+3. What advantage does the `InputManager` provide over handling input directly?
+
+ :::question-answer
+ The `InputManager` centralizes all input handling, automatically tracks states between frames, and provides a consistent API across different input devices. This makes the code more organized, reusable, and easier to maintain.
+ :::
diff --git a/articles/tutorials/building_2d_games/11_input_management/snippets/compare_previous_state.cs b/articles/tutorials/building_2d_games/11_input_management/snippets/compare_previous_state.cs
new file mode 100644
index 00000000..9d947e75
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/snippets/compare_previous_state.cs
@@ -0,0 +1,21 @@
+// Track the state of keyboard input during the previous frame.
+private KeyboardState _previousKeyboardState;
+
+protected override void Update(GameTime gameTime)
+{
+ // Get the current state of keyboard input.
+ KeyboardState keyboardState = Keyboard.GetState();
+
+ // Compare if the space key is down on the current frame but was up on the previous frame.
+ if (keyboardState.IsKeyDown(Keys.Space) && _previousKeyboardState.IsKeyUp(Keys.Space))
+ {
+ // This will only run on the first frame Space is pressed and will not
+ // happen again until it has been released and then pressed again.
+ }
+
+ // At the end of update, store the current state of keyboard input into the
+ // previous state tracker.
+ _previousKeyboardState = keyboardState;
+
+ base.Update(gameTime);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/11_input_management/snippets/core.cs b/articles/tutorials/building_2d_games/11_input_management/snippets/core.cs
new file mode 100644
index 00000000..21457ac5
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/snippets/core.cs
@@ -0,0 +1,119 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary.Input;
+
+namespace MonoGameLibrary;
+
+public class Core : Game
+{
+ internal static Core s_instance;
+
+ ///
+ /// Gets a reference to the Core instance.
+ ///
+ public static Core Instance => s_instance;
+
+ ///
+ /// Gets the graphics device manager to control the presentation of graphics.
+ ///
+ public static GraphicsDeviceManager Graphics { get; private set; }
+
+ ///
+ /// Gets the graphics device used to create graphical resources and perform primitive rendering.
+ ///
+ public static new GraphicsDevice GraphicsDevice { get; private set; }
+
+ ///
+ /// Gets the sprite batch used for all 2D rendering.
+ ///
+ public static SpriteBatch SpriteBatch { get; private set; }
+
+ ///
+ /// Gets the content manager used to load global assets.
+ ///
+ public static new ContentManager Content { get; private set; }
+
+ ///
+ /// Gets a reference to the input management system.
+ ///
+ public static InputManager Input { get; private set; }
+
+ ///
+ /// Gets or Sets a value that indicates if the game should exit when the esc key on the keyboard is pressed.
+ ///
+ public static bool ExitOnEscape { get; set; }
+
+ ///
+ /// Creates a new Core instance.
+ ///
+ /// The title to display in the title bar of the game window.
+ /// The initial width, in pixels, of the game window.
+ /// The initial height, in pixels, of the game window.
+ /// Indicates if the game should start in fullscreen mode.
+ public Core(string title, int width, int height, bool fullScreen)
+ {
+ // Ensure that multiple cores are not created.
+ if (s_instance != null)
+ {
+ throw new InvalidOperationException($"Only a single Core instance can be created");
+ }
+
+ // Store reference to engine for global member access.
+ s_instance = this;
+
+ // Create a new graphics device manager.
+ Graphics = new GraphicsDeviceManager(this);
+
+ // Set the graphics defaults
+ Graphics.PreferredBackBufferWidth = width;
+ Graphics.PreferredBackBufferHeight = height;
+ Graphics.IsFullScreen = fullScreen;
+
+ // Apply the graphic presentation changes
+ Graphics.ApplyChanges();
+
+ // Set the window title
+ Window.Title = title;
+
+ // Set the core's content manager to a reference of hte base Game's
+ // content manager.
+ Content = base.Content;
+
+ // Set the root directory for content
+ Content.RootDirectory = "Content";
+
+ // Mouse is visible by default
+ IsMouseVisible = true;
+ }
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+
+ // Set the core's graphics device to a reference of the base Game's
+ // graphics device.
+ GraphicsDevice = base.GraphicsDevice;
+
+ // Create the sprite batch instance.
+ SpriteBatch = new SpriteBatch(GraphicsDevice);
+
+ // Create a new input manager
+ Input = new InputManager();
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ // Update the input manager
+ Input.Update(gameTime);
+
+ if (ExitOnEscape && Input.Keyboard.IsKeyDown(Keys.Escape))
+ {
+ Exit();
+ }
+
+ base.Update(gameTime);
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/11_input_management/snippets/game1.cs b/articles/tutorials/building_2d_games/11_input_management/snippets/game1.cs
new file mode 100644
index 00000000..ab380346
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/snippets/game1.cs
@@ -0,0 +1,177 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary;
+using MonoGameLibrary.Graphics;
+using MonoGameLibrary.Input;
+
+namespace DungeonSlime;
+
+public class Game1 : Core
+{
+ // Defines the slime animated sprite.
+ private AnimatedSprite _slime;
+
+ // Defines the bat animated sprite.
+ private AnimatedSprite _bat;
+
+ // Tracks the position of the slime.
+ private Vector2 _slimePosition;
+
+ // Speed multiplier when moving.
+ private const float MOVEMENT_SPEED = 5.0f;
+
+ public Game1() : base("Dungeon Slime", 1280, 720, false)
+ {
+
+ }
+
+ protected override void Initialize()
+ {
+ // TODO: Add your initialization logic here
+
+ base.Initialize();
+ }
+
+ protected override void LoadContent()
+ {
+ // Create the texture atlas from the XML configuration file
+ TextureAtlas atlas = TextureAtlas.FromFile(Content, "images/atlas-definition.xml");
+
+ // Create the slime animated sprite from the atlas.
+ _slime = atlas.CreateAnimatedSprite("slime-animation");
+ _slime.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+ _bat.Scale = new Vector2(4.0f, 4.0f);
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
+ Exit();
+
+ // Update the slime animated sprite.
+ _slime.Update(gameTime);
+
+ // Update the bat animated sprite.
+ _bat.Update(gameTime);
+
+ // Check for keyboard input and handle it.
+ CheckKeyboardInput();
+
+ // Check for gamepad input and handle it.
+ CheckGamePadInput();
+
+ base.Update(gameTime);
+ }
+
+ private void CheckKeyboardInput()
+ {
+ // If the space key is held down, the movement speed increases by 1.5
+ float speed = MOVEMENT_SPEED;
+ if (Input.Keyboard.IsKeyDown(Keys.Space))
+ {
+ speed *= 1.5f;
+ }
+
+ // If the W or Up keys are down, move the slime up on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.W) || Input.Keyboard.IsKeyDown(Keys.Up))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // if the S or Down keys are down, move the slime down on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.S) || Input.Keyboard.IsKeyDown(Keys.Down))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If the A or Left keys are down, move the slime left on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.A) || Input.Keyboard.IsKeyDown(Keys.Left))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If the D or Right keys are down, move the slime right on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.D) || Input.Keyboard.IsKeyDown(Keys.Right))
+ {
+ _slimePosition.X += speed;
+ }
+ }
+
+ private void CheckGamePadInput()
+ {
+ GamePadInfo gamePadOne = Input.GamePads[(int)PlayerIndex.One];
+
+ // If the A button is held down, the movement speed increases by 1.5
+ // and the gamepad vibrates as feedback to the player.
+ float speed = MOVEMENT_SPEED;
+ if (gamePadOne.IsButtonDown(Buttons.A))
+ {
+ speed *= 1.5f;
+ gamePadOne.SetVibration(1.0f, TimeSpan.FromSeconds(1));
+ }
+ else
+ {
+ gamePadOne.StopVibration();
+ }
+
+ // Check thumbstick first since it has priority over which gamepad input
+ // is movement. It has priority since the thumbstick values provide a
+ // more granular analog value that can be used for movement.
+ if (gamePadOne.LeftThumbStick != Vector2.Zero)
+ {
+ _slimePosition.X += gamePadOne.LeftThumbStick.X * speed;
+ _slimePosition.Y -= gamePadOne.LeftThumbStick.Y * speed;
+ }
+ else
+ {
+ // If DPadUp is down, move the slime up on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadUp))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // If DPadDown is down, move the slime down on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadDown))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If DPapLeft is down, move the slime left on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadLeft))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If DPadRight is down, move the slime right on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadRight))
+ {
+ _slimePosition.X += speed;
+ }
+ }
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
+
+ // Draw the slime sprite.
+ _slime.Draw(SpriteBatch, _slimePosition);
+
+ // Draw the bat sprite 10px to the right of the slime.
+ _bat.Draw(SpriteBatch, new Vector2(_slime.Width + 10, 0));
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/11_input_management/snippets/gamepadinfo.cs b/articles/tutorials/building_2d_games/11_input_management/snippets/gamepadinfo.cs
new file mode 100644
index 00000000..5999999f
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/snippets/gamepadinfo.cs
@@ -0,0 +1,163 @@
+#region declaration
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Input;
+
+namespace MonoGameLibrary.Input;
+
+public class GamePadInfo
+{
+
+}
+#endregion
+{
+ #region member_fields
+ private TimeSpan _vibrationTimeRemaining = TimeSpan.Zero;
+ #endregion
+
+ #region properties_playerindex
+ ///
+ /// Gets the index of the player this gamepad is for.
+ ///
+ public PlayerIndex PlayerIndex { get; }
+ #endregion
+
+ #region properties_state
+ ///
+ /// Gets the state of input for this gamepad during the previous update cycle.
+ ///
+ public GamePadState PreviousState { get; private set; }
+
+ ///
+ /// Gets the state of input for this gamepad during the current update cycle.
+ ///
+ public GamePadState CurrentState { get; private set; }
+ #endregion
+
+ #region properties_connected
+ ///
+ /// Gets a value that indicates if this gamepad is currently connected.
+ ///
+ public bool IsConnected => CurrentState.IsConnected;
+ #endregion
+
+ #region properties_thumbsticks_triggers
+ ///
+ /// Gets the value of the left thumbstick of this gamepad.
+ ///
+ public Vector2 LeftThumbStick => CurrentState.ThumbSticks.Left;
+
+ ///
+ /// Gets the value of the right thumbstick of this gamepad.
+ ///
+ public Vector2 RightThumbStick => CurrentState.ThumbSticks.Right;
+
+ ///
+ /// Gets the value of the left trigger of this gamepad.
+ ///
+ public float LeftTrigger => CurrentState.Triggers.Left;
+
+ ///
+ /// Gets the value of the right trigger of this gamepad.
+ ///
+ public float RightTrigger => CurrentState.Triggers.Right;
+ #endregion
+
+ #region ctors
+ ///
+ /// Creates a new GamePadInfo for the gamepad connected at the specified player index.
+ ///
+ /// The index of the player for this gamepad.
+ public GamePadInfo(PlayerIndex playerIndex)
+ {
+ PlayerIndex = playerIndex;
+ PreviousState = new GamePadState();
+ CurrentState = GamePad.GetState(playerIndex);
+ }
+ #endregion
+
+ #region methods_update
+ ///
+ /// Updates the state information for this gamepad input.
+ ///
+ ///
+ public void Update(GameTime gameTime)
+ {
+ PreviousState = CurrentState;
+ CurrentState = GamePad.GetState(PlayerIndex);
+
+ if (_vibrationTimeRemaining > TimeSpan.Zero)
+ {
+ _vibrationTimeRemaining -= gameTime.ElapsedGameTime;
+
+ if (_vibrationTimeRemaining <= TimeSpan.Zero)
+ {
+ StopVibration();
+ }
+ }
+ }
+ #endregion
+
+ #region methods_buttonstate
+ ///
+ /// Returns a value that indicates whether the specified gamepad button is current down.
+ ///
+ /// The gamepad button to check.
+ /// true if the specified gamepad button is currently down; otherwise, false.
+ public bool IsButtonDown(Buttons button)
+ {
+ return CurrentState.IsButtonDown(button);
+ }
+
+ ///
+ /// Returns a value that indicates whether the specified gamepad button is currently up.
+ ///
+ /// The gamepad button to check.
+ /// true if the specified gamepad button is currently up; otherwise, false.
+ public bool IsButtonUp(Buttons button)
+ {
+ return CurrentState.IsButtonUp(button);
+ }
+
+ ///
+ /// Returns a value that indicates whether the specified gamepad button was just pressed on the current frame.
+ ///
+ ///
+ /// true if the specified gamepad button was just pressed on the current frame; otherwise, false.
+ public bool WasButtonJustPressed(Buttons button)
+ {
+ return CurrentState.IsButtonDown(button) && PreviousState.IsButtonUp(button);
+ }
+
+ ///
+ /// Returns a value that indicates whether the specified gamepad button was just released on the current frame.
+ ///
+ ///
+ /// true if the specified gamepad button was just released on the current frame; otherwise, false.
+ public bool WasButtonJustReleased(Buttons button)
+ {
+ return CurrentState.IsButtonUp(button) && PreviousState.IsButtonDown(button);
+ }
+ #endregion
+
+ #region methods_vibration
+ ///
+ /// Sets the vibration for all motors of this gamepad.
+ ///
+ /// The strength of the vibration from 0.0f (none) to 1.0f (full).
+ /// The amount of time the vibration should occur.
+ public void SetVibration(float strength, TimeSpan time)
+ {
+ _vibrationTimeRemaining = time;
+ GamePad.SetVibration(PlayerIndex, strength, strength);
+ }
+
+ ///
+ /// Stops the vibration of all motors for this gamepad.
+ ///
+ public void StopVibration()
+ {
+ GamePad.SetVibration(PlayerIndex, 0.0f, 0.0f);
+ }
+ #endregion
+}
diff --git a/articles/tutorials/building_2d_games/11_input_management/snippets/inputmanager.cs b/articles/tutorials/building_2d_games/11_input_management/snippets/inputmanager.cs
new file mode 100644
index 00000000..68d765e4
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/snippets/inputmanager.cs
@@ -0,0 +1,61 @@
+#region declaration
+using Microsoft.Xna.Framework;
+
+namespace MonoGameLibrary.Input;
+
+public class InputManager { }
+
+#endregion
+{
+ #region properties
+ ///
+ /// Gets the state information of keyboard input.
+ ///
+ public KeyboardInfo Keyboard { get; private set; }
+
+ ///
+ /// Gets the state information of mouse input.
+ ///
+ public MouseInfo Mouse { get; private set; }
+
+ ///
+ /// Gets the state information of a gamepad.
+ ///
+ public GamePadInfo[] GamePads { get; private set; }
+ #endregion
+
+ #region ctors
+ ///
+ /// Creates a new InputManager.
+ ///
+ /// The game this input manager belongs to.
+ public InputManager()
+ {
+ Keyboard = new KeyboardInfo();
+ Mouse = new MouseInfo();
+
+ GamePads = new GamePadInfo[4];
+ for (int i = 0; i < 4; i++)
+ {
+ GamePads[i] = new GamePadInfo((PlayerIndex)i);
+ }
+ }
+ #endregion
+
+ #region methods
+ ///
+ /// Updates the state information for the keyboard, mouse, and gamepad inputs.
+ ///
+ /// A snapshot of the timing values for the current frame.
+ public void Update(GameTime gameTime)
+ {
+ Keyboard.Update();
+ Mouse.Update();
+
+ for (int i = 0; i < 4; i++)
+ {
+ GamePads[i].Update(gameTime);
+ }
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/11_input_management/snippets/key_down_every_frame.cs b/articles/tutorials/building_2d_games/11_input_management/snippets/key_down_every_frame.cs
new file mode 100644
index 00000000..d1075b4a
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/snippets/key_down_every_frame.cs
@@ -0,0 +1,8 @@
+// Get the current state of keyboard input.
+KeyboardState keyboardState = Keyboard.GetState();
+
+// Check if the space key is down.
+if (keyboardState.IsKeyDown(Keys.Space))
+{
+ // This runs EVERY frame the space key is held down
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/11_input_management/snippets/keyboardinfo.cs b/articles/tutorials/building_2d_games/11_input_management/snippets/keyboardinfo.cs
new file mode 100644
index 00000000..4d6e2518
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/snippets/keyboardinfo.cs
@@ -0,0 +1,85 @@
+#region declaration
+using Microsoft.Xna.Framework.Input;
+
+namespace MonoGameLibrary.Input;
+
+public class KeyboardInfo { }
+#endregion
+{
+ #region members
+ ///
+ /// Gets the state of keyboard input during the previous update cycle.
+ ///
+ public KeyboardState PreviousState { get; private set; }
+
+ ///
+ /// Gets the state of keyboard input during the current input cycle.
+ ///
+ public KeyboardState CurrentState { get; private set; }
+ #endregion
+
+ #region ctors
+ ///
+ /// Creates a new KeyboardInfo
+ ///
+ public KeyboardInfo()
+ {
+ PreviousState = new KeyboardState();
+ CurrentState = Keyboard.GetState();
+ }
+ #endregion
+
+
+ #region methods_update
+ ///
+ /// Updates the state information about keyboard input.
+ ///
+ public void Update()
+ {
+ PreviousState = CurrentState;
+ CurrentState = Keyboard.GetState();
+ }
+ #endregion
+
+ #region methods_keystate
+ ///
+ /// Returns a value that indicates if the specified key is currently down.
+ ///
+ /// The key to check.
+ /// true if the specified key is currently down; otherwise, false.
+ public bool IsKeyDown(Keys key)
+ {
+ return CurrentState.IsKeyDown(key);
+ }
+
+ ///
+ /// Returns a value that indicates whether the specified key is currently up.
+ ///
+ /// The key to check.
+ /// true if the specified key is currently up; otherwise, false.
+ public bool IsKeyUp(Keys key)
+ {
+ return CurrentState.IsKeyUp(key);
+ }
+
+ ///
+ /// Returns a value that indicates if the specified key was just pressed on the current frame.
+ ///
+ /// The key to check.
+ /// true if the specified key was just pressed on the current frame; otherwise, false.
+ public bool WasKeyJustPressed(Keys key)
+ {
+ return CurrentState.IsKeyDown(key) && PreviousState.IsKeyUp(key);
+ }
+
+ ///
+ /// Returns a value that indicates if the specified key was just released on the current frame.
+ ///
+ /// The key to check.
+ /// true if the specified key was just released on the current frame; otherwise, false.
+ public bool WasKeyJustReleased(Keys key)
+ {
+ return CurrentState.IsKeyUp(key) && PreviousState.IsKeyDown(key);
+ }
+ #endregion
+}
diff --git a/articles/tutorials/building_2d_games/11_input_management/snippets/mousebutton.cs b/articles/tutorials/building_2d_games/11_input_management/snippets/mousebutton.cs
new file mode 100644
index 00000000..dc789516
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/snippets/mousebutton.cs
@@ -0,0 +1,10 @@
+namespace MonoGameLibrary.Input;
+
+public enum MouseButton
+{
+ Left,
+ Middle,
+ Right,
+ XButton1,
+ XButton2
+}
diff --git a/articles/tutorials/building_2d_games/11_input_management/snippets/mouseinfo.cs b/articles/tutorials/building_2d_games/11_input_management/snippets/mouseinfo.cs
new file mode 100644
index 00000000..a9f11cf3
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/snippets/mouseinfo.cs
@@ -0,0 +1,229 @@
+#region declaration
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Input;
+
+namespace MonoGameLibrary.Input;
+
+public class MouseInfo
+{
+
+}
+#endregion
+{
+ #region properties_states
+ ///
+ /// The state of mouse input during the previous update cycle.
+ ///
+ public MouseState PreviousState { get; private set; }
+
+ ///
+ /// The state of mouse input during the current update cycle.
+ ///
+ public MouseState CurrentState { get; private set; }
+ #endregion
+
+ #region properties_position
+ ///
+ /// Gets or Sets the current position of the mouse cursor in screen space.
+ ///
+ public Point Position
+ {
+ get => CurrentState.Position;
+ set => SetPosition(value.X, value.Y);
+ }
+
+ ///
+ /// Gets or Sets the current x-coordinate position of the mouse cursor in screen space.
+ ///
+ public int X
+ {
+ get => CurrentState.X;
+ set => SetPosition(value, CurrentState.Y);
+ }
+
+ ///
+ /// Gets or Sets the current y-coordinate position of the mouse cursor in screen space.
+ ///
+ public int Y
+ {
+ get => CurrentState.Y;
+ set => SetPosition(CurrentState.X, value);
+ }
+ #endregion
+
+ #region properties_position_delta
+ ///
+ /// Gets the difference in the mouse cursor position between the previous and current frame.
+ ///
+ public Point PositionDelta => CurrentState.Position - PreviousState.Position;
+
+ ///
+ /// Gets the difference in the mouse cursor x-position between the previous and current frame.
+ ///
+ public int XDelta => CurrentState.X - PreviousState.X;
+
+ ///
+ /// Gets the difference in the mouse cursor y-position between the previous and current frame.
+ ///
+ public int YDelta => CurrentState.Y - PreviousState.Y;
+
+ ///
+ /// Gets a value that indicates if the mouse cursor moved between the previous and current frames.
+ ///
+ public bool WasMoved => PositionDelta != Point.Zero;
+ #endregion
+
+ #region properties_scrollwheel
+ ///
+ /// Gets the cumulative value of the mouse scroll wheel since the start of the game.
+ ///
+ public int ScrollWheel => CurrentState.ScrollWheelValue;
+
+ ///
+ /// Gets the value of the scroll wheel between the previous and current frame.
+ ///
+ public int ScrollWheelDelta => CurrentState.ScrollWheelValue - PreviousState.ScrollWheelValue;
+ #endregion
+
+ #region ctors
+ ///
+ /// Creates a new MouseInfo.
+ ///
+ public MouseInfo()
+ {
+ PreviousState = new MouseState();
+ CurrentState = Mouse.GetState();
+ }
+ #endregion
+
+ #region methods_update
+ ///
+ /// Updates the state information about mouse input.
+ ///
+ public void Update()
+ {
+ PreviousState = CurrentState;
+ CurrentState = Mouse.GetState();
+ }
+ #endregion
+
+ #region methods_buttonstate
+ ///
+ /// Returns a value that indicates whether the specified mouse button is currently down.
+ ///
+ /// The mouse button to check.
+ /// true if the specified mouse button is currently down; otherwise, false.
+ public bool IsButtonDown(MouseButton button)
+ {
+ switch (button)
+ {
+ case MouseButton.Left:
+ return CurrentState.LeftButton == ButtonState.Pressed;
+ case MouseButton.Middle:
+ return CurrentState.MiddleButton == ButtonState.Pressed;
+ case MouseButton.Right:
+ return CurrentState.RightButton == ButtonState.Pressed;
+ case MouseButton.XButton1:
+ return CurrentState.XButton1 == ButtonState.Pressed;
+ case MouseButton.XButton2:
+ return CurrentState.XButton2 == ButtonState.Pressed;
+ default:
+ return false;
+ }
+ }
+
+ ///
+ /// Returns a value that indicates whether the specified mouse button is current up.
+ ///
+ /// The mouse button to check.
+ /// true if the specified mouse button is currently up; otherwise, false.
+ public bool IsButtonUp(MouseButton button)
+ {
+ switch (button)
+ {
+ case MouseButton.Left:
+ return CurrentState.LeftButton == ButtonState.Released;
+ case MouseButton.Middle:
+ return CurrentState.MiddleButton == ButtonState.Released;
+ case MouseButton.Right:
+ return CurrentState.RightButton == ButtonState.Released;
+ case MouseButton.XButton1:
+ return CurrentState.XButton1 == ButtonState.Released;
+ case MouseButton.XButton2:
+ return CurrentState.XButton2 == ButtonState.Released;
+ default:
+ return false;
+ }
+ }
+
+ ///
+ /// Returns a value that indicates whether the specified mouse button was just pressed on the current frame.
+ ///
+ /// The mouse button to check.
+ /// true if the specified mouse button was just pressed on the current frame; otherwise, false.
+ public bool WasButtonJustPressed(MouseButton button)
+ {
+ switch (button)
+ {
+ case MouseButton.Left:
+ return CurrentState.LeftButton == ButtonState.Pressed && PreviousState.LeftButton == ButtonState.Released;
+ case MouseButton.Middle:
+ return CurrentState.MiddleButton == ButtonState.Pressed && PreviousState.MiddleButton == ButtonState.Released;
+ case MouseButton.Right:
+ return CurrentState.RightButton == ButtonState.Pressed && PreviousState.RightButton == ButtonState.Released;
+ case MouseButton.XButton1:
+ return CurrentState.XButton1 == ButtonState.Pressed && PreviousState.XButton1 == ButtonState.Released;
+ case MouseButton.XButton2:
+ return CurrentState.XButton2 == ButtonState.Pressed && PreviousState.XButton2 == ButtonState.Released;
+ default:
+ return false;
+ }
+ }
+
+ ///
+ /// Returns a value that indicates whether the specified mouse button was just released on the current frame.
+ ///
+ /// The mouse button to check.
+ /// true if the specified mouse button was just released on the current frame; otherwise, false.F
+ public bool WasButtonJustReleased(MouseButton button)
+ {
+ switch (button)
+ {
+ case MouseButton.Left:
+ return CurrentState.LeftButton == ButtonState.Released && PreviousState.LeftButton == ButtonState.Pressed;
+ case MouseButton.Middle:
+ return CurrentState.MiddleButton == ButtonState.Released && PreviousState.MiddleButton == ButtonState.Pressed;
+ case MouseButton.Right:
+ return CurrentState.RightButton == ButtonState.Released && PreviousState.RightButton == ButtonState.Pressed;
+ case MouseButton.XButton1:
+ return CurrentState.XButton1 == ButtonState.Released && PreviousState.XButton1 == ButtonState.Pressed;
+ case MouseButton.XButton2:
+ return CurrentState.XButton2 == ButtonState.Released && PreviousState.XButton2 == ButtonState.Pressed;
+ default:
+ return false;
+ }
+ }
+ #endregion
+
+ #region methods_setposition
+ ///
+ /// Sets the current position of the mouse cursor in screen space and updates the CurrentState with the new position.
+ ///
+ /// The x-coordinate location of the mouse cursor in screen space.
+ /// The y-coordinate location of the mouse cursor in screen space.
+ public void SetPosition(int x, int y)
+ {
+ Mouse.SetPosition(x, y);
+ CurrentState = new MouseState(
+ x,
+ y,
+ CurrentState.ScrollWheelValue,
+ CurrentState.LeftButton,
+ CurrentState.MiddleButton,
+ CurrentState.RightButton,
+ CurrentState.XButton1,
+ CurrentState.XButton2
+ );
+ }
+ #endregion
+}
diff --git a/articles/tutorials/building_2d_games/11_input_management/videos/input-moving-slime.webm b/articles/tutorials/building_2d_games/11_input_management/videos/input-moving-slime.webm
new file mode 100644
index 00000000..a4d21c4a
Binary files /dev/null and b/articles/tutorials/building_2d_games/11_input_management/videos/input-moving-slime.webm differ
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/images/aabb-collision-example.svg b/articles/tutorials/building_2d_games/12_collision_detection/images/aabb-collision-example.svg
new file mode 100644
index 00000000..f253d849
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/images/aabb-collision-example.svg
@@ -0,0 +1,93 @@
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/images/aabb-vs-non-aabb.svg b/articles/tutorials/building_2d_games/12_collision_detection/images/aabb-vs-non-aabb.svg
new file mode 100644
index 00000000..b326edda
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/images/aabb-vs-non-aabb.svg
@@ -0,0 +1,199 @@
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/images/circle-collision.svg b/articles/tutorials/building_2d_games/12_collision_detection/images/circle-collision.svg
new file mode 100644
index 00000000..cd9c4f0a
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/images/circle-collision.svg
@@ -0,0 +1,635 @@
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/images/circle-distance-right-triangle.svg b/articles/tutorials/building_2d_games/12_collision_detection/images/circle-distance-right-triangle.svg
new file mode 100644
index 00000000..55530dec
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/images/circle-distance-right-triangle.svg
@@ -0,0 +1,96 @@
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/images/reflection-diagram.svg b/articles/tutorials/building_2d_games/12_collision_detection/images/reflection-diagram.svg
new file mode 100644
index 00000000..29588376
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/images/reflection-diagram.svg
@@ -0,0 +1,298 @@
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/index.md b/articles/tutorials/building_2d_games/12_collision_detection/index.md
new file mode 100644
index 00000000..4736510b
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/index.md
@@ -0,0 +1,419 @@
+---
+title: "Chapter 12: Collision Detection"
+description: "Learn how to implement collision detection between game objects and handle collision responses like blocking, triggering events, and bouncing."
+---
+
+In [Chapter 11](../11_input_management/index.md), you learned how to manage player input to control game objects. However, for objects in your game to interact with each other, collecting items, hitting obstacles, or triggering events, you need to detect when these objects come into contact. This is accomplished through collision detection.
+
+In this chapter you will:
+
+- Understand different collision shapes and their use cases.
+- Implement rectangle-based collision detection.
+- Create circle-based collision detection.
+- Learn how to handle object overlap and response.
+- Build a reusable collision system for your game.
+
+We will first start by understanding the basics of collision detection and the different approaches that can be used.
+
+> [!NOTE]
+> There is a lot to understand when it comes to collision detection and the many complex ways that two objects can be considered IN collsion or NEAR collision. It is critical to get an understanding of the basics before jumping into code. So buckle up, we have a story to tell before you can get back to the keyboard.
+>
+> Feel free to keep coming back to this chapter and refer to the content when you need to, with a fresh cup of coffee.
+
+## Understanding Collision Detection
+
+Before we start implementing collision detection, we should discuss what collision detection actually is. In 2D games, collision detection involves checking if two objects interact with each other in some way. There are several approaches to detecting collisions, ranging from simple to complex:
+
+### Proximity Collision Detection
+
+The simplest form is checking if objects are within a certain range of each other. This is useful when you only need to know if objects are "near" each other like detecting if an enemy is close enough to chase a player or if two objects are close enough to perform a more complex collision check.
+
+### Simple Shape Based Collision Detection
+
+Shaped based collision detection checks if two shapes overlap. The most common and simple shapes used are circles and rectangles:
+
+#### Circle Collision Detection
+
+Circle collision detection is computationally a simpler check than that rectangles. There are also no special considerations if the circles are rotated, which makes them easier to use. To determine if two circle shapes are overlapping, we only need to check if the square of the sum of the radii between the two circles is less than the squared distance between the two circles with the following formula:
+
+Two find the distance between two circles, imagine drawing a line from the center of one circle to the center of the other. This length of this line is the distance, but we could also calculate it by first walking up or down and then walking left or right from the center of one circle to another, forming a right triangle.
+
+|  |
+| :---------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 12-1: Showing the distance between the center of two circles forms a right triangle** |
+
+In the *Figure 12-1* above
+
+- $a$ is the distance between the center of the two on the x-axis (horizontal).
+- $b$ is the distance between the center of the two circles on the y-axis (vertical).
+- $c$ is the total distance between the center of the two circles.
+
+Since this forms a right triangle, to calculate the squared distance, we can use Pythagorean's Theorem:
+
+$$c^2 = a^2 + b^2$$
+
+Then we just check if the squared sum of the radii of the two circles is less than the squared distance:
+
+$$(radius_{circle1} + radius_{circle2})^2 < c^2$$
+
+If it is less, then the circles are overlapping; otherwise, they are not.
+
+To calculate the squared distance between to points, MonoGame provides the [**Vector2.DistanceSquared**](xref:Microsoft.Xna.Framework.Vector2.DistanceSquared(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) method:
+
+[!code-csharp[](./snippets/vector2_distance.cs)]
+
+> [!TIP]
+> MonoGame also provides a distance calculation method with [**Vector2.Distance**](xref:Microsoft.Xna.Framework.Vector2.Distance(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) which returns the distance by providing the square root of the distance squared. So why do not we use this instead?
+>
+> Square root operations are more computationally complex for a CPU. So instead of getting the normal distance, which would require the square root operation, it is more efficient for the cpu to multiply the sum of the radii by itself to get the squared sum and use that for comparison instead.
+
+#### Rectangle Collision Detection
+
+Rectangles, often called *bounding boxes*, typically uses what is called *Axis-Aligned Bounding Box* (AABB) collision detection to determine if two rectangle shapes overlap. Unlike circles, to perform AABB collision detection, the x- and y-axes of both rectangles must be aligned with the x- and y-axes of the screen. This is just another way of saying that the rectangles cannot be rotated.
+
+|  |
+| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 12-2: The rectangle on the left is axis-aligned since both the axes are aligned with the screen axes. The rectangle on the right is non axis-aligned since it is rotated and the axes do not align with the screen axes** |
+
+MonoGame provides the [**Rectangle**](xref:Microsoft.Xna.Framework.Rectangle) struct which represents a rectangle by its position (X,Y) and size (Width,Height). The following table shows some of the properties of the [**Rectangle**](xref:Microsoft.Xna.Framework.Rectangle) struct:
+
+| Property | Type | Description |
+| ----------------------------------------------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [**Bottom**](xref:Microsoft.Xna.Framework.Rectangle.Bottom) | `int` | Returns the y-coordinate location of the bottom edge of the rectangle. This is equal to [**Rectangle.Y**](xref:Microsoft.Xna.Framework.Rectangle.Y) plus the height of the rectangle. |
+| [**Left**](xref:Microsoft.Xna.Framework.Rectangle.Left) | `int` | Returns the x-coordinate location of the left edge of the rectangle. This is equal to [**Rectangle.X**](xref:Microsoft.Xna.Framework.Rectangle.X). |
+| [**Right**](xref:Microsoft.Xna.Framework.Rectangle.Right) | `int` | Returns the x-coordinate location of the right edge of the rectangle. This is equal to [**Rectangle.X**](xref:Microsoft.Xna.Framework.Rectangle.X) plus the width of the rectangle. |
+| [**Top**](xref:Microsoft.Xna.Framework.Rectangle.Top) | `int` | Returns the y-coordinate location of the top edge of the rectangle. This is equal to [**Rectangle.Y**](xref:Microsoft.Xna.Framework.Rectangle.Y). |
+
+To determine if two rectangles overlap using AABB collision detection, there are four conditions that need to be checked, and all four conditions must be true. Given two rectangles $A$ and $B$, these conditions are:
+
+1. $A_{Left}$ must be less than $B_{Right}$.
+2. $A_{Right}$ must be greater than $B_{Left}$.
+3. $A_{Top}$ must be less than $B_{Bottom}$.
+4. $A_{Bottom}$ must be greater than $B_{Top}$.
+
+If even a single one of these conditions is false, then the rectangles are not overlapping and thus not colliding.
+
+MonoGame provides the [**Rectangle.Intersects**](xref:Microsoft.Xna.Framework.Rectangle.Intersects(Microsoft.Xna.Framework.Rectangle)) method which will perform an AABB collision check for us:
+
+[!code-csharp[](./snippets/rectangle_intersects.cs)]
+
+|  |
+| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 12-3: The rectangle on the left is overlapping the rectangle on the right based on the conditions required for the Axis-Aligned Bounding Box collision check** |
+
+#### Complex Polygon Collision Detection
+
+Complex polygon collision detection uses a method called *Separating Axis Theorem* (SAT) to determine if two polygon shapes overlap. SAT uses more complex calculations that can determine if any polygon shape overlaps with another polygon shape, including if they are rotated. There are performance considerations to consider when using SAT.
+
+Implementing SAT is out-of-scope for this tutorial. If you are interested in further reading about this, please see the following articles as a good starting point:
+
+- [Separating Axis Theorem (SAT) Explanation](https://www.sevenson.com.au/actionscript/sat/).
+- [Collision Detection Using the Separating Axis Theorem](https://gamedevelopment.tutsplus.com/tutorials/collision-detection-using-the-separating-axis-theorem--gamedev-169) by Kah Shiu Chong.
+- [N Tutorial A - Collision Detection and Response](http://www.metanetsoftware.com/technique/tutorialA.html).
+
+#### Choosing a Collision Detection Method
+
+When determining which collision detection method to use, you should start with the simplest one that meets the needs of your game. If distance checks work for your game mechanic, there's no need to implement more complex shape based detections. Similarly, if a circle can represent the bounding area of a game object, start with that before moving onto rectangles.
+
+Some other points to consider are
+
+- Circles:
+ - Better for round objects like balls and coins.
+ - More accurate for rotating objects.
+ - Simpler check for overlap than rectangles.
+- Rectangles:
+ - Great for walls, platforms, and most game objects.
+ - Easy to visualize and debug.
+ - Works well with tile-based games.
+
+### Collision Detection vs Collision Response
+
+Often times when talking about collision detection, the term is used to mean both the detection of overlapping shapes and what to do once a positive check has occurred. What you do after a positive collision check has occurred is called the *collision response*. Some of the common responses are:
+
+#### Blocking Collision Response
+
+A blocking collision response is the most basic response which just prevents the two objects from overlapping. This is commonly used for walls, platforms and other solid objects. To perform a blocking collision response:
+
+1. Store the location of an object calculating the new location to move it to.
+2. Check if it is overlapping an object at the new location:
+
+- If it is overlapping, then set the position to the the position before it was moved.
+- If it is not overlapping, set the position to the new calculated position.
+
+For example:
+
+[!code-csharp[](./snippets/blocking_example.cs)]
+
+Sometimes, instead of preventing an object from moving onto another object, we want to ensure an object remains contained within a certain bounding area. MonoGame also provides the [**Rectangle.Contains**](xref:Microsoft.Xna.Framework.Rectangle.Contains(Microsoft.Xna.Framework.Rectangle)) method that we can use to determine this. [**Rectangle.Contains**](xref:Microsoft.Xna.Framework.Rectangle.Contains(Microsoft.Xna.Framework.Rectangle)) can check if any of the following are completely contained within the bounds of the rectangle;
+
+- [**Point**](xref:Microsoft.Xna.Framework.Point)
+- [**Rectangle**](xref:Microsoft.Xna.Framework.Rectangle)
+- [**Vector2**](xref:Microsoft.Xna.Framework.Vector2)
+
+For example, if we wanted to perform a blocking collision response that ensure a sprite remained contained within the bounds of the game screen:
+
+[!code-csharp[](./snippets/contains_example.cs)]
+
+> [!TIP]
+> Use [**GraphicsDevice.PresentationParameters**](xref:Microsoft.Xna.Framework.Graphics.GraphicsDevice.PresentationParameters) to get the actual screen dimensions instead of [**GraphicsDeviceManager.PreferredBackBufferWidth**](xref:Microsoft.Xna.Framework.GraphicsDeviceManager.PreferredBackBufferWidth) and [**GraphicsDeviceManager.PreferredBackBufferHeight**](xref:Microsoft.Xna.Framework.GraphicsDeviceManager.PreferredBackBufferHeight). The preferred values are only hints and may not reflect the actual back buffer size.
+
+#### Trigger Collision Response
+
+Sometimes you want to trigger an event, rather than block movement, when a collision occurs. Common examples include
+
+- Collecting items.
+- Activating switches.
+- Entering zones or areas.
+- Triggering cutscenes.
+
+Performing a trigger collision response is just simply checking if the game object is overlapping with the bounding area of the trigger zone, and if so trigger the event.
+
+For example:
+
+[!code-csharp[](./snippets/trigger_example.cs)]
+
+#### Bounce Collision Response
+
+For games that need objects to bonce off each other (like a the ball in a Pong game), we need to calculate how their velocity should change after the collision. MonoGame provides the [**Vector2.Reflect**](xref:Microsoft.Xna.Framework.Vector2.Reflect(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) method to handle this calculation for us. The method needs two pieces of information:
+
+1. The incoming vector (the direction something is moving).
+2. The normal vector (the direction perpendicular to the surface).
+
+|  |
+| :--------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 12-4: A diagram showing how an incoming vector reflects off of a surface base around the normal vector of the surface** |
+
+As shown in the diagram above, when an incoming vector hits a surface, it reflects at the same angle ($\theta$) relative to the normal vector.
+
+> [!TIP]
+> Think of the normal vector like the line you'd draw perpendicular to a mirror's surface. The angle between your incoming path and this line will be the same as the angle between your reflection and this line.
+
+For example, if we had a ball moving around the screen and wanted it to bounce off the edges of the screen:
+
+[!code-csharp[](./snippets/bounce_example.cs)]
+
+> [!TIP]
+> [**Vector2.UnitX**](xref:Microsoft.Xna.Framework.Vector2.UnitX) is $(1, 0)$ and [**Vector2.UnitY**](xref:Microsoft.Xna.Framework.Vector2.UnitY) is $(0, 1)$. We use these to get the screen edge normal since the edges of the screen are not at an angle. For more complex surfaces, you would need to calculate the appropriate normal vector based on the surface angle
+
+### Optimizing Collision Performance
+
+When checking for collisions between multiple objects, testing every object against every other object (often called brute force checking) becomes inefficient as your game grows. Brute force checking can be calculated as $(n * (n - 1)) / 2$ where $n$ is the total number of objects. For example, if you have 100 objects in your game, that's $(100 * 99) / 2 = 4950$ collision checks every frame. To improve performance, we can use a two-phase approach:
+
+1. Broad Phase: A quick, simple check to rule out objects that definitely are not colliding.
+2. Narrow Phase: A more precise check only performed on objects that passed the broad phase.
+
+For our simple game with just two objects, this optimization is not necessary. However, as you develop more complex games, implementing a broad-phase check can significantly improve performance. Later in this tutorial series we will implement an algorithm called spatial hashing to perform broad phase checks.
+
+> [!NOTE]
+> Time to get back to the code! The fun starts again here.
+
+## The Circle Struct
+
+For our game, we are going to implement circle based collision detection. MonoGame does not have a `Circle` struct to represent a circle like it does with [**Rectangle**](xref:Microsoft.Xna.Framework.Rectangle). Before we can perform circle collision, we will need to create our own.
+
+In the root of the *MonoGameLibrary* project, add a new file named `Circle.cs`. Add the following code as the foundation of the `Circle` struct:
+
+[!code-csharp[](./snippets/cirlce.cs#declaration)]
+
+> [!NOTE]
+> Notice that the struct has declared it will implement the [`IEquatable`](https://learn.microsoft.com/en-us/dotnet/api/system.iequatable-1) Interface. When creating value types like this, it is recommended to implement `IEquatable` because it has better performance for comparing objects and can help avoid boxing.
+>
+> For more information on recommended design guidelines for structs, see [Struct Design - Framework Design Guidelines | Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/struct)
+>
+> Ignore the red squiggles for now, they will be resolved as we add more code.
+
+### Circle Fields
+
+The `Circle` struct uses both private and public fields to store its state.
+
+First, add the following private static field that stores a reusable empty circle:
+
+[!code-csharp[](./snippets/cirlce.cs#fields_static)]
+
+Next, add the following public fields that define the circle's position and size:
+
+[!code-csharp[](./snippets/cirlce.cs#fields)]
+
+These public fields store the fundamental properties of the circle:
+
+- `X` and `Y` define the center point location.
+- `Radius` defines how far the circle extends from its center.
+
+### Circle Properties
+
+The `Circle` struct provides properties to access its location, state, and its boundaries.
+
+Add the following property to get the location of the circle as a [**Point**](xref:Microsoft.Xna.Framework.Point) value:
+
+[!code-csharp[](./snippets/cirlce.cs#properties_location)]
+
+Add the following properties to track empty circles:
+
+[!code-csharp[](./snippets/cirlce.cs#properties_empty)]
+
+> [!NOTE]
+> The `Empty` property returns a reusable instance of an empty circle stored in the private static field `s_empty`. This is more efficient than creating new empty circles each time one is needed, as it reuses the same instance in memory.
+
+Add the following properties for getting the circle's boundaries:
+
+[!code-csharp[](./snippets/cirlce.cs#properties_boundaries)]
+
+> [!TIP]
+> These boundary properties are particularly useful when you need to know the extent of a circle in screen space, such as determining if a circle is visible on screen or creating a bounding box around the circle.
+
+### Circle Constructors
+
+The `Circle` struct provides two ways to create a new circle:
+
+[!code-csharp[](./snippets/cirlce.cs#ctors)]
+
+* The first constructor accepts individual x and y coordinates for the circle's center.
+* The second accepts a [**Point**](xref:Microsoft.Xna.Framework.Point) struct that combines both coordinates.
+
+Both constructors require a radius value that defines the circle's size.
+
+### Circle Methods
+
+The `Circle` struct implements several methods to support equality comparison between circles. These methods allow us to check if two circles are identical (have the same center position and radius).
+
+First, add the following method that will check if two circles are overlapping with each other:
+
+[!code-csharp[](./snippets/cirlce.cs#methods_intersects)]
+
+Next we start implementing the [`IEquatable`](https://learn.microsoft.com/en-us/dotnet/api/system.iequatable-1) Interface, add the following methods for comparing a circle with another object:
+
+[!code-csharp[](./snippets/cirlce.cs#methods_equals)]
+
+Next, add the following override for `GetHashCode` to support using circles in hash-based collections:
+
+[!code-csharp[](./snippets/cirlce.cs#methods_hashcode)]
+
+Finally, add the following operator overloads to support using == and != with circles:
+
+[!code-csharp[](./snippets/cirlce.cs#methods_operators)]
+
+IEquatable interface implemented, red squiggles be gone.
+
+> [!TIP]
+> The operator overloads allow you to compare circles using familiar syntax:
+>
+> [!code-csharp[](./snippets/circle_equal_example.cs)]
+
+Now that we have a struct to represent a circle and check for overlapping, we will update our game to implement collision detection and responses.
+
+## Adding Collision To Our Game
+
+If you run the game right now and move the slime around, you will notice a few issues that can be fixed by adding collision detection and response:
+
+1. You can move the slime outside the bounds of the screen.
+2. Nothing occurs when the slime collides with the bat.
+3. The bat does not move, providing no challenge in the game.
+
+We can now implement these features using collision detection and response in our game. In the *DungeonSlime* project (your main game project), open the `Game1.cs` file and make the following changes to the `Game1` class:
+
+[!code-csharp[](./snippets/game1.cs?highlight=1,5,25-29,40-45,79-179,184-196,296-297)]
+
+The key changes made here are:
+
+1. The field `_batPosition` was added to track the position of the bat.
+2. The field `_batVelocity` was added to track the velocity of the bat.
+3. The `AssignRandomBatVelocity()` method was added which calculates a random x and y velocity for the bat to move at when called.
+4. In [**Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize), the initial position of the bat is set and `AssignRandomVelocity` is called to assign the initial velocity for the bat.
+5. In [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)), collision detection and response logic was added to perform the following in order:
+ 1. A [**Rectangle**](xref:Microsoft.Xna.Framework.Rectangle) bound is created to represent the bounds of the screen.
+ 2. A `Circle` bound is created to represent the bounds of the slime.
+ 3. Distance based checks are performed to ensure that the slime cannot move outside of the screen, the resolution of which is to perform a blocking response.
+ 4. A new position for the bat is calculated based on the current velocity of the bat.
+ 5. A `Circle` bound is created to represent the bounds of the bat.
+ 6. Distance based checks are performed to ensure the bat cannot move outside of the screen, the resolution of which is to perform a bounce response.
+ 7. A collision check is made to determine if the slime and bat are colliding (bat "eating" the slime). If so, the bat is assigned a new random position within the screen and assigned a new random velocity.
+6. In [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)), the bat is now drawn using the `_batPosition` value.
+
+Running the game now
+
+- The bat will start moving with a random velocity and bounce off the edges of the screen
+- You can move the slime around, but cannot leave the bounds of the screen with the slime.
+- If you move the slime to collide ("eat") the bat, the bat will respawn at a new location with a new velocity.
+
+|  |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 12-5: When the slime collides ("eats") the bat, the bat respawns in a new location on the screen with a random velocity assigned** |
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+- Learned about different approaches to collision detection:
+ - Distance-based checks for simple proximity detection.
+ - Shape-based checks using circles and rectangles.
+ - Complex polygon checks using SAT.
+- Understood when to use different collision shapes:
+ - Circles for round objects and rotation.
+ - Rectangles for walls and platforms.
+- Explored different types of collision responses:
+ - Blocking to prevent objects from overlapping.
+ - Triggering to cause events when objects collide.
+ - Bouncing to reflect objects off surfaces.
+- Created reusable components:
+ - Implemented a Circle struct for circle-based collision.
+ - Added methods to detect circle intersection.
+- Applied collision concepts to our game:
+ - Added screen boundary collision for the slime.
+ - Implemented bouncing behavior for the bat.
+ - Created a trigger response when the slime "eats" the bat.
+
+In the next chapter, we will explore using tilesets and tilemaps to create tile based environments for our game.
+
+## Test Your Knowledge
+
+1. What is the difference between collision detection and collision response?
+
+ ::: question-answer
+ Collision detection is determining when two objects overlap or intersect, while collision response is what happens after a collision is detected (like blocking movement, triggering events, or bouncing objects off each other).
+ :::
+
+2. When using Rectangle.Intersects for AABB collision, what four conditions must all be true for a collision to occur?
+
+ ::: question-answer
+ For two rectangles A and B to collide:
+
+ 1. A's left edge must be less than B's right edge
+ 2. A's right edge must be greater than B's left edge
+ 3. A's top edge must be less than B's bottom edge
+ 4. A's bottom edge must be greater than B's top edge
+
+ :::
+
+3. When implementing circle collision, why do we compare the distance between centers to the sum of the radii?
+
+ ::: question-answer
+ Two circles are colliding if the distance between their centers is less than the sum of their radii. If the distance is greater, they are separate. If the distance equals the sum of radii, they are just touching at one point.
+ :::
+
+4. When implementing bounce collision response, what two pieces of information does [**Vector2.Reflect**](xref:Microsoft.Xna.Framework.Vector2.Reflect(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) need?
+
+ ::: question-answer
+ [**Vector2.Reflect**](xref:Microsoft.Xna.Framework.Vector2.Reflect(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) needs:
+
+ 1. The incoming vector (direction the object is moving)
+ 2. The normal vector (direction perpendicular to the surface being hit)
+
+ :::
+
+5. Why might you choose to use circle collision over rectangle collision for certain objects?
+
+ ::: question-answer
+ Circle collision might be chosen because:
+
+ - It is more accurate for round objects
+ - It handles rotating objects better
+ - It is simpler for continuous collision detection
+ - It is natural for radius-based interactions
+
+ :::
+
+6. In the blocking collision response example, why do we store the previous position before handling input?
+
+ ::: question-answer
+ We store the previous position so that if a collision occurs after movement, we can reset the object back to its last valid position. This prevents objects from moving through each other by undoing any movement that would cause overlap.
+ :::
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/snippets/blocking_example.cs b/articles/tutorials/building_2d_games/12_collision_detection/snippets/blocking_example.cs
new file mode 100644
index 00000000..0716995f
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/snippets/blocking_example.cs
@@ -0,0 +1,31 @@
+// Store the current location
+Vector2 previousLocation = _spriteLocation;
+
+// Calculate a new location
+Vector2 newLocation = _spriteLocation + new Vector2(10, 0);
+
+// Create a bounding box for the sprite object
+Rectangle spriteBounds = new Rectangle(
+ (int)newLocation.X,
+ (int)newLocation.Y,
+ (int)_sprite.Width,
+ (int)_sprite.Height
+);
+
+// Create a bounding box for the blocking object
+Rectangle blockingBounds = new Rectangle(
+ (int)_blockingLocation_.X,
+ (int)_blockingLocation_.Y,
+ (int)_blockingSprite_.Width,
+ (int)_blockingSprite_.Height
+);
+
+// Detect if they are colliding
+if(spriteBounds.Intersects(blockingBounds))
+{
+ // Respond by not allowing the sprite to move by setting
+ // the location back to the previous location.
+ newLocation = previousLocation;
+}
+
+_spriteLocation = newLocation;
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/snippets/bounce_example.cs b/articles/tutorials/building_2d_games/12_collision_detection/snippets/bounce_example.cs
new file mode 100644
index 00000000..b839521d
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/snippets/bounce_example.cs
@@ -0,0 +1,68 @@
+// Calculate the new position of the ball based on the velocity
+Vector2 newPosition = _ballPosition + _ballVelocity;
+
+// Get the bounds of the ball as a rectangle
+Rectangle ballBounds = new Rectangle(
+ (int)_ballPosition.X,
+ (int)_ballPosition.Y,
+ (int)_ball.Width,
+ (int)_ball.Height
+);
+
+// Get the bounds of the screen as a rectangle
+Rectangle screenBounds = new Rectangle(
+ 0,
+ 0,
+ GraphicsDevice.PresentationParameters.BackBufferWidth,
+ GraphicsDevice.PresentationParameters.BackBufferHeight
+);
+
+// Detect if the ball object is within the screen bounds
+if(!screenBounds.Contains(ballBounds))
+{
+ // Ball would move outside the screen
+ // First find the distance from the edge of the ball to each edge of the screen.
+ float distanceLeft = Math.Abs(screenBounds.Left - ballBounds.Left);
+ float distanceRight = Math.Abs(screenBounds.Right - ballBounds.Right);
+ float distanceTop = Math.Abs(screenBounds.Top - ballBounds.Top);
+ float distanceBottom = Math.Abs(screenBounds.Bottom - ballBounds.Bottom);
+
+ // Determine which screen edge is the closest
+ float minDistance = Math.Min(
+ Math.Min(distanceLeft, distanceRight),
+ Math.Min(distanceTop, distanceBottom)
+ );
+
+ // Determine the normal vector based on which screen edge is the closest
+ Vector2 normal;
+ if (minDistance == distanceLeft)
+ {
+ // Closest to the left edge
+ normal = Vector2.UnitX;
+ newPosition.X = 0;
+ }
+ else if (minDistance == distanceRight)
+ {
+ // Closest to the right edge
+ normal = -Vector2.UnitX;
+ newPosition.X = screenBounds.Right - _ball.Width;
+ }
+ else if (minDistance == distanceTop)
+ {
+ // Closest to the top edge
+ normal = Vector2.UnitY;
+ newPosition.Y = 0;
+ }
+ else
+ {
+ // Closest to the bottom edge
+ normal = -Vector2.UnitY;
+ newPosition.Y = screenBounds.Bottom - _ball.Height;
+ }
+
+ // Reflect the velocity about the normal
+ _ballVelocity = Vector2.Reflect(_ballVelocity, normal);
+}
+
+// Set the new position of the ball
+_ballVelocity = newPosition;
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/snippets/circle_equal_example.cs b/articles/tutorials/building_2d_games/12_collision_detection/snippets/circle_equal_example.cs
new file mode 100644
index 00000000..6c7f9790
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/snippets/circle_equal_example.cs
@@ -0,0 +1,3 @@
+Circle circle1 = new Circle(0, 0, 5);
+Circle circle2 = new Circle(0, 0, 5);
+bool areEqual = circle1 == circle2; // Returns true
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/snippets/cirlce.cs b/articles/tutorials/building_2d_games/12_collision_detection/snippets/cirlce.cs
new file mode 100644
index 00000000..d8121981
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/snippets/cirlce.cs
@@ -0,0 +1,161 @@
+#region declaration
+using System;
+using Microsoft.Xna.Framework;
+
+namespace MonoGameLibrary;
+
+public readonly struct Circle : IEquatable
+{
+
+}
+#endregion
+{
+ #region fields_static
+ private static readonly Circle s_empty = new Circle();
+ #endregion
+
+ #region fields
+ ///
+ /// The x-coordinate of the center of this circle.
+ ///
+ public readonly int X;
+
+ ///
+ /// The y-coordinate of the center of this circle.
+ ///
+ public readonly int Y;
+
+ ///
+ /// The length, in pixels, from the center of this circle to the edge.
+ ///
+ public readonly int Radius;
+ #endregion
+
+ #region properties_location
+ ///
+ /// Gets the location of the center of this circle.
+ ///
+ public readonly Point Location => new Point(X, Y);
+ #endregion
+
+ #region properties_empty
+ ///
+ /// Gets a circle with X=0, Y=0, and Radius=0.
+ ///
+ public static Circle Empty => s_empty;
+
+ ///
+ /// Gets a value that indicates whether this circle has a radius of 0 and a location of (0, 0).
+ ///
+ public readonly bool IsEmpty => X == 0 && Y == 0 && Radius == 0;
+ #endregion
+
+ #region properties_boundaries
+ ///
+ /// Gets the y-coordinate of the highest point on this circle.
+ ///
+ public readonly int Top => Y - Radius;
+
+ ///
+ /// Gets the y-coordinate of the lowest point on this circle.
+ ///
+ public readonly int Bottom => Y + Radius;
+
+ ///
+ /// Gets the x-coordinate of the leftmost point on this circle.
+ ///
+ public readonly int Left => X - Radius;
+
+ ///
+ /// Gets the x-coordinate of the rightmost point on this circle.
+ ///
+ public readonly int Right => X + Radius;
+ #endregion
+
+ #region ctors
+ ///
+ /// Creates a new circle with the specified position and radius.
+ ///
+ /// The x-coordinate of the center of the circle.
+ /// The y-coordinate of the center of the circle..
+ /// The length from the center of the circle to an edge.
+ public Circle(int x, int y, int radius)
+ {
+ X = x;
+ Y = y;
+ Radius = radius;
+ }
+
+ ///
+ /// Creates a new circle with the specified position and radius.
+ ///
+ /// The center of the circle.
+ /// The length from the center of the circle to an edge.
+ public Circle(Point location, int radius)
+ {
+ X = location.X;
+ Y = location.Y;
+ Radius = radius;
+ }
+ #endregion
+
+ #region methods_intersects
+ ///
+ /// Returns a value that indicates whether the specified circle intersects with this circle.
+ ///
+ /// The other circle to check.
+ /// true if the other circle intersects with this circle; otherwise, false.
+ public bool Intersects(Circle other)
+ {
+ int radiiSquared = (this.Radius + other.Radius) * (this.Radius + other.Radius);
+ float distanceSquared = Vector2.DistanceSquared(this.Location.ToVector2(), other.Location.ToVector2());
+ return distanceSquared < radiiSquared;
+ }
+ #endregion
+
+ #region methods_equals
+ ///
+ /// Returns a value that indicates whether this circle and the specified object are equal
+ ///
+ /// The object to compare with this circle.
+ /// true if this circle and the specified object are equal; otherwise, false.
+ public override readonly bool Equals(object obj) => obj is Circle other && Equals(other);
+
+ ///
+ /// Returns a value that indicates whether this circle and the specified circle are equal.
+ ///
+ /// The circle to compare with this circle.
+ /// true if this circle and the specified circle are equal; otherwise, false.
+ public readonly bool Equals(Circle other) => this.X == other.X &&
+ this.Y == other.Y &&
+ this.Radius == other.Radius;
+ #endregion
+
+ #region methods_hashcode
+ ///
+ /// Returns the hash code for this circle.
+ ///
+ /// The hash code for this circle as a 32-bit signed integer.
+ public override readonly int GetHashCode() => HashCode.Combine(X, Y, Radius);
+ #endregion
+
+ #region methods_operators
+ ///
+ /// Returns a value that indicates if the circle on the left hand side of the equality operator is equal to the
+ /// circle on the right hand side of the equality operator.
+ ///
+ /// The circle on the left hand side of the equality operator.
+ /// The circle on the right hand side of the equality operator.
+ /// true if the two circles are equal; otherwise, false.
+ public static bool operator ==(Circle lhs, Circle rhs) => lhs.Equals(rhs);
+
+ ///
+ /// Returns a value that indicates if the circle on the left hand side of the inequality operator is not equal to the
+ /// circle on the right hand side of the inequality operator.
+ ///
+ /// The circle on the left hand side of the inequality operator.
+ /// The circle on the right hand side fo the inequality operator.
+ /// true if the two circle are not equal; otherwise, false.
+ public static bool operator !=(Circle lhs, Circle rhs) => !lhs.Equals(rhs);
+ #endregion
+}
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/snippets/contains_example.cs b/articles/tutorials/building_2d_games/12_collision_detection/snippets/contains_example.cs
new file mode 100644
index 00000000..9bec3770
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/snippets/contains_example.cs
@@ -0,0 +1,31 @@
+// Store the current location
+Vector2 previousLocation = _spriteLocation;
+
+// Calculate a new location
+Vector2 newLocation = _spriteLocation + new Vector2(10, 0);
+
+// Create a bounding box for the sprite object
+Rectangle spriteBounds = new Rectangle(
+ (int)newLocation.X,
+ (int)newLocation.Y,
+ (int)_sprite.Width,
+ (int)_sprite.Height
+);
+
+// Get the bounds of the screen as a rectangle
+Rectangle screenBounds = new Rectangle(
+ 0,
+ 0,
+ GraphicsDevice.PresentationParameters.BackBufferWidth,
+ GraphicsDevice.PresentationParameters.BackBufferHeight
+);
+
+// Detect if the sprite is contained within the bounds of the screen
+if(!screenBounds.Contains(spriteBounds))
+{
+ // Respond by not allowing the sprite to move to move outside the screen
+ // bounds by setting the location back to the previous location.
+ newLocation = previousLocation;
+}
+
+_spriteLocation = newLocation;
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/snippets/game1.cs b/articles/tutorials/building_2d_games/12_collision_detection/snippets/game1.cs
new file mode 100644
index 00000000..7a85b431
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/snippets/game1.cs
@@ -0,0 +1,304 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary;
+using MonoGameLibrary.Graphics;
+using MonoGameLibrary.Input;
+
+namespace DungeonSlime;
+
+public class Game1 : Core
+{
+ // Defines the slime animated sprite.
+ private AnimatedSprite _slime;
+
+ // Defines the bat animated sprite.
+ private AnimatedSprite _bat;
+
+ // Tracks the position of the slime.
+ private Vector2 _slimePosition;
+
+ // Speed multiplier when moving.
+ private const float MOVEMENT_SPEED = 5.0f;
+
+ // Tracks the position of the bat.
+ private Vector2 _batPosition;
+
+ // Tracks the velocity of the bat.
+ private Vector2 _batVelocity;
+
+ public Game1() : base("Dungeon Slime", 1280, 720, false)
+ {
+
+ }
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+
+ // Set the initial position of the bat to be 10px
+ // to the right of the slime.
+ _batPosition = new Vector2(_slime.Width + 10, 0);
+
+ // Assign the initial random velocity to the bat.
+ AssignRandomBatVelocity();
+ }
+
+ protected override void LoadContent()
+ {
+ // Create the texture atlas from the XML configuration file
+ TextureAtlas atlas = TextureAtlas.FromFile(Content, "images/atlas-definition.xml");
+
+ // Create the slime animated sprite from the atlas.
+ _slime = atlas.CreateAnimatedSprite("slime-animation");
+ _slime.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+ _bat.Scale = new Vector2(4.0f, 4.0f);
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
+ Exit();
+
+ // Update the slime animated sprite.
+ _slime.Update(gameTime);
+
+ // Update the bat animated sprite.
+ _bat.Update(gameTime);
+
+ // Check for keyboard input and handle it.
+ CheckKeyboardInput();
+
+ // Check for gamepad input and handle it.
+ CheckGamePadInput();
+
+ // Create a bounding rectangle for the screen
+ Rectangle screenBounds = new Rectangle(
+ 0,
+ 0,
+ GraphicsDevice.PresentationParameters.BackBufferWidth,
+ GraphicsDevice.PresentationParameters.BackBufferHeight
+ );
+
+ // Creating a bounding circle for the slime
+ Circle slimeBounds = new Circle(
+ (int)(_slimePosition.X + (_slime.Width * 0.5f)),
+ (int)(_slimePosition.Y + (_slime.Height * 0.5f)),
+ (int)(_slime.Width * 0.5f)
+ );
+
+ // Use distance based checks to determine if the slime is within the
+ // bounds of the game screen, and if it is outside that screen edge,
+ // move it back inside.
+ if (slimeBounds.Left < screenBounds.Left)
+ {
+ _slimePosition.X = screenBounds.Left;
+ }
+ else if (slimeBounds.Right > screenBounds.Right)
+ {
+ _slimePosition.X = screenBounds.Right - _slime.Width;
+ }
+
+ if (slimeBounds.Top < screenBounds.Top)
+ {
+ _slimePosition.Y = screenBounds.Top;
+ }
+ else if (slimeBounds.Bottom > screenBounds.Bottom)
+ {
+ _slimePosition.Y = screenBounds.Bottom - _slime.Height;
+ }
+
+ // Calculate the new position of the bat based on the velocity
+ Vector2 newBatPosition = _batPosition + _batVelocity;
+
+ // Create a bounding circle for the bat
+ Circle batBounds = new Circle(
+ (int)(newBatPosition.X + (_bat.Width * 0.5f)),
+ (int)(newBatPosition.Y + (_bat.Height * 0.5f)),
+ (int)(_bat.Width * 0.5f)
+ );
+
+ Vector2 normal = Vector2.Zero;
+
+ // Use distance based checks to determine if the bat is within the
+ // bounds of the game screen, and if it is outside that screen edge,
+ // reflect it about the screen edge normal
+ if (batBounds.Left < screenBounds.Left)
+ {
+ normal.X = Vector2.UnitX.X;
+ newBatPosition.X = screenBounds.Left;
+ }
+ else if (batBounds.Right > screenBounds.Right)
+ {
+ normal.X = -Vector2.UnitX.X;
+ newBatPosition.X = screenBounds.Right - _bat.Width;
+ }
+
+ if (batBounds.Top < screenBounds.Top)
+ {
+ normal.Y = Vector2.UnitY.Y;
+ newBatPosition.Y = screenBounds.Top;
+ }
+ else if (batBounds.Bottom > screenBounds.Bottom)
+ {
+ normal.Y = -Vector2.UnitY.Y;
+ newBatPosition.Y = screenBounds.Bottom - _bat.Height;
+ }
+
+ // If the normal is anything but Vector2.Zero, this means the bat had
+ // moved outside the screen edge so we should reflect it about the
+ // normal.
+ if (normal != Vector2.Zero)
+ {
+ _batVelocity = Vector2.Reflect(_batVelocity, normal);
+ }
+
+ _batPosition = newBatPosition;
+
+ if (slimeBounds.Intersects(batBounds))
+ {
+ // Divide the width and height of the screen into equal columns and
+ // rows based on the width and height of the bat.
+ int totalColumns = GraphicsDevice.PresentationParameters.BackBufferWidth / (int)_bat.Width;
+ int totalRows = GraphicsDevice.PresentationParameters.BackBufferHeight / (int)_bat.Height;
+
+ // Choose a random row and column based on the total number of each
+ int column = Random.Shared.Next(0, totalColumns);
+ int row = Random.Shared.Next(0, totalRows);
+
+ // Change the bat position by setting the x and y values equal to
+ // the column and row multiplied by the width and height.
+ _batPosition = new Vector2(column * _bat.Width, row * _bat.Height);
+
+ // Assign a new random velocity to the bat
+ AssignRandomBatVelocity();
+ }
+
+ base.Update(gameTime);
+ }
+
+ private void AssignRandomBatVelocity()
+ {
+ // Generate a random angle
+ float angle = (float)(Random.Shared.NextDouble() * Math.PI * 2);
+
+ // Convert angle to a direction vector
+ float x = (float)Math.Cos(angle);
+ float y = (float)Math.Sin(angle);
+ Vector2 direction = new Vector2(x, y);
+
+ // Multiply the direction vector by the movement speed
+ _batVelocity = direction * MOVEMENT_SPEED;
+ }
+
+ private void CheckKeyboardInput()
+ {
+ // If the space key is held down, the movement speed increases by 1.5
+ float speed = MOVEMENT_SPEED;
+ if (Input.Keyboard.IsKeyDown(Keys.Space))
+ {
+ speed *= 1.5f;
+ }
+
+ // If the W or Up keys are down, move the slime up on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.W) || Input.Keyboard.IsKeyDown(Keys.Up))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // if the S or Down keys are down, move the slime down on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.S) || Input.Keyboard.IsKeyDown(Keys.Down))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If the A or Left keys are down, move the slime left on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.A) || Input.Keyboard.IsKeyDown(Keys.Left))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If the D or Right keys are down, move the slime right on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.D) || Input.Keyboard.IsKeyDown(Keys.Right))
+ {
+ _slimePosition.X += speed;
+ }
+ }
+
+ private void CheckGamePadInput()
+ {
+ GamePadInfo gamePadOne = Input.GamePads[(int)PlayerIndex.One];
+
+ // If the A button is held down, the movement speed increases by 1.5
+ // and the gamepad vibrates as feedback to the player.
+ float speed = MOVEMENT_SPEED;
+ if (gamePadOne.IsButtonDown(Buttons.A))
+ {
+ speed *= 1.5f;
+ GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f);
+ }
+ else
+ {
+ GamePad.SetVibration(PlayerIndex.One, 0.0f, 0.0f);
+ }
+
+ // Check thumbstick first since it has priority over which gamepad input
+ // is movement. It has priority since the thumbstick values provide a
+ // more granular analog value that can be used for movement.
+ if (gamePadOne.LeftThumbStick != Vector2.Zero)
+ {
+ _slimePosition.X += gamePadOne.LeftThumbStick.X * speed;
+ _slimePosition.Y -= gamePadOne.LeftThumbStick.Y * speed;
+ }
+ else
+ {
+ // If DPadUp is down, move the slime up on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadUp))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // If DPadDown is down, move the slime down on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadDown))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If DPapLeft is down, move the slime left on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadLeft))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If DPadRight is down, move the slime right on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadRight))
+ {
+ _slimePosition.X += speed;
+ }
+ }
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
+
+ // Draw the slime sprite.
+ _slime.Draw(SpriteBatch, _slimePosition);
+
+ // Draw the bat sprite.
+ _bat.Draw(SpriteBatch, _batPosition);
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/snippets/rectangle_intersects.cs b/articles/tutorials/building_2d_games/12_collision_detection/snippets/rectangle_intersects.cs
new file mode 100644
index 00000000..421d44ae
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/snippets/rectangle_intersects.cs
@@ -0,0 +1,29 @@
+// Rectangle 1
+// Top: 0
+// ----------------
+// | |
+// | |
+// Left: 0 | | Right: 32
+// | |
+// | |
+// ----------------
+// Bottom: 32
+Rectangle rect1 = new Rectangle(0, 0, 32, 32);
+
+// Rectangle 2
+// Top: 16
+// ----------------
+// | |
+// | |
+// Left: 16 | | Right: 48
+// | |
+// | |
+// ----------------
+// Bottom: 48
+Rectangle rect2 = new Rectangle (16, 16, 32, 32);
+
+// rect1.Left (0) < rect2.Right (48) = true
+// rect1.Right (32) > rect3.Left (16) = true
+// rect1.Top (0) < rect2.Bottom (48) = true
+// rect1.Bottom (32) > rect2.Top (16) = true
+bool isColliding = rect1.Intersects(rect2); // returns true
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/snippets/trigger_example.cs b/articles/tutorials/building_2d_games/12_collision_detection/snippets/trigger_example.cs
new file mode 100644
index 00000000..0d65cb6e
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/snippets/trigger_example.cs
@@ -0,0 +1,14 @@
+// Create a bounding box for the sprite object
+Rectangle spriteBounds = new Rectangle(
+ (int)_spriteLocation.X,
+ (int)_spriteLocation.Y,
+ (int)_sprite.Width,
+ (int)_sprite.Height
+);
+
+// Detect if the sprite object is within the trigger zone
+if(_spriteBounds.Intersects(_triggerBounds))
+{
+ // Perform some event
+ CollectItem();
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/snippets/vector2_distance.cs b/articles/tutorials/building_2d_games/12_collision_detection/snippets/vector2_distance.cs
new file mode 100644
index 00000000..69f87b1b
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/snippets/vector2_distance.cs
@@ -0,0 +1,22 @@
+Vector2 circle1Position = new Vector2(8, 10);
+Vector2 circle2Position = new Vector2(5, 6);
+
+float circle1Radius = 5;
+float circle2Radius = 5;
+
+// c^2 = (8 - 5)^2 + (10 - 6)^2
+// c^2 = 3^2 + 4^2
+// c^2 = 9 + 16
+// c^2 = 25
+float distanceSquared = Vector2.DistanceSquared(circle1Position, circle2Position);
+
+// r^2 = (5 + 5)^2
+// r^2 = (10)^2
+// r^2 = 100
+int radiiSquared = (circle1Radius + circle2Radius) * (circle1Radius + circle2Radius)
+
+// They do not overlap since 100 is not less than 25
+if(radii < distanceSquared)
+{
+
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/12_collision_detection/videos/gameplay.webm b/articles/tutorials/building_2d_games/12_collision_detection/videos/gameplay.webm
new file mode 100644
index 00000000..126a7196
Binary files /dev/null and b/articles/tutorials/building_2d_games/12_collision_detection/videos/gameplay.webm differ
diff --git a/articles/tutorials/building_2d_games/13_working_with_tilemaps/images/atlas.png b/articles/tutorials/building_2d_games/13_working_with_tilemaps/images/atlas.png
new file mode 100644
index 00000000..ce804206
Binary files /dev/null and b/articles/tutorials/building_2d_games/13_working_with_tilemaps/images/atlas.png differ
diff --git a/articles/tutorials/building_2d_games/13_working_with_tilemaps/images/mgcb-editor.png b/articles/tutorials/building_2d_games/13_working_with_tilemaps/images/mgcb-editor.png
new file mode 100644
index 00000000..f3182e7c
Binary files /dev/null and b/articles/tutorials/building_2d_games/13_working_with_tilemaps/images/mgcb-editor.png differ
diff --git a/articles/tutorials/building_2d_games/13_working_with_tilemaps/images/tileset-grid-comparison.png b/articles/tutorials/building_2d_games/13_working_with_tilemaps/images/tileset-grid-comparison.png
new file mode 100644
index 00000000..0ea32089
Binary files /dev/null and b/articles/tutorials/building_2d_games/13_working_with_tilemaps/images/tileset-grid-comparison.png differ
diff --git a/articles/tutorials/building_2d_games/13_working_with_tilemaps/images/tileset-to-tilemap-example.png b/articles/tutorials/building_2d_games/13_working_with_tilemaps/images/tileset-to-tilemap-example.png
new file mode 100644
index 00000000..a489824f
Binary files /dev/null and b/articles/tutorials/building_2d_games/13_working_with_tilemaps/images/tileset-to-tilemap-example.png differ
diff --git a/articles/tutorials/building_2d_games/13_working_with_tilemaps/index.md b/articles/tutorials/building_2d_games/13_working_with_tilemaps/index.md
new file mode 100644
index 00000000..29168949
--- /dev/null
+++ b/articles/tutorials/building_2d_games/13_working_with_tilemaps/index.md
@@ -0,0 +1,260 @@
+---
+title: "Chapter 13: Working with Tilemaps"
+description: "Learn how to implement tile-based game environments using tilemaps and tilesets, including creating reusable classes for managing tiles and loading level designs from XML configuration files."
+---
+
+In the previous chapters, you have learned how to draw individual sprites and animated sprites from a texture atlas and handle collision detection. However, the game so far is lacking an actual world or environment to exist in; it is just sprites on a cornflower blue background. Most 2D games feature game worlds built from many tiles arranged in a grid-like patten. These *tilemaps* allow you to efficiently create large game environments without managing thousands of individual sprites.
+
+In this chapter you will:
+
+- Learn what tilemaps are and how they are used in game development.
+- Create a `Tileset` class to manage collections of related tiles.
+- Build a `Tilemap` class to render tile-based game worlds.
+- Implement an XML-based tilemap loading system.
+- Update our game to use tilemaps for the game environment.
+
+## Understanding Tilemaps
+
+Tilemaps are a common technique used in 2D game development to create game worlds. Instead of positioning individual sprites for each element in the game world, a tilemap divides the world into a grid and places tiles from a *tileset* at each grid position.
+
+### What is a Tileset?
+
+A tileset is a collection of small images (tiles) that can be combined and arranged to create game environments. Typically these are stored in a single texture atlas, similar to how we have been handing sprites and animations. Common examples of tiles might include:
+
+- Floor and ground tiles.
+- Walls and obstacle tiles.
+- Decorative elements like plants and furniture.
+- Special tiles like doors, ladders, or water.
+
+Each tile in a tileset is assigned an ID number, which the tilemap uses to reference which tile goes where. For example, in *Figure 13-1* below, the tileset we will add to our game in a moment is shown on the left and on the right is the same tileset with an overlay showing how each tile is assigned an ID number.
+
+|  |
+|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
+| **Figure 13-1: Left: Original dungeon tileset. Right: The same tileset with an overlay showing how each tile is assigned a numeric ID** |
+
+### What is a Tilemap?
+
+A tilemap is a grid-based data structure that defines while tiles from a tileset appear at each position in the game world. The tilemap stores an ID for each cell in the grid, where the ID corresponds to a specific tile in the tileset.
+
+For example, a simple tilemap may look like this conceptually:
+
+```text
+00 01 02 01 03
+04 05 06 05 07
+08 09 10 09 11
+04 09 09 09 07
+12 13 14 13 15
+```
+
+If we took the above tilemap data and mapped each cell to the tile in the related tileset, it would look something similar to *Figure 13-2* below:
+
+|  |
+|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
+| **Figure 13-2: From tileset to tilemap. Left: Tileset with an overlay showing the tile IDs. Right: The tilemap created using the tiles arranged with the pattern from the code example above** |
+
+This approach offers several advantage:
+
+1. **Memory efficiency**: Instead of storing complete information about each tile's appearance, you only need to store a reference ID.
+2. **Performance**: Drawing a tilemap can be optimized to reduce texture swapping compared to rendering many individual sprites.
+3. **Design flexibility**: Tilemaps make it easy to create, modify, and load level designs from external files.
+
+We will now take this concept and implement it in our game by creating a `Tileset` class and a `Tilemap` class.
+
+## The Tileset Class
+
+The `Tileset` class will manage a collection of tiles from a texture atlas. Each tile will be represented as a `TextureRegion`, building on the tools in the library we created earlier.
+
+In the `Graphics` folder of the *MonoGameLibrary* project, create a new file named `Tileset.cs` with the following code as the initial structure:
+
+[!code-csharp[](./snippets/tileset.cs#declaration)]
+
+### Tileset Properties and Fields
+
+The `Tileset` class needs to store a `TextureRegion` for each of the individual tiles in the tile set and provide the dimensions (with and height) of the tiles. It should also offers additional properties that provide the total number of rows and columns in the tileset and the total number of tiles. Add the following fields and properties:
+
+[!code-csharp[](./snippets/tileset.cs#properties)]
+
+### Tileset Constructor
+
+The `Tileset` class constructor requires a source `TextureRegion` that represents the tileset and the width and height of the tiles. Based on these parameters provided, it can automatically divide the source `TextureRegion` into a grid of smaller texture regions and calculate the total number of rows, columns, and tiles.
+
+Add the following constructor:
+
+[!code-csharp[](./snippets/tileset.cs#ctors)]
+
+### Tileset Methods
+
+The `Tileset` class needs to provide methods to retrieve the `TextureRegion` of a tile based on the index (tile ID) or by the location (row and column) of the tile in the tileset. Add the following methods:
+
+[!code-csharp[](./snippets/tileset.cs#methods)]
+
+## The Tilemap Class
+
+Now that we have a `Tileset` class to define our tile collection, we need a `Tilemap` class to arrange these tiles into a game level. The `Tilemap` class will store which tile goes where in our game world and provide methods to draw the entire map.
+
+In the `Graphics` folder of the *MonoGameLibrary* project, create a new file named `Tilemap.cs` with the following code as the initial structure:
+
+[!code-csharp[](./snippets/tilemap.cs#declaration)]
+
+### Tilemap Properties and Fields
+
+The `Tilemap` class needs to store a reference to the tileset being used, along with an array of the tile IDs representing each tile in the map. It should also offer additional properties that provide the total number of rows and columns are in the tilemap and the total number of tiles. Add the following fields and properties:
+
+[!code-csharp[](./snippets/tilemap.cs#properties)]
+
+### Tilemap Constructor
+
+The `Tilemap` constructor requires the `Tilemap` to reference for each tile, the total number of columns and rows in the map, and the size (width and height) of each tile.
+
+Add the following constructor:
+
+[!code-csharp[](./snippets/tilemap.cs#ctors)]
+
+### Tilemap Tile Management Methods
+
+The `Tilemap` class should provide methods to set and retrieve tiles, either by index or location (rows and column). Add the following methods:
+
+[!code-csharp[](./snippets/tilemap.cs#tile-management)]
+
+### Tilemap Draw Method
+
+The `Tilemap` class needs a method to draw the tilemap by iterating through each of the tiles and drawing the `TextureRegion` for that tile at its correct position. Add the following method:
+
+[!code-csharp[](./snippets/tilemap.cs#draw)]
+
+### Tilemap FromFile Method
+
+The `Tilemap` class also requires a method to load and create an instance of the tilemap from an external configuration file. This allows us to separate level design from code. Add the following method:
+
+[!code-csharp[](./snippets/tilemap.cs#from-file)]
+
+## Updating the Game
+
+Now that we have the `Tilemap` and `Tileset` classes defined, we can update our game to use them. We will need to
+
+1. Update the texture atlas to include the tileset.
+2. Create a tilemap xml configuration file.
+3. Update the game to load the tilemap from the configuration file and draw it.
+
+### Update the Texture Atlas
+
+Currently, the texture atlas we have been using only contains the sprites for the slime and bat animations. We need update it to a new version that contains the tileset as well.
+
+Right-click the following image and save it as `atlas.png` in the `Content/images` directory of the *DungeonSlime* project (your main game project), overwriting the existing one.
+
+> [!NOTE]
+> You do not need to do this in the MGCB editor as you are simply replacing the file and not altering any of its import properties.
+
+|  |
+|:-----------------------------------------------------------------------------------------------------------------:|
+| **Figure 13-3: The texture atlas for our game updated to include the tileset for the tilemap** |
+
+> [!NOTE]
+> Since the slime and bat sprites are in the same position in the new texture atlas, we do not need to update the atlas XML configuration file.
+
+## Creating a Tilemap XML Configuration
+
+Now that we have the texture atlas updated to include the tileset, we need to create a tilemap configuration that our game can load. The configuration will be an XML file that specifies the tileset to use and the arrangement of tiles in the tilemap.
+
+We need to add this configuration file to our content project in the *Content/images* folder with the MGCB Editor, in the same way we did with the "atlas-definition.xml":
+
+1. Open the *Content.mgcb* content project file in the MGCB Editor.
+2. Right-click the *images* folder and choose *Add > New Item...*.
+3. Select the *Xml Content (.xml)* type and name it `tilemap-definition`
+4. Select the `tilemap-definition.xml` file you just created.
+5. In the Properties panel, change the *Build Action* property from *Build* to *Copy*.
+6. Save the changes in the MGCB Editor.
+
+|  |
+|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
+| **Figure 13-4: The Content project in the MGCB Editor with the tilemap-definition.xml file added and the Build Action property set to copy** |
+
+7. Open the `tilemap-definition.xml` file in your code editor and replace its contents with the following and save it:
+
+[!code-xml[](./snippets/tilemap-definition.xml)]
+
+This tilemap configuration creates a simple dungeon layout with walls around the perimeter and an open floor in the middle. The tile IDs correspond to specific tiles in the tileset:
+
+- `00`, `03`, `12`, `15`: Corner wall tiles (top-left, top-right, bottom-left, bottom-right).
+- `01`, `02`, `13`, `14`: Horizontal wall tiles (top and bottom walls).
+- `04`, `07`, `08`, `11`: Vertical wall tiles (left and right walls).
+- `05` and `06`: Top floor edge tiles.
+- `09`: Standard floor tile.
+- `10`: Decorated floor tile with a crack in it.
+
+### Update the Game1 Class
+
+With all of the assets now in place and configured, we can update the `Game1` class to load the tilemap and draw it. We will also need to update the collision logic so that the boundary is no longer the edge of the screen, but instead the edges of the wall tiles of the tilemap. Open `Game1.cs` and make the following updates:
+
+[!code-csharp[](./snippets/game1.cs?highlight=31-35,46-61,80-82,112,114,116,128,121,123,125,127,145,148,150,153,156,159,161,164,179-181,303-304)]
+
+The key changes to the `Game1` class include:
+
+1. The `_tilemap` field was added to hold the loaded tilemap.
+2. The `_roombounds` [**Rectangle**](xref:Microsoft.Xna.Framework.Rectangle) was added to define the playable area within the tilemap to keep the slime and bat inside the walls.
+3. In [**Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize):
+ 1. The `_roomBounds` is set based on the tilemap's tile size.
+ 2. The starting position of the slime is now set to be in the center of the room.
+4. In [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent):
+ 1. The tilemap is loaded from the XML configuration file.
+ 2. The scale of the tilemap is set to a factor of 4.0.
+5. In [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)), the `screenBounds` variable was removed and the collision logic has been updated to instead use the `_roomBounds` instead.
+6. In [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) the tilemap is drawn.
+
+Running the game now with these changes, our game now visually transforms from a simple screen with sprites to a proper game environment with walls and floors. The slime and bat are now confined within the walls of the dungeon defined by our tilemap.
+
+|  |
+|:-----------------------------------------------------------------------------------------------------------------------------------:|
+| **Figure 13-5: Gameplay with the tilemap rendered and the bat and slime contained within the dungeon walls** |
+
+## Additional Notes
+
+While the method provided in this chapter offers a straightforward approach to loading tilemaps from external configuration files, several dedicated tools exist specifically for creating tilemaps for games. Popular options include [Tiled](https://www.mapeditor.org/), [LDtk](https://ldtk.io/), and [Ogmo](https://ogmo-editor-3.github.io/). These specialized tools export map configurations in various formats such as XML (similar to what we implemented) or JSON, and often include additional features like multiple layers, object placement, and custom properties for tiles.
+
+> [!NOTE]
+> In order to utilize Tilemaps from other tools, you will need a way to import those maps as MonoGame does not natively support them, one option is to use [MonoGame.Extended](https://www.monogameextended.net/) which provides Tilemap importers for Tiled (and more in the future) as well as a host of other features.
+
+Although these tools are more robust than our implementation, the underlying concept remains the same: a tilemap is fundamentally a grid layout where each cell references a tile ID from a tileset. The principles you have learned in this chapter form the foundation for working with any tilemap system, regardless of which tool you might use.
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+- Learned about tilemaps and how they are used in 2D game development.
+- Created a `Tileset` class to manage collections of tiles from a texture atlas.
+- Implemented a `Tilemap` class to render grid-based game environments.
+- Created an XML-based tilemap definition system for storing level layouts.
+- Updated our game to use tilemaps for the game environment.
+
+In the next chapter, we will start exploring audio to add sound effects when a collision occurs and background music to our game.
+
+## Test Your Knowledge
+
+1. What is the main advantage of using tilemaps for game environments rather than individual sprites?
+
+ :::question-answer
+ Tilemaps offer several advantages: memory efficiency (reusing tiles instead of storing complete environments), performance optimization (batched rendering), and design flexibility (easier to create and modify levels). They allow creating large game worlds by reusing a small set of tiles in different arrangements.
+ :::
+
+2. What is the relationship between a tileset and a tilemap?
+
+ :::question-answer
+ A tileset is a collection of individual tiles stored in a texture atlas, where each tile has a unique ID. A tilemap is a grid-based structure that references tiles from the tileset by their IDs to create a complete game environment. The tileset provides the visual elements, while the tilemap defines their arrangement.
+ :::
+
+3. Why might you use an XML definition for a tilemap instead of hardcoding the tile layout?
+
+ :::question-answer
+ Using XML definitions for tilemaps separates level design from game code, offering several benefits: easier level editing (without changing code), support for multiple levels, ability to create external level editors, and better organization of game content. It also allows non-programmers like game designers to create and modify levels.
+ :::
+
+4. In our implementation, how does the Tilemap's Draw method work?
+
+ :::question-answer
+ The Tilemap's Draw method iterates through each position in the grid. For each position, it:
+
+ 1. Retrieves the tile ID stored at that position.
+ 2. Gets the corresponding texture region from the tileset.
+ 3. Calculates the screen position based on the grid coordinates and tile size.
+ 4. Draws the texture region at that position using the sprite batch.
+ :::
diff --git a/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/atlas-definition.xml b/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/atlas-definition.xml
new file mode 100644
index 00000000..5532e660
--- /dev/null
+++ b/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/atlas-definition.xml
@@ -0,0 +1,23 @@
+
+
+ images/atlas
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/game1.cs b/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/game1.cs
new file mode 100644
index 00000000..adbd0a34
--- /dev/null
+++ b/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/game1.cs
@@ -0,0 +1,317 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary;
+using MonoGameLibrary.Graphics;
+using MonoGameLibrary.Input;
+
+namespace DungeonSlime;
+
+public class Game1 : Core
+{
+ // Defines the slime animated sprite.
+ private AnimatedSprite _slime;
+
+ // Defines the bat animated sprite.
+ private AnimatedSprite _bat;
+
+ // Tracks the position of the slime.
+ private Vector2 _slimePosition;
+
+ // Speed multiplier when moving.
+ private const float MOVEMENT_SPEED = 5.0f;
+
+ // Tracks the position of the bat.
+ private Vector2 _batPosition;
+
+ // Tracks the velocity of the bat.
+ private Vector2 _batVelocity;
+
+ // Defines the tilemap to draw.
+ private Tilemap _tilemap;
+
+ // Defines the bounds of the room that the slime and bat are contained within.
+ private Rectangle _roomBounds;
+
+ public Game1() : base("Dungeon Slime", 1280, 720, false)
+ {
+
+ }
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+
+ Rectangle screenBounds = GraphicsDevice.PresentationParameters.Bounds;
+
+ _roomBounds = new Rectangle(
+ (int)_tilemap.TileWidth,
+ (int)_tilemap.TileHeight,
+ screenBounds.Width - (int)_tilemap.TileWidth * 2,
+ screenBounds.Height - (int)_tilemap.TileHeight * 2
+ );
+
+ // Initial slime position will be the center tile of the tile map.
+ int centerRow = _tilemap.Rows / 2;
+ int centerColumn = _tilemap.Columns / 2;
+ _slimePosition = new Vector2(centerColumn * _tilemap.TileWidth, centerRow * _tilemap.TileHeight);
+
+ // Initial bat position will the in the top left corner of the room
+ _batPosition = new Vector2(_roomBounds.Left, _roomBounds.Top);
+
+ // Assign the initial random velocity to the bat.
+ AssignRandomBatVelocity();
+ }
+
+ protected override void LoadContent()
+ {
+ // Create the texture atlas from the XML configuration file
+ TextureAtlas atlas = TextureAtlas.FromFile(Content, "images/atlas-definition.xml");
+
+ // Create the slime animated sprite from the atlas.
+ _slime = atlas.CreateAnimatedSprite("slime-animation");
+ _slime.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+ _bat.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the tilemap from the XML configuration file.
+ _tilemap = Tilemap.FromFile(Content, "images/tilemap-definition.xml");
+ _tilemap.Scale = new Vector2(4.0f, 4.0f);
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
+ Exit();
+
+ // Update the slime animated sprite.
+ _slime.Update(gameTime);
+
+ // Update the bat animated sprite.
+ _bat.Update(gameTime);
+
+ // Check for keyboard input and handle it.
+ CheckKeyboardInput();
+
+ // Check for gamepad input and handle it.
+ CheckGamePadInput();
+
+ // Creating a bounding circle for the slime
+ Circle slimeBounds = new Circle(
+ (int)(_slimePosition.X + (_slime.Width * 0.5f)),
+ (int)(_slimePosition.Y + (_slime.Height * 0.5f)),
+ (int)(_slime.Width * 0.5f)
+ );
+
+ // Use distance based checks to determine if the slime is within the
+ // bounds of the game screen, and if it is outside that screen edge,
+ // move it back inside.
+ if (slimeBounds.Left < _roomBounds.Left)
+ {
+ _slimePosition.X = _roomBounds.Left;
+ }
+ else if (slimeBounds.Right > _roomBounds.Right)
+ {
+ _slimePosition.X = _roomBounds.Right - _slime.Width;
+ }
+
+ if (slimeBounds.Top < _roomBounds.Top)
+ {
+ _slimePosition.Y = _roomBounds.Top;
+ }
+ else if (slimeBounds.Bottom > _roomBounds.Bottom)
+ {
+ _slimePosition.Y = _roomBounds.Bottom - _slime.Height;
+ }
+
+ // Calculate the new position of the bat based on the velocity
+ Vector2 newBatPosition = _batPosition + _batVelocity;
+
+ // Create a bounding circle for the bat
+ Circle batBounds = new Circle(
+ (int)(newBatPosition.X + (_bat.Width * 0.5f)),
+ (int)(newBatPosition.Y + (_bat.Height * 0.5f)),
+ (int)(_bat.Width * 0.5f)
+ );
+
+ Vector2 normal = Vector2.Zero;
+
+ // Use distance based checks to determine if the bat is within the
+ // bounds of the game screen, and if it is outside that screen edge,
+ // reflect it about the screen edge normal
+ if (batBounds.Left < _roomBounds.Left)
+ {
+ normal.X = Vector2.UnitX.X;
+ newBatPosition.X = _roomBounds.Left;
+ }
+ else if (batBounds.Right > _roomBounds.Right)
+ {
+ normal.X = -Vector2.UnitX.X;
+ newBatPosition.X = _roomBounds.Right - _bat.Width;
+ }
+
+ if (batBounds.Top < _roomBounds.Top)
+ {
+ normal.Y = Vector2.UnitY.Y;
+ newBatPosition.Y = _roomBounds.Top;
+ }
+ else if (batBounds.Bottom > _roomBounds.Bottom)
+ {
+ normal.Y = -Vector2.UnitY.Y;
+ newBatPosition.Y = _roomBounds.Bottom - _bat.Height;
+ }
+
+ // If the normal is anything but Vector2.Zero, this means the bat had
+ // moved outside the screen edge so we should reflect it about the
+ // normal.
+ if (normal != Vector2.Zero)
+ {
+ _batVelocity = Vector2.Reflect(_batVelocity, normal);
+ }
+
+ _batPosition = newBatPosition;
+
+ if (slimeBounds.Intersects(batBounds))
+ {
+ // Choose a random row and column based on the total number of each
+ int column = Random.Shared.Next(1, _tilemap.Columns - 1);
+ int row = Random.Shared.Next(1, _tilemap.Rows - 1);
+
+ // Change the bat position by setting the x and y values equal to
+ // the column and row multiplied by the width and height.
+ _batPosition = new Vector2(column * _bat.Width, row * _bat.Height);
+
+ // Assign a new random velocity to the bat
+ AssignRandomBatVelocity();
+ }
+
+ base.Update(gameTime);
+ }
+
+ private void AssignRandomBatVelocity()
+ {
+ // Generate a random angle
+ float angle = (float)(Random.Shared.NextDouble() * Math.PI * 2);
+
+ // Convert angle to a direction vector
+ float x = (float)Math.Cos(angle);
+ float y = (float)Math.Sin(angle);
+ Vector2 direction = new Vector2(x, y);
+
+ // Multiply the direction vector by the movement speed
+ _batVelocity = direction * MOVEMENT_SPEED;
+ }
+
+ private void CheckKeyboardInput()
+ {
+ // If the space key is held down, the movement speed increases by 1.5
+ float speed = MOVEMENT_SPEED;
+ if (Input.Keyboard.IsKeyDown(Keys.Space))
+ {
+ speed *= 1.5f;
+ }
+
+ // If the W or Up keys are down, move the slime up on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.W) || Input.Keyboard.IsKeyDown(Keys.Up))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // if the S or Down keys are down, move the slime down on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.S) || Input.Keyboard.IsKeyDown(Keys.Down))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If the A or Left keys are down, move the slime left on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.A) || Input.Keyboard.IsKeyDown(Keys.Left))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If the D or Right keys are down, move the slime right on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.D) || Input.Keyboard.IsKeyDown(Keys.Right))
+ {
+ _slimePosition.X += speed;
+ }
+ }
+
+ private void CheckGamePadInput()
+ {
+ GamePadInfo gamePadOne = Input.GamePads[(int)PlayerIndex.One];
+
+ // If the A button is held down, the movement speed increases by 1.5
+ // and the gamepad vibrates as feedback to the player.
+ float speed = MOVEMENT_SPEED;
+ if (gamePadOne.IsButtonDown(Buttons.A))
+ {
+ speed *= 1.5f;
+ GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f);
+ }
+ else
+ {
+ GamePad.SetVibration(PlayerIndex.One, 0.0f, 0.0f);
+ }
+
+ // Check thumbstick first since it has priority over which gamepad input
+ // is movement. It has priority since the thumbstick values provide a
+ // more granular analog value that can be used for movement.
+ if (gamePadOne.LeftThumbStick != Vector2.Zero)
+ {
+ _slimePosition.X += gamePadOne.LeftThumbStick.X * speed;
+ _slimePosition.Y -= gamePadOne.LeftThumbStick.Y * speed;
+ }
+ else
+ {
+ // If DPadUp is down, move the slime up on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadUp))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // If DPadDown is down, move the slime down on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadDown))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If DPapLeft is down, move the slime left on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadLeft))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If DPadRight is down, move the slime right on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadRight))
+ {
+ _slimePosition.X += speed;
+ }
+ }
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
+
+ // Draw the tilemap.
+ _tilemap.Draw(SpriteBatch);
+
+ // Draw the slime sprite.
+ _slime.Draw(SpriteBatch, _slimePosition);
+
+ // Draw the bat sprite.
+ _bat.Draw(SpriteBatch, _batPosition);
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/tilemap-definition.xml b/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/tilemap-definition.xml
new file mode 100644
index 00000000..f91b6b62
--- /dev/null
+++ b/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/tilemap-definition.xml
@@ -0,0 +1,15 @@
+
+
+ images/atlas
+
+ 00 01 02 01 02 01 02 01 02 01 02 01 02 01 02 03
+ 04 05 05 06 05 05 06 05 05 06 05 05 06 05 05 07
+ 08 09 09 09 09 09 09 09 09 09 09 09 09 09 09 11
+ 04 09 09 09 09 09 09 09 10 09 09 09 09 10 09 07
+ 08 09 10 09 09 09 09 09 09 09 09 09 09 09 09 11
+ 04 09 09 09 09 09 09 09 09 09 09 09 09 09 09 07
+ 08 10 09 09 09 09 09 09 09 09 10 09 09 09 09 11
+ 04 09 09 09 09 09 10 09 09 09 09 09 09 09 09 07
+ 12 13 14 13 14 13 14 13 14 13 14 13 14 13 14 15
+
+
diff --git a/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/tilemap.cs b/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/tilemap.cs
new file mode 100644
index 00000000..4b9b7fcb
--- /dev/null
+++ b/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/tilemap.cs
@@ -0,0 +1,246 @@
+#region declaration
+using System;
+using System.IO;
+using System.Xml;
+using System.Xml.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace MonoGameLibrary.Graphics;
+
+public class Tilemap
+{
+
+}
+#endregion
+{
+ #region properties
+ private readonly Tileset _tileset;
+ private readonly int[] _tiles;
+
+ ///
+ /// Gets the total number of rows in this tilemap.
+ ///
+ public int Rows { get; }
+
+ ///
+ /// Gets the total number of columns in this tilemap.
+ ///
+ public int Columns { get; }
+
+ ///
+ /// Gets the total number of tiles in this tilemap.
+ ///
+ public int Count { get; }
+
+ ///
+ /// Gets or Sets the scale factor to draw each tile at.
+ ///
+ public Vector2 Scale { get; set; }
+
+ ///
+ /// Gets the width, in pixels, each tile is drawn at.
+ ///
+ public float TileWidth => _tileset.TileWidth * Scale.X;
+
+ ///
+ /// Gets the height, in pixels, each tile is drawn at.
+ ///
+ public float TileHeight => _tileset.TileHeight * Scale.Y;
+ #endregion
+
+ #region ctors
+ ///
+ /// Creates a new tilemap.
+ ///
+ /// The tileset used by this tilemap.
+ /// The total number of columns in this tilemap.
+ /// The total number of rows in this tilemap.
+ public Tilemap(Tileset tileset, int columns, int rows)
+ {
+ _tileset = tileset;
+ Rows = rows;
+ Columns = columns;
+ Count = Columns * Rows;
+ Scale = Vector2.One;
+ _tiles = new int[Count];
+ }
+ #endregion
+
+ #region tile-management
+ ///
+ /// Sets the tile at the given index in this tilemap to use the tile from
+ /// the tileset at the specified tileset id.
+ ///
+ /// The index of the tile in this tilemap.
+ /// The tileset id of the tile from the tileset to use.
+ public void SetTile(int index, int tilesetID)
+ {
+ _tiles[index] = tilesetID;
+ }
+
+ ///
+ /// Sets the tile at the given column and row in this tilemap to use the tile
+ /// from the tileset at the specified tileset id.
+ ///
+ /// The column of the tile in this tilemap.
+ /// The row of the tile in this tilemap.
+ /// The tileset id of the tile from the tileset to use.
+ public void SetTile(int column, int row, int tilesetID)
+ {
+ int index = row * Columns + column;
+ SetTile(index, tilesetID);
+ }
+
+ ///
+ /// Gets the texture region of the tile from this tilemap at the specified index.
+ ///
+ /// The index of the tile in this tilemap.
+ /// The texture region of the tile from this tilemap at the specified index.
+ public TextureRegion GetTile(int index)
+ {
+ return _tileset.GetTile(_tiles[index]);
+ }
+
+ ///
+ /// Gets the texture region of the tile frm this tilemap at the specified
+ /// column and row.
+ ///
+ /// The column of the tile in this tilemap.
+ /// The row of hte tile in this tilemap.
+ /// The texture region of the tile from this tilemap at the specified column and row.
+ public TextureRegion GetTile(int column, int row)
+ {
+ int index = row * Columns + column;
+ return GetTile(index);
+ }
+ #endregion
+
+ #region draw
+ ///
+ /// Draws this tilemap using the given sprite batch.
+ ///
+ /// The sprite batch used to draw this tilemap.
+ public void Draw(SpriteBatch spriteBatch)
+ {
+ for (int i = 0; i < Count; i++)
+ {
+ int tileSetIndex = _tiles[i];
+ TextureRegion tile = _tileset.GetTile(tileSetIndex);
+
+ int x = i % Columns;
+ int y = i / Columns;
+
+ Vector2 position = new Vector2(x * TileWidth, y * TileHeight);
+ tile.Draw(spriteBatch, position, Color.White, 0.0f, Vector2.Zero, Scale, SpriteEffects.None, 1.0f);
+ }
+ }
+ #endregion
+
+ #region from-file
+ ///
+ /// Creates a new tilemap based on a tilemap xml configuration file.
+ ///
+ /// The content manager used to load the texture for the tileset.
+ /// The path to the xml file, relative to the content root directory.
+ /// The tilemap created by this method.
+ public static Tilemap FromFile(ContentManager content, string filename)
+ {
+ 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 information about the tileset
+ // used by the tilemap.
+ //
+ // Example
+ // contentPath
+ //
+ // The region attribute represents the x, y, width, and height
+ // components of the boundary for the texture region within the
+ // texture at the contentPath specified.
+ //
+ // the tileWidth and tileHeight attributes specify the width and
+ // height of each tile in the tileset.
+ //
+ // the contentPath value is the contentPath to the texture to
+ // load that contains the tileset
+ XElement tilesetElement = root.Element("Tileset");
+
+ string regionAttribute = tilesetElement.Attribute("region").Value;
+ string[] split = regionAttribute.Split(" ", StringSplitOptions.RemoveEmptyEntries);
+ int x = int.Parse(split[0]);
+ int y = int.Parse(split[1]);
+ int width = int.Parse(split[2]);
+ int height = int.Parse(split[3]);
+
+ int tileWidth = int.Parse(tilesetElement.Attribute("tileWidth").Value);
+ int tileHeight = int.Parse(tilesetElement.Attribute("tileHeight").Value);
+ string contentPath = tilesetElement.Value;
+
+ // Load the texture 2d at the content path
+ Texture2D texture = content.Load(contentPath);
+
+ // Create the texture region from the texture
+ TextureRegion textureRegion = new TextureRegion(texture, x, y, width, height);
+
+ // Create the tileset using the texture region
+ Tileset tileset = new Tileset(textureRegion, tileWidth, tileHeight);
+
+ // The element contains lines of strings where each line
+ // represents a row in the tilemap. Each line is a space
+ // separated string where each element represents a column in that
+ // row. The value of the column is the id of the tile in the
+ // tileset to draw for that location.
+ //
+ // Example:
+ //
+ // 00 01 01 02
+ // 03 04 04 05
+ // 03 04 04 05
+ // 06 07 07 08
+ //
+ XElement tilesElement = root.Element("Tiles");
+
+ // Split the value of the tiles data into rows by splitting on
+ // the new line character
+ string[] rows = tilesElement.Value.Trim().Split('\n', StringSplitOptions.RemoveEmptyEntries);
+
+ // Split the value of the first row to determine the total number of columns
+ int columnCount = rows[0].Split(" ", StringSplitOptions.RemoveEmptyEntries).Length;
+
+ // Create the tilemap
+ Tilemap tilemap = new Tilemap(tileset, columnCount, rows.Length);
+
+ // Process each row
+ for (int row = 0; row < rows.Length; row++)
+ {
+ // Split the row into individual columns
+ string[] columns = rows[row].Trim().Split(" ", StringSplitOptions.RemoveEmptyEntries);
+
+ // Process each column of the current row
+ for (int column = 0; column < columnCount; column++)
+ {
+ // Get the tileset index for this location
+ int tilesetIndex = int.Parse(columns[column]);
+
+ // Get the texture region of that tile from the tileset
+ TextureRegion region = tileset.GetTile(tilesetIndex);
+
+ // Add that region to the tilemap at the row and column location
+ tilemap.SetTile(column, row, tilesetIndex);
+ }
+ }
+
+ return tilemap;
+ }
+ }
+ }
+ #endregion
+}
diff --git a/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/tileset.cs b/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/tileset.cs
new file mode 100644
index 00000000..c4b3d32d
--- /dev/null
+++ b/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/tileset.cs
@@ -0,0 +1,87 @@
+#region declaration
+namespace MonoGameLibrary.Graphics;
+
+public class Tileset
+{
+
+}
+#endregion
+{
+ #region properties
+ private readonly TextureRegion[] _tiles;
+
+ ///
+ /// Gets the width, in pixels, of each tile in this tileset.
+ ///
+ public int TileWidth { get; }
+
+ ///
+ /// Gets the height, in pixels, of each tile in this tileset.
+ ///
+ public int TileHeight { get; }
+
+ ///
+ /// Gets the total number of columns in this tileset.
+ ///
+ public int Columns { get; }
+
+ ///
+ /// Gets the total number of rows in this tileset.
+ ///
+ public int Rows { get; }
+
+ ///
+ /// Gets the total number of tiles in this tileset.
+ ///
+ public int Count { get; }
+ #endregion
+
+ #region ctors
+ ///
+ /// Creates a new tileset based on the given texture region with the specified
+ /// tile width and height.
+ ///
+ /// The texture region that contains the tiles for the tileset.
+ /// The width of each tile in the tileset.
+ /// The height of each tile in the tileset.
+ public Tileset(TextureRegion textureRegion, int tileWidth, int tileHeight)
+ {
+ TileWidth = tileWidth;
+ TileHeight = tileHeight;
+ Columns = textureRegion.Width / tileWidth;
+ Rows = textureRegion.Height / tileHeight;
+ Count = Columns * Rows;
+
+ // Create the texture regions that make up each individual tile
+ _tiles = new TextureRegion[Count];
+
+ for (int i = 0; i < Count; i++)
+ {
+ int x = i % Columns * tileWidth;
+ int y = i / Columns * tileHeight;
+ _tiles[i] = new TextureRegion(textureRegion.Texture, textureRegion.SourceRectangle.X + x, textureRegion.SourceRectangle.Y + y, tileWidth, tileHeight);
+ }
+ }
+ #endregion
+
+ #region methods
+ ///
+ /// Gets the texture region for the tile from this tileset at the given index.
+ ///
+ /// The index of the texture region in this tile set.
+ /// The texture region for the tile form this tileset at the given index.
+ public TextureRegion GetTile(int index) => _tiles[index];
+
+ ///
+ /// Gets the texture region for the tile from this tileset at the given location.
+ ///
+ /// The column in this tileset of the texture region.
+ /// The row in this tileset of the texture region.
+ /// The texture region for the tile from this tileset at given location.
+ public TextureRegion GetTile(int column, int row)
+ {
+ int index = row * Columns + column;
+ return GetTile(index);
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/13_working_with_tilemaps/videos/gameplay.webm b/articles/tutorials/building_2d_games/13_working_with_tilemaps/videos/gameplay.webm
new file mode 100644
index 00000000..6b4037c8
Binary files /dev/null and b/articles/tutorials/building_2d_games/13_working_with_tilemaps/videos/gameplay.webm differ
diff --git a/articles/tutorials/building_2d_games/14_soundeffects_and_music/files/bounce.wav b/articles/tutorials/building_2d_games/14_soundeffects_and_music/files/bounce.wav
new file mode 100644
index 00000000..baa7a47b
Binary files /dev/null and b/articles/tutorials/building_2d_games/14_soundeffects_and_music/files/bounce.wav differ
diff --git a/articles/tutorials/building_2d_games/14_soundeffects_and_music/files/collect.wav b/articles/tutorials/building_2d_games/14_soundeffects_and_music/files/collect.wav
new file mode 100644
index 00000000..506220de
Binary files /dev/null and b/articles/tutorials/building_2d_games/14_soundeffects_and_music/files/collect.wav differ
diff --git a/articles/tutorials/building_2d_games/14_soundeffects_and_music/files/theme.ogg b/articles/tutorials/building_2d_games/14_soundeffects_and_music/files/theme.ogg
new file mode 100644
index 00000000..72e1fd3b
Binary files /dev/null and b/articles/tutorials/building_2d_games/14_soundeffects_and_music/files/theme.ogg differ
diff --git a/articles/tutorials/building_2d_games/14_soundeffects_and_music/images/song-properties.png b/articles/tutorials/building_2d_games/14_soundeffects_and_music/images/song-properties.png
new file mode 100644
index 00000000..87d53631
Binary files /dev/null and b/articles/tutorials/building_2d_games/14_soundeffects_and_music/images/song-properties.png differ
diff --git a/articles/tutorials/building_2d_games/14_soundeffects_and_music/images/sound-effect-properties.png b/articles/tutorials/building_2d_games/14_soundeffects_and_music/images/sound-effect-properties.png
new file mode 100644
index 00000000..6fe24eed
Binary files /dev/null and b/articles/tutorials/building_2d_games/14_soundeffects_and_music/images/sound-effect-properties.png differ
diff --git a/articles/tutorials/building_2d_games/14_soundeffects_and_music/images/xact-editor.png b/articles/tutorials/building_2d_games/14_soundeffects_and_music/images/xact-editor.png
new file mode 100644
index 00000000..667d6771
Binary files /dev/null and b/articles/tutorials/building_2d_games/14_soundeffects_and_music/images/xact-editor.png differ
diff --git a/articles/tutorials/building_2d_games/14_soundeffects_and_music/index.md b/articles/tutorials/building_2d_games/14_soundeffects_and_music/index.md
new file mode 100644
index 00000000..5ae662e4
--- /dev/null
+++ b/articles/tutorials/building_2d_games/14_soundeffects_and_music/index.md
@@ -0,0 +1,257 @@
+---
+title: "Chapter 14: SoundEffects and Music"
+description: "Learn how to load and play sound effects and background music in MonoGame including managing audio volume, looping, and handling multiple sound effects at once."
+---
+
+In [Chapter 12](../12_collision_detection/index.md), we implemented collision detection to enable interactions between game objects; the slime can now "eat" the bat, which respawns in a random location, while the bat bounces off walls of the dungeon. While these mechanics work visually, our game lacks an important element of player feedback: audio.
+
+Audio plays a crucial role in game development by providing immediate feedback for player actions and creating atmosphere. Sound effects alert players when events occur (like collisions or collecting items), while background music helps establish mood and atmosphere.
+
+In this chapter, you will:
+
+- Learn how MonoGame handles different types of audio content.
+- Learn how to load and play sound effects and music using the content pipeline.
+- Implement sound effects for collision events.
+- Add background music to enhance atmosphere.
+
+We will first start by understanding how MonoGame approaches audio content.
+
+## Understanding Audio in MonoGame
+
+Recall from [Chapter 01](../01_what_is_monogame/index.md) that MonoGame is an implementation of the XNA API. With XNA, there were two methods for implementing audio in your game: the *Microsoft Cross-Platform Audio Creation Tool* (XACT) and the simplified sound API.
+
+> [!IMPORTANT]
+> XACT is a mini audio engineering studio where you can easily edit the audio for your game like editing volume, pitch, looping, applying effects, and other properties without having to do it in code. At that time, XACT for XNA games was akin to what FMOD Studio is today for game audio.
+>
+> |  |
+> |:--------------------------------------------------------------------------------------:|
+> | **Figure 14-1: Microsoft Cross-Platform Audio Creation Tool** |
+>
+> While XACT projects are still fully supported in MonoGame, it remains a Windows-only tool that has not been updated since Microsoft discontinued the original XNA, nor has its source code been made open source. Though it is possible to install XACT on modern Windows, the process can be complex.
+>
+> For these reasons, this tutorial will focus on the simplified sound API, which provides all the core functionality needed for most games while remaining cross-platform compatible.
+
+The simplified sound API approaches audio management through two distinct paths, each optimized for different use cases in games. When adding audio to your game, you need to consider how different types of sounds should be handled:
+
+- **Sound Effects**: Short audio clips that need to play immediately and often simultaneously, like the bounce of a ball or feedback for picking up a collectable.
+- **Music**: Longer audio pieces that play continuously in the background, like level themes.
+
+MonoGame addresses these different needs through two main classes:
+
+### Sound Effects
+
+The [**SoundEffect**](xref:Microsoft.Xna.Framework.Audio.SoundEffect) class handles short audio clips like:
+
+- Collision sounds.
+- Player action feedback (jumping, shooting, etc.).
+- UI interactions (button clicks, menu navigation).
+- Environmental effects (footsteps, ambient sounds).
+
+The key characteristics of sound effects are:
+
+- Loaded entirely into memory for quick access
+- Can play multiple instances simultaneously:
+ - Mobile platforms can have a maximum of 32 sounds playing simultaneously.
+ - Desktop platforms have a maximum of 256 sounds playing simultaneously.
+ - Consoles and other platforms have their own constraints, and you would need to refer to the SDK documentation for that platform.
+- Lower latency playback (ideal for immediate feedback)
+- Individual volume control per instance.
+
+### Music
+
+The [**Song**](xref:Microsoft.Xna.Framework.Media.Song) class handles longer audio pieces like background music. The key characteristics of songs are:
+
+- Streamed from storage rather than loaded into memory.
+- Only one song can be played at a time.
+- Higher latency, but lower memory usage.
+
+Throughout this chapter, we will use both classes to add audio feedback to our game; sound effects for the bat bouncing and being eaten by the slime, and background music to create atmosphere.
+
+## Loading Audio Content
+
+Just like textures, audio content in MonoGame can be loaded through the content pipeline, optimizing the format for your target platform.
+
+### Supported Audio Formats
+
+MonoGame supports several audio file formats for both sound effects and music:
+
+- `.wav`: Uncompressed audio, ideal for short sound effects
+- `.mp3`: Compressed audio, better for music and longer sounds
+- `.ogg`: Open source compressed format, supported on all platforms
+- `.wma`: Windows Media Audio format (not recommended for cross-platform games)
+
+> [!TIP]
+> For sound effects, `.wav` files provide the best loading and playback performance since they do not need to be decompressed. For music, `.mp3` or `.ogg` files are better choices as they reduce file size while maintaining good quality.
+
+### Adding Audio Files
+
+Adding audio files can be done through the content pipeline, just like we did for image files, using the MGCB Editor. When you add an audio file to the content project, the MGCB Editor will automatically select the appropriate importer and processor for the audio file based on the file extension.
+
+The processor that are available for audio files file:
+
+- **Sound Effects**: Processes the audio file as a [**SoundEffect**](xref:Microsoft.Xna.Framework.Audio.SoundEffect). This is automatically selected for *.wav* files.
+- **Song**: Processes the audio file as a [**Song**](xref:Microsoft.Xna.Framework.Media.Song). This is automatically selected for *.mp3*, *.ogg*, and *.wma* files.
+
+|  |  |
+| :-----------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 14-2: MGCB Editor properties panel showing Sound Effect content processor settings for .wav files** | **Figure 14-3: MGCB Editor properties panel showing Song content processor settings for .mp3 files** |
+
+> [!NOTE]
+> While you typically will not need to change the processor it automatically selects, there may be times where you add files, such as *.mp3* files that are meant to be sound effects and not songs. Always double check that the processor selected is for the intended type.
+
+### Loading Sound Effects
+
+To load a sound effect, we use [**ContentManager.Load**](xref:Microsoft.Xna.Framework.Content.ContentManager.Load``1(System.String)) with the [**SoundEffect**](xref:Microsoft.Xna.Framework.Audio.SoundEffect) type:
+
+[!code-csharp[](./snippets/load_soundeffect.cs)]
+
+### Loading Music
+
+Loading music is similar, only we specify the [**Song**](xref:Microsoft.Xna.Framework.Media.Song) type instead.
+
+[!code-csharp[](./snippets/load_song.cs)]
+
+## Playing Sound Effects
+
+Sound effects are played using the [**SoundEffect**](xref:Microsoft.Xna.Framework.Audio.SoundEffect) class. This class provides two ways to play sounds:
+
+1. Direct playback using [**SoundEffect.Play**](xref:Microsoft.Xna.Framework.Audio.SoundEffect.Play):
+
+ [!code-csharp[](./snippets/play_soundeffect.cs)]
+
+2. Creating an instance using [**SoundEffect.CreateInstance**](xref:Microsoft.Xna.Framework.Audio.SoundEffect.CreateInstance):
+
+ [!code-csharp[](./snippets/play_soundeffect_instance.cs)]
+
+- Use [**SoundEffect.Play**](xref:Microsoft.Xna.Framework.Audio.SoundEffect.Play) for simple sound effects that you just want to play once.
+- Use [**SoundEffect.CreateInstance**](xref:Microsoft.Xna.Framework.Audio.SoundEffect.CreateInstance) when you need more control over the sound effect, like adjusting volume, looping, or managing multiple instances of the same sound.
+
+[**SoundEffectInstance**](xref:Microsoft.Xna.Framework.Audio.SoundEffectInstance) contains several properties that can be used to control how the sound effect is played:
+
+| Property | Type | Description |
+| ------------------------------------------------------------------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------- |
+| [**IsLooped**](xref:Microsoft.Xna.Framework.Audio.SoundEffectInstance.IsLooped) | `bool` | Whether the sound should loop when it reaches the end. |
+| [**Pan**](xref:Microsoft.Xna.Framework.Audio.SoundEffectInstance.Pan) | `float` | Stereo panning between -1.0f (full left) and 1.0f (full right). |
+| [**Pitch**](xref:Microsoft.Xna.Framework.Audio.SoundEffectInstance.Pitch) | `float` | Pitch adjustment between -1.0f (down one octave) and 1.0f (up one octave). |
+| [**State**](xref:Microsoft.Xna.Framework.Audio.SoundEffectInstance.State) | [**SoundState**](xref:Microsoft.Xna.Framework.Audio.SoundState) | Current playback state (Playing, Paused, or Stopped). |
+| [**Volume**](xref:Microsoft.Xna.Framework.Audio.SoundEffectInstance.Volume) | `float` | Volume level between 0.0f (silent) and 1.0f (full volume). |
+
+> [!NOTE]
+> There is a lot more behind SoundEffectInstances such as the ability to play 3D sounds, as well as the advanced capabilities of the [DynamicSoundEffectInstance](xref:Microsoft.Xna.Framework.Audio.SoundEffectInstance) which can build audio streams. However, these are beyond the scope of this beginners guide. If you wish to know more, then check the [MonoGame documentation](/articles/getting_to_know/whatis/audio/)
+
+## Playing Music
+
+Unlike sound effects, music is played through the [**MediaPlayer**](xref:Microsoft.Xna.Framework.Media.MediaPlayer) class. This static class manages playback of [**Song**](xref:Microsoft.Xna.Framework.Media.Song) instances and provides global control over music playback:
+
+[!code-csharp[](./snippets/play_song.cs)]
+
+> [!IMPORTANT]
+> While [**SoundEffect**](xref:Microsoft.Xna.Framework.Audio.SoundEffect) instances can be played simultaneously, trying to play a new [**Song**](xref:Microsoft.Xna.Framework.Media.Song) while another is playing will stop the current song in the best case, and in the worst case cause a crash on some platforms. In the example above, the state of the media player is checked first before we tell it to play a song. Checking the state first and stopping it manually if it is playing is best practice to prevent potential crashes.
+
+## Adding Audio To Our Game
+
+Before we can add audio to our game, we need some sound files to work with. Download the following audio files:
+
+- [bounce.wav](./files/bounce.wav){download} - For when the bat bounces off screen edges
+- [collect.wav](./files/collect.wav){download} - For when the slime eats the bat
+- [theme.ogg](./files/theme.ogg){download} - Background music
+
+> [!NOTE]
+>
+> - *bounce.wav* is "Retro Impact Punch 07" by Davit Masia ().
+> - *collect.wav* is "Retro Jump Classic 08" by Davit Masia ().
+> - *theme.mp3* is "Exploration" by Luis Zuno ([@ansimuz](https://twitter.com/ansimuz)).
+
+Add these files to your content project using the MGCB Editor:
+
+1. Open the *Content.mgcb* file in the MGCB Editor.
+2. Create a new folder called `audio` (right-click *Content* > *Add* > *New Folder*).
+3. Right-click the new *audio* folder and choose *Add* > *Existing Item...*.
+4. Navigate to and select the audio files you downloaded.
+5. For each file that is added, check its properties in the Properties panel:
+ - For `.wav` files, ensure the *Processor* is set to `Sound Effect`.
+ - For `.mp3` files, ensure the *Processor* is set to `Song`.
+6. Save the changes and close the MGCB Editor.
+
+Next, open the `Game1.cs` file and update it to the following:
+
+[!code-csharp[](./snippets/game1.cs?highlight=3,6,39-43,92-111,203-204,222-223)]
+
+The key changes here are:
+
+1. Added the `using Microsoft.Xna.Framework.Audio;` and `using Microsoft.Xna.Framework.Media;` directories to access the [**Song**](xref:Microsoft.Xna.Framework.Media.Song) and [**SoundEffect**](xref:Microsoft.Xna.Framework.Audio) classes.
+2. Added the `_boundSoundEffect` and `_collectSoundEffect` fields to store those sound effects when loaded and use them for playback.
+3. In [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent)
+ 1. The bounce and collect sound effects are loaded using the content manager.
+ 2. The background theme music is loaded using the content manager.
+ 3. The background music is played using the media player, checking its state first.
+ 4. The [**MediaPlayer.IsRepeating**](xref:Microsoft.Xna.Framework.Media.MediaPlayer.IsRepeating) is set to `true` so the background music loops.
+4. In [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)):
+ 1. The bounce sound effect is played when the bat bounces off the edge of the screen.
+ 2. The collect sound effect is played when the slime eats the bat.
+
+Running the game now, the theme music plays in the background, you can hear the bat bounce off the edge of the screen, and if you move the slime to eat the bat, you hear that as well.
+
+|  |
+| :----------------------------------------------------------: |
+| **Figure 14-4: Gameplay with audio.** |
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+- Learned about MonoGame's audio system including sound effects and music.
+- Explored the key differences between:
+ - Sound effects (short, multiple simultaneous playback).
+ - Music (longer, streamed, single playback).
+- Added audio content to your game project through the content pipeline.
+- Loaded audio files using the ContentManager.
+- Implemented audio feedback in your game:
+ - Background music to set atmosphere.
+ - Sound effects for bat bouncing and collection events.
+- Learned best practices for handling audio playback across different platforms.
+
+In the next chapter, we will explore additional ways to manage audio by creating an audio controller module that will help with common tasks such as volume control, muting, and state management.
+
+## Test Your Knowledge
+
+1. What are the two main classes MonoGame provides for audio playback and how do they differ?
+
+ :::question-answer
+ MonoGame provides:
+
+ - [**SoundEffect**](xref:Microsoft.Xna.Framework.Audio.SoundEffect) for short audio clips (loaded entirely into memory, multiple can play at once) and
+ - [**Song**](xref:Microsoft.Xna.Framework.Media.Song) for longer audio like music (streamed from storage, only one can play at a time).
+
+ :::
+
+2. Why is it important to check if [**MediaPlayer**](xref:Microsoft.Xna.Framework.Media.MediaPlayer) is already playing before starting a new song?
+
+ :::question-answer
+ Checking if MediaPlayer is already playing and stopping it if necessary helps prevent crashes on some platforms. Since only one song can play at a time, properly stopping the current song before starting a new one ensures reliable behavior across different platforms.
+ :::
+
+3. What file formats are best suited for sound effects and music, respectively, and why?
+
+ :::question-answer
+ For sound effects, .wav files are generally best because they are uncompressed and load quickly into memory for immediate playback. For music, compressed formats like .mp3 or .ogg are better suited because they greatly reduce file size while maintaining good audio quality, which is important for longer audio that's streamed rather than fully loaded.
+ :::
+
+4. What is the difference between using [**SoundEffect.Play**](xref:Microsoft.Xna.Framework.Audio.SoundEffect.Play) directly and creating a [**SoundEffectInstance**](xref:Microsoft.Xna.Framework.Audio.SoundEffectInstance)?
+
+ :::question-answer
+
+ - [**SoundEffect.Play**](xref:Microsoft.Xna.Framework.Audio.SoundEffect.Play) is simpler but provides limited control - it plays the sound once with basic volume/pitch/pan settings.
+ - Creating a [**SoundEffectInstance**](xref:Microsoft.Xna.Framework.Audio.SoundEffectInstance) gives more control including the ability to pause, resume, loop, and change properties during playback, as well as track the sound's state.
+
+ :::
+
+5. How many sound effects can play simultaneously on different platforms?
+
+ :::question-answer
+ The number of simultaneous sound effects varies by platform:
+
+ - Mobile platforms: maximum of 32 sounds.
+ - Desktop platforms: maximum of 256 sounds.
+ - Consoles and other platforms have their own constraints specified in their respective SDK documentation.
+ :::
diff --git a/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/game1.cs b/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/game1.cs
new file mode 100644
index 00000000..e06f1633
--- /dev/null
+++ b/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/game1.cs
@@ -0,0 +1,352 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using Microsoft.Xna.Framework.Media;
+using MonoGameLibrary;
+using MonoGameLibrary.Graphics;
+using MonoGameLibrary.Input;
+
+namespace DungeonSlime;
+
+public class Game1 : Core
+{
+ // Defines the slime animated sprite.
+ private AnimatedSprite _slime;
+
+ // Defines the bat animated sprite.
+ private AnimatedSprite _bat;
+
+ // Tracks the position of the slime.
+ private Vector2 _slimePosition;
+
+ // Speed multiplier when moving.
+ private const float MOVEMENT_SPEED = 5.0f;
+
+ // Tracks the position of the bat.
+ private Vector2 _batPosition;
+
+ // Tracks the velocity of the bat.
+ private Vector2 _batVelocity;
+
+ // Defines the tilemap to draw.
+ private Tilemap _tilemap;
+
+ // Defines the bounds of the room that the slime and bat are contained within.
+ private Rectangle _roomBounds;
+
+ // The sound effect to play when the bat bounces off the edge of the screen.
+ private SoundEffect _bounceSoundEffect;
+
+ // The sound effect to play when the slime eats a bat.
+ private SoundEffect _collectSoundEffect;
+
+ public Game1() : base("Dungeon Slime", 1280, 720, false)
+ {
+
+ }
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+
+ Rectangle screenBounds = GraphicsDevice.PresentationParameters.Bounds;
+
+ _roomBounds = new Rectangle(
+ (int)_tilemap.TileWidth,
+ (int)_tilemap.TileHeight,
+ screenBounds.Width - (int)_tilemap.TileWidth * 2,
+ screenBounds.Height - (int)_tilemap.TileHeight * 2
+ );
+
+ // Initial slime position will be the center tile of the tile map.
+ int centerRow = _tilemap.Rows / 2;
+ int centerColumn = _tilemap.Columns / 2;
+ _slimePosition = new Vector2(centerColumn * _tilemap.TileWidth, centerRow * _tilemap.TileHeight);
+
+ // Initial bat position will the in the top left corner of the room
+ _batPosition = new Vector2(_roomBounds.Left, _roomBounds.Top);
+
+ // Assign the initial random velocity to the bat.
+ AssignRandomBatVelocity();
+ }
+
+ protected override void LoadContent()
+ {
+ // Create the texture atlas from the XML configuration file
+ TextureAtlas atlas = TextureAtlas.FromFile(Content, "images/atlas-definition.xml");
+
+ // Create the slime animated sprite from the atlas.
+ _slime = atlas.CreateAnimatedSprite("slime-animation");
+ _slime.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+ _bat.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the tilemap from the XML configuration file.
+ _tilemap = Tilemap.FromFile(Content, "images/tilemap-definition.xml");
+ _tilemap.Scale = new Vector2(4.0f, 4.0f);
+
+ // Load the bounce sound effect
+ _bounceSoundEffect = Content.Load("audio/bounce");
+
+ // Load the collect sound effect
+ _collectSoundEffect = Content.Load("audio/collect");
+
+ // Load the background theme music
+ Song theme = Content.Load("audio/theme");
+
+ // Ensure media player is not already playing on device, if so, stop it
+ if (MediaPlayer.State == MediaState.Playing)
+ {
+ MediaPlayer.Stop();
+ }
+
+ // Play the background theme music.
+ MediaPlayer.Play(theme);
+
+ // Set the theme music to repeat.
+ MediaPlayer.IsRepeating = true;
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
+ Exit();
+
+ // Update the slime animated sprite.
+ _slime.Update(gameTime);
+
+ // Update the bat animated sprite.
+ _bat.Update(gameTime);
+
+ // Check for keyboard input and handle it.
+ CheckKeyboardInput();
+
+ // Check for gamepad input and handle it.
+ CheckGamePadInput();
+
+ // Creating a bounding circle for the slime
+ Circle slimeBounds = new Circle(
+ (int)(_slimePosition.X + (_slime.Width * 0.5f)),
+ (int)(_slimePosition.Y + (_slime.Height * 0.5f)),
+ (int)(_slime.Width * 0.5f)
+ );
+
+ // Use distance based checks to determine if the slime is within the
+ // bounds of the game screen, and if it is outside that screen edge,
+ // move it back inside.
+ if (slimeBounds.Left < _roomBounds.Left)
+ {
+ _slimePosition.X = _roomBounds.Left;
+ }
+ else if (slimeBounds.Right > _roomBounds.Right)
+ {
+ _slimePosition.X = _roomBounds.Right - _slime.Width;
+ }
+
+ if (slimeBounds.Top < _roomBounds.Top)
+ {
+ _slimePosition.Y = _roomBounds.Top;
+ }
+ else if (slimeBounds.Bottom > _roomBounds.Bottom)
+ {
+ _slimePosition.Y = _roomBounds.Bottom - _slime.Height;
+ }
+
+ // Calculate the new position of the bat based on the velocity
+ Vector2 newBatPosition = _batPosition + _batVelocity;
+
+ // Create a bounding circle for the bat
+ Circle batBounds = new Circle(
+ (int)(newBatPosition.X + (_bat.Width * 0.5f)),
+ (int)(newBatPosition.Y + (_bat.Height * 0.5f)),
+ (int)(_bat.Width * 0.5f)
+ );
+
+ Vector2 normal = Vector2.Zero;
+
+ // Use distance based checks to determine if the bat is within the
+ // bounds of the game screen, and if it is outside that screen edge,
+ // reflect it about the screen edge normal
+ if (batBounds.Left < _roomBounds.Left)
+ {
+ normal.X = Vector2.UnitX.X;
+ newBatPosition.X = _roomBounds.Left;
+ }
+ else if (batBounds.Right > _roomBounds.Right)
+ {
+ normal.X = -Vector2.UnitX.X;
+ newBatPosition.X = _roomBounds.Right - _bat.Width;
+ }
+
+ if (batBounds.Top < _roomBounds.Top)
+ {
+ normal.Y = Vector2.UnitY.Y;
+ newBatPosition.Y = _roomBounds.Top;
+ }
+ else if (batBounds.Bottom > _roomBounds.Bottom)
+ {
+ normal.Y = -Vector2.UnitY.Y;
+ newBatPosition.Y = _roomBounds.Bottom - _bat.Height;
+ }
+
+ // If the normal is anything but Vector2.Zero, this means the bat had
+ // moved outside the screen edge so we should reflect it about the
+ // normal.
+ if (normal != Vector2.Zero)
+ {
+ _batVelocity = Vector2.Reflect(_batVelocity, normal);
+
+ // Play the bounce sound effect
+ _bounceSoundEffect.Play();
+ }
+
+ _batPosition = newBatPosition;
+
+ if (slimeBounds.Intersects(batBounds))
+ {
+ // Choose a random row and column based on the total number of each
+ int column = Random.Shared.Next(1, _tilemap.Columns - 1);
+ int row = Random.Shared.Next(1, _tilemap.Rows - 1);
+
+ // Change the bat position by setting the x and y values equal to
+ // the column and row multiplied by the width and height.
+ _batPosition = new Vector2(column * _bat.Width, row * _bat.Height);
+
+ // Assign a new random velocity to the bat
+ AssignRandomBatVelocity();
+
+ // Play the collect sound effect
+ _collectSoundEffect.Play();
+ }
+
+ base.Update(gameTime);
+ }
+
+ private void AssignRandomBatVelocity()
+ {
+ // Generate a random angle
+ float angle = (float)(Random.Shared.NextDouble() * Math.PI * 2);
+
+ // Convert angle to a direction vector
+ float x = (float)Math.Cos(angle);
+ float y = (float)Math.Sin(angle);
+ Vector2 direction = new Vector2(x, y);
+
+ // Multiply the direction vector by the movement speed
+ _batVelocity = direction * MOVEMENT_SPEED;
+ }
+
+ private void CheckKeyboardInput()
+ {
+ // If the space key is held down, the movement speed increases by 1.5
+ float speed = MOVEMENT_SPEED;
+ if (Input.Keyboard.IsKeyDown(Keys.Space))
+ {
+ speed *= 1.5f;
+ }
+
+ // If the W or Up keys are down, move the slime up on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.W) || Input.Keyboard.IsKeyDown(Keys.Up))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // if the S or Down keys are down, move the slime down on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.S) || Input.Keyboard.IsKeyDown(Keys.Down))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If the A or Left keys are down, move the slime left on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.A) || Input.Keyboard.IsKeyDown(Keys.Left))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If the D or Right keys are down, move the slime right on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.D) || Input.Keyboard.IsKeyDown(Keys.Right))
+ {
+ _slimePosition.X += speed;
+ }
+ }
+
+ private void CheckGamePadInput()
+ {
+ GamePadInfo gamePadOne = Input.GamePads[(int)PlayerIndex.One];
+
+ // If the A button is held down, the movement speed increases by 1.5
+ // and the gamepad vibrates as feedback to the player.
+ float speed = MOVEMENT_SPEED;
+ if (gamePadOne.IsButtonDown(Buttons.A))
+ {
+ speed *= 1.5f;
+ GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f);
+ }
+ else
+ {
+ GamePad.SetVibration(PlayerIndex.One, 0.0f, 0.0f);
+ }
+
+ // Check thumbstick first since it has priority over which gamepad input
+ // is movement. It has priority since the thumbstick values provide a
+ // more granular analog value that can be used for movement.
+ if (gamePadOne.LeftThumbStick != Vector2.Zero)
+ {
+ _slimePosition.X += gamePadOne.LeftThumbStick.X * speed;
+ _slimePosition.Y -= gamePadOne.LeftThumbStick.Y * speed;
+ }
+ else
+ {
+ // If DPadUp is down, move the slime up on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadUp))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // If DPadDown is down, move the slime down on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadDown))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If DPapLeft is down, move the slime left on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadLeft))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If DPadRight is down, move the slime right on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadRight))
+ {
+ _slimePosition.X += speed;
+ }
+ }
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
+
+ // Draw the tilemap
+ _tilemap.Draw(SpriteBatch);
+
+ // Draw the slime sprite.
+ _slime.Draw(SpriteBatch, _slimePosition);
+
+ // Draw the bat sprite.
+ _bat.Draw(SpriteBatch, _batPosition);
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/load_song.cs b/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/load_song.cs
new file mode 100644
index 00000000..c58272ab
--- /dev/null
+++ b/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/load_song.cs
@@ -0,0 +1,2 @@
+// Loading a Song using the content pipeline
+Song song = Content.Load("song");
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/load_soundeffect.cs b/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/load_soundeffect.cs
new file mode 100644
index 00000000..4ca6fd7f
--- /dev/null
+++ b/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/load_soundeffect.cs
@@ -0,0 +1,2 @@
+// Loading a SoundEffect using the content pipeline
+SoundEffect soundEffect = Content.Load("soundEffect");
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/play_song.cs b/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/play_song.cs
new file mode 100644
index 00000000..be6c0782
--- /dev/null
+++ b/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/play_song.cs
@@ -0,0 +1,17 @@
+// Loading a Song using the content pipeline
+Song song = Content.Load("song");
+
+// Set whether the song should repeat when finished
+MediaPlayer.IsRepeating = true;
+
+// Adjust the volume (0.0f to 1.0f)
+MediaPlayer.Volume = 0.5f;
+
+// Check if the media player is already playing, if so, stop it
+if(MediaPlayer.State == MediaState.Playing)
+{
+ MediaPlayer.Stop();
+}
+
+// Start playing the background music
+MediaPlayer.Play(song);
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/play_soundeffect.cs b/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/play_soundeffect.cs
new file mode 100644
index 00000000..cff82a26
--- /dev/null
+++ b/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/play_soundeffect.cs
@@ -0,0 +1,5 @@
+// Loading a SoundEffect using the content pipeline
+SoundEffect soundEffect = Content.Load("soundEffect");
+
+// Play the sound effect with default settings
+soundEffect.Play();
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/play_soundeffect_instance.cs b/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/play_soundeffect_instance.cs
new file mode 100644
index 00000000..9be58a45
--- /dev/null
+++ b/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/play_soundeffect_instance.cs
@@ -0,0 +1,12 @@
+// Loading a SoundEffect using the content pipeline
+SoundEffect soundEffect = Content.Load("soundEffect");
+
+// Create an instance we can control
+SoundEffectInstance soundEffectInstance = soundEffect.CreateInstance();
+
+// Adjust the properties of the instance as needed
+soundEffectInstance.IsLooped = true; // Make it loop
+soundEffectInstance.Volume = 0.5f; // Set half volume.
+
+// Play the sound effect using the instance.
+soundEffectInstance.Play();
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/14_soundeffects_and_music/videos/gameplay.webm b/articles/tutorials/building_2d_games/14_soundeffects_and_music/videos/gameplay.webm
new file mode 100644
index 00000000..1c1981cb
Binary files /dev/null and b/articles/tutorials/building_2d_games/14_soundeffects_and_music/videos/gameplay.webm differ
diff --git a/articles/tutorials/building_2d_games/15_audio_controller/index.md b/articles/tutorials/building_2d_games/15_audio_controller/index.md
new file mode 100644
index 00000000..80f7bc69
--- /dev/null
+++ b/articles/tutorials/building_2d_games/15_audio_controller/index.md
@@ -0,0 +1,175 @@
+---
+title: "Chapter 15: Audio Controller"
+description: "Learn how to create a reusable audio controller class to manage sound effects and music, including volume control, muting/unmuting, and proper resource cleanup."
+---
+
+While playing sounds and music using the simplified sound API is straightforward, a game needs to handle various audio states and resource cleanup including:
+
+- Track and manage sound effect instances that are created.
+- Dispose of sound effect instances when they are finished.
+- Handle volume control for songs and sound effects.
+- Manage audio states (pause/resume, mute/unmute).
+
+In this chapter you will:
+
+- Learn how to create a central audio management system.
+- Implement proper resource tracking and cleanup for sound effects.
+- Build methods to control audio state (play/pause, mute/unmute).
+- Add global volume control for different audio types.
+- Integrate the audio controller with your game's core systems.
+- Implement keyboard shortcuts for audio control.
+
+By the end of this chapter, you will have an audio control system that can be easily reused in future game projects.
+
+## The AudioController Class
+
+To get started, in the *MonoGameLibrary* project:
+
+1. Create a new folder named `Audio`.
+2. Add a new class file named `AudioController.cs` to the `Audio` folder you just created.
+3. Add the following code as the initial structure for the class
+
+ [!code-csharp[](./snippets/audiocontroller.cs#declaration)]
+
+ > [!NOTE]
+ > The `AudioController` class will implement the `IDisposable` interface, This interface is part of .NET and provides a standardized implementation for an object to release resources. Implementing `IDisposable` allows other code to properly clean up the resources held by our audio controller when it is no longer needed. For more information on `IDisposable`, you can read the [Implement a Dispose Method](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose) article on Microsoft Learn.
+
+### AudioController Properties and Fields
+
+The `AudioController` will need to track sound effect instances created for cleanup and track the state and volume levels of songs and sound effects when toggling between mute states.
+
+Add the following fields and properties:
+
+[!code-csharp[](./snippets/audiocontroller.cs#properties)]
+
+### AudioController Constructor
+
+The constructor just initializes the collection used to track the sound effect instances.
+
+Add the following constructor and finalizer:
+
+[!code-csharp[](./snippets/audiocontroller.cs#ctors)]
+
+> [!NOTE]
+> The `AudioController` class implements a [finalizer](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/finalizers) method `~AudioManager()`. This method is called when an instance of the class is collected by the garbage collector and is here as part of the `IDisposable` implementation.
+
+### AudioController Methods
+
+The `AudioController` needs methods to:
+
+- Update it to check for resources to clean up.
+- Playing sound effects and songs
+- State management (play/pause, mute/unmute)
+- Volume control
+- Implement the `IDisposable` interface.
+
+So lets add them below.
+
+#### AudioController Update
+
+The `Update` method will check for existing sound effect instances that have expired and properly dispose of them. Add the following method:
+
+[!code-csharp[](./snippets/audiocontroller.cs#update)]
+
+#### AudioController Playback
+
+While the MonoGame simplified audio API allows sound effects to be played in a fire and forget manner, doing it this way does not work if you need to pause them because the game paused. Instead, we can add playback methods through the `AudioController` that can track the sound effect instances and pause them if needed, as well as checking the media player state before playing a song.
+
+Add the following methods:
+
+[!code-csharp[](./snippets/audiocontroller.cs#playback)]
+
+#### AudioController State Control
+
+The `AudioController` provides methods to control the state of audio playback including pausing and resuming audio as well as muting and unmuting.
+
+Add the following methods:
+
+[!code-csharp[](./snippets/audiocontroller.cs#state)]
+
+#### AudioController IDisposable Implementation
+
+Finally, the `AudioController` is required to implement the `IDisposable` interface, to complete this add the following methods:
+
+[!code-csharp[](./snippets/audiocontroller.cs#idisposable)]
+
+Games often use limited system resources like audio channels, when we are done with these resources we need to clean them up properly. In .NET, the standard way to handle resource cleanup is through the `IDisposable` interface.
+
+Think of `IDisposable` like a cleanup checklist that runs when you are finished with something:
+
+1. The interface provides a `Dispose` method that contains all cleanup logic.
+2. When called, `Dispose` releases any resources the class was using.
+3. Even if you forget to call `Dispose`, the finalizer acts as a backup cleanup mechanism.
+
+For our `AudioController`, implementing `IDisposable` means we can ensure all sound effect instances are properly stopped and disposed when our game ends, preventing resource leaks.
+
+> [!NOTE]
+> Fore more information on `IDisposable` and the `Dispose` method, check out the [Implementing a Dispose Method](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose) article on Microsoft Learn.
+
+## Implementing the AudioController Class
+
+Now that we have the audio controller class complete, we can update the game to use it. We will do this in two steps:
+
+1. First, update the `Core` class to add the `AudioController` globally.
+1. Update the `Game1` class to use the global audio controller from `Core`.
+
+### Updating the Core Class
+
+The `Core` class serves as our the base game class, so we will update it first to add and expose the `AudioController` globally. Open the `Core.cs` file in the *MonoGameLibrary* project and update it to the following:
+
+[!code-csharp[](./snippets/core.cs?highlight=6,50-53,112-113,116-122,129-130)]
+
+The key changes made here are:
+
+1. Added the `using MonoGameLibrary.Audio;` directive to access the `AudioController` class.
+2. Added a static `Audio` property to provide global access to the audio controller.
+3. Created the new audio controller instance in the `Initialize` method.
+4. Added an override for the `UnloadContent` method where we dispose of the audio controller.
+5. The audio controller is updated in the `Update` method.
+
+### Updating the Game1 Class
+
+Next, update the `Game1` class to use the audio controller for audio playback. Open `Game1.cs` and make the following updates:
+
+[!code-csharp[](./snippets/game1.cs?highlight=45-46,77-78,104-105,197-198,216-217,270-288)]
+
+> [!NOTE]
+> Note there were a lot of replacements in the `LoadContent` method, switching from loading and initializing the background Song and replacing it with a call to the new `AudioController` to do all the work managing the Song reference. Much cleaner.
+
+The key changes made here are:
+
+1. The `_themeSong` field is added to store a reference to the background song to play.
+2. In [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent), the background theme song is loaded using hte content manager.
+3. In [**Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize), the audio manager is used to play the background theme song.
+4. In [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)) the audio manager is used to play the bounce and collect sound effects.
+5. In `CheckKeyboardInput` the following checks were added
+ 1. If the M key on the keyboard is pressed, it will toggle mute for all audio.
+ 2. If the + key is pressed, the song and sound effect volumes are increased by `0.1f`.
+ 3. If the - key is pressed, the song and sound effect volumes are decreased by `0.1f`.
+
+Running the game now will produce the same result as the previous chapter, only now the lifetime of sound effects and the state management of audio is done through the new audio controller. You can also mute and unumte the audio with the M key and increase and decrease the volume using the + and - keys.
+
+|  |
+|:--------------------------------------------------------------------------------------:|
+| **Figure 15-1: Gameplay with audio.** |
+
+> [!NOTE]
+> You may note that while we added keybindings to change the audio settings, we did not add any bindings for the GamePad. This is simply becuase this is not normally how you would adjust these values on a console, on consoles you would have a settings/options screen to update them.
+>
+> Later in [Chapter 20: Implementing UI with GUM](../20_implementing_ui_with_gum/index.md) we will add an Options screen to adjust all the audio values for the game.
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+- Created a reusable `AudioController` class to centralize audio management.
+- Learned about proper resource management for audio using the `IDisposable` pattern.
+- Implemented tracking and cleanup of sound effect instances.
+- Added global volume control for both sound effects and music.
+- Created methods to toggle audio states (play/pause, mute/unmute).
+- Updated the `Core` class to provide global access to the audio controller.
+- Added keyboard controls to adjust volume and toggle mute state.
+
+The `AudioController` class you created is a significant improvement over directly using MonoGame's audio APIs. It handles common audio management tasks that would otherwise need to be implemented repeatedly in different parts of your game. By centralizing these functions, you make your code more maintainable and provide a consistent audio experience across your game.
+
+In the next chapter, we will start exploring fonts and adding text to the game.
diff --git a/articles/tutorials/building_2d_games/15_audio_controller/snippets/audiocontroller.cs b/articles/tutorials/building_2d_games/15_audio_controller/snippets/audiocontroller.cs
new file mode 100644
index 00000000..34b57261
--- /dev/null
+++ b/articles/tutorials/building_2d_games/15_audio_controller/snippets/audiocontroller.cs
@@ -0,0 +1,297 @@
+#region declaration
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Media;
+
+namespace MonoGameLibrary.Audio;
+
+public class AudioController : IDisposable
+{
+
+}
+#endregion
+{
+ #region properties
+ // Tracks sound effect instances created so they can be paused, unpaused, and/or disposed.
+ private readonly List _activeSoundEffectInstances;
+
+ // Tracks the volume for song playback when muting and unmuting.
+ private float _previousSongVolume;
+
+ // Tracks the volume for sound effect playback when muting and unmuting.
+ private float _previousSoundEffectVolume;
+
+ ///
+ /// Gets a value that indicates if audio is muted.
+ ///
+ public bool IsMuted { get; private set; }
+
+///
+ /// Gets or Sets the global volume of songs.
+ ///
+ ///
+ /// If IsMuted is true, the getter will always return back 0.0f and the
+ /// setter will ignore setting the volume.
+ ///
+ public float SongVolume
+ {
+ get
+ {
+ if(IsMuted)
+ {
+ return 0.0f;
+ }
+
+ return MediaPlayer.Volume;
+ }
+ set
+ {
+ if(IsMuted)
+ {
+ return;
+ }
+
+ MediaPlayer.Volume = Math.Clamp(value, 0.0f, 1.0f);
+ }
+ }
+
+ ///
+ /// Gets or Sets the global volume of sound effects.
+ ///
+ ///
+ /// If IsMuted is true, the getter will always return back 0.0f and the
+ /// setter will ignore setting the volume.
+ ///
+ public float SoundEffectVolume
+ {
+ get
+ {
+ if(IsMuted)
+ {
+ return 0.0f;
+ }
+
+ return SoundEffect.MasterVolume;
+ }
+ set
+ {
+ if(IsMuted)
+ {
+ return;
+ }
+
+ SoundEffect.MasterVolume = Math.Clamp(value, 0.0f, 1.0f);
+ }
+ }
+
+ ///
+ /// Gets a value that indicates if this audio controller has been disposed.
+ ///
+ public bool IsDisposed {get; private set; }
+ #endregion
+
+ #region ctors
+ ///
+ /// Creates a new audio controller instance.
+ ///
+ public AudioController()
+ {
+ _activeSoundEffectInstances = new List();
+ }
+
+ // Finalizer called when object is collected by the garbage collector
+ ~AudioController() => Dispose(false);
+ #endregion
+
+ #region update
+ ///
+ /// Updates this audio controller
+ ///
+ public void Update()
+ {
+ int index = 0;
+
+ while (index < _activeSoundEffectInstances.Count)
+ {
+ SoundEffectInstance instance = _activeSoundEffectInstances[index];
+
+ if (instance.State == SoundState.Stopped && !instance.IsDisposed)
+ {
+ instance.Dispose();
+ }
+
+ _activeSoundEffectInstances.RemoveAt(index);
+ }
+ }
+ #endregion
+
+ #region idisposable
+ ///
+ /// Disposes of this audio controller and cleans up resources.
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Disposes this audio controller and cleans up resources.
+ ///
+ /// Indicates whether managed resources should be disposed.
+ protected void Dispose(bool disposing)
+ {
+ if(IsDisposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ foreach (SoundEffectInstance soundEffectInstance in _activeSoundEffectInstances)
+ {
+ soundEffectInstance.Dispose();
+ }
+ _activeSoundEffectInstances.Clear();
+ }
+
+ IsDisposed = true;
+ }
+ #endregion
+
+ #region playback
+ ///
+ /// Plays the given sound effect.
+ ///
+ /// The sound effect to play.
+ /// The sound effect instance created by this method.
+ public SoundEffectInstance PlaySoundEffect(SoundEffect soundEffect)
+ {
+ return PlaySoundEffect(soundEffect, 1.0f, 1.0f, 0.0f, false);
+ }
+
+ ///
+ /// Plays the given sound effect with the specified properties.
+ ///
+ /// The sound effect to play.
+ /// The volume, ranging from 0.0 (silence) to 1.0 (full volume).
+ /// The pitch adjustment, ranging from -1.0 (down an octave) to 0.0 (no change) to 1.0 (up an octave).
+ /// The panning, ranging from -1.0 (left speaker) to 0.0 (centered), 1.0 (right speaker).
+ /// Whether the the sound effect should loop after playback.
+ /// The sound effect instance created by playing the sound effect.
+ /// The sound effect instance created by this method.
+ public SoundEffectInstance PlaySoundEffect(SoundEffect soundEffect, float volume, float pitch, float pan, bool isLooped)
+ {
+ // Create an instance from the sound effect given.
+ SoundEffectInstance soundEffectInstance = soundEffect.CreateInstance();
+
+ // Apply the volume, pitch, pan, and loop values specified.
+ soundEffectInstance.Volume = volume;
+ soundEffectInstance.Pitch = pitch;
+ soundEffectInstance.Pan = pan;
+ soundEffectInstance.IsLooped = isLooped;
+
+ // Tell the instance to play
+ soundEffectInstance.Play();
+
+ // Add it to the active instances for tracking
+ _activeSoundEffectInstances.Add(soundEffectInstance);
+
+ return soundEffectInstance;
+ }
+
+ ///
+ /// Plays the given song.
+ ///
+ /// The song to play.
+ /// Optionally specify if the song should repeat. Default is true.
+ public void PlaySong(Song song, bool isRepeating = true)
+ {
+ // Check if the media player is already playing, if so, stop it.
+ // If we do not stop it, this could cause issues on some platforms
+ if (MediaPlayer.State == MediaState.Playing)
+ {
+ MediaPlayer.Stop();
+ }
+
+ MediaPlayer.Play(song);
+ MediaPlayer.IsRepeating = isRepeating;
+ }
+ #endregion
+
+ #region state
+ ///
+ /// Pauses all audio.
+ ///
+ public void PauseAudio()
+ {
+ // Pause any active songs playing
+ MediaPlayer.Pause();
+
+ // Pause any active sound effects
+ foreach (SoundEffectInstance soundEffectInstance in _activeSoundEffectInstances)
+ {
+ soundEffectInstance.Pause();
+ }
+ }
+
+ ///
+ /// Resumes play of all previous paused audio.
+ ///
+ public void ResumeAudio()
+ {
+ // Resume paused music
+ MediaPlayer.Resume();
+
+ // Resume any active sound effects
+ foreach (SoundEffectInstance soundEffectInstance in _activeSoundEffectInstances)
+ {
+ soundEffectInstance.Resume();
+ }
+ }
+
+ ///
+ /// Mutes all audio.
+ ///
+ public void MuteAudio()
+ {
+ // Store the volume so they can be restored during ResumeAudio
+ _previousSongVolume = MediaPlayer.Volume;
+ _previousSoundEffectVolume = SoundEffect.MasterVolume;
+
+ // Set all volumes to 0
+ MediaPlayer.Volume = 0.0f;
+ SoundEffect.MasterVolume = 0.0f;
+
+ IsMuted = true;
+ }
+
+ ///
+ /// Unmutes all audio to the volume level prior to muting.
+ ///
+ public void UnmuteAudio()
+ {
+ // Restore the previous volume values
+ MediaPlayer.Volume = _previousSongVolume;
+ SoundEffect.MasterVolume = _previousSoundEffectVolume;
+
+ IsMuted = false;
+ }
+
+ ///
+ /// Toggles the current audio mute state.
+ ///
+ public void ToggleMute()
+ {
+ if (IsMuted)
+ {
+ UnmuteAudio();
+ }
+ else
+ {
+ MuteAudio();
+ }
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/15_audio_controller/snippets/core.cs b/articles/tutorials/building_2d_games/15_audio_controller/snippets/core.cs
new file mode 100644
index 00000000..592e1bd9
--- /dev/null
+++ b/articles/tutorials/building_2d_games/15_audio_controller/snippets/core.cs
@@ -0,0 +1,139 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary.Audio;
+using MonoGameLibrary.Input;
+
+namespace MonoGameLibrary;
+
+public class Core : Game
+{
+ internal static Core s_instance;
+
+ ///
+ /// Gets a reference to the Core instance.
+ ///
+ public static Core Instance => s_instance;
+
+ ///
+ /// Gets the graphics device manager to control the presentation of graphics.
+ ///
+ public static GraphicsDeviceManager Graphics { get; private set; }
+
+ ///
+ /// Gets the graphics device used to create graphical resources and perform primitive rendering.
+ ///
+ public static new GraphicsDevice GraphicsDevice { get; private set; }
+
+ ///
+ /// Gets the sprite batch used for all 2D rendering.
+ ///
+ public static SpriteBatch SpriteBatch { get; private set; }
+
+ ///
+ /// Gets the content manager used to load global assets.
+ ///
+ public static new ContentManager Content { get; private set; }
+
+ ///
+ /// Gets a reference to to the input management system.
+ ///
+ public static InputManager Input { get; private set; }
+
+ ///
+ /// Gets or Sets a value that indicates if the game should exit when the esc key on the keyboard is pressed.
+ ///
+ public static bool ExitOnEscape { get; set; }
+
+ ///
+ /// Gets a reference to the audio control system.
+ ///
+ public static AudioController Audio { get; private set; }
+
+ ///
+ /// Creates a new Core instance.
+ ///
+ /// The title to display in the title bar of the game window.
+ /// The initial width, in pixels, of the game window.
+ /// The initial height, in pixels, of the game window.
+ /// Indicates if the game should start in fullscreen mode.
+ public Core(string title, int width, int height, bool fullScreen)
+ {
+ // Ensure that multiple cores are not created.
+ if (s_instance != null)
+ {
+ throw new InvalidOperationException($"Only a single Core instance can be created");
+ }
+
+ // Store reference to engine for global member access.
+ s_instance = this;
+
+ // Create a new graphics device manager.
+ Graphics = new GraphicsDeviceManager(this);
+
+ // Set the graphics defaults
+ Graphics.PreferredBackBufferWidth = width;
+ Graphics.PreferredBackBufferHeight = height;
+ Graphics.IsFullScreen = fullScreen;
+
+ // Apply the graphic presentation changes
+ Graphics.ApplyChanges();
+
+ // Set the window title
+ Window.Title = title;
+
+ // Set the core's content manager to a reference of hte base Game's
+ // content manager.
+ Content = base.Content;
+
+ // Set the root directory for content
+ Content.RootDirectory = "Content";
+
+ // Mouse is visible by default
+ IsMouseVisible = true;
+ }
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+
+ // Set the core's graphics device to a reference of the base Game's
+ // graphics device.
+ GraphicsDevice = base.GraphicsDevice;
+
+ // Create the sprite batch instance.
+ SpriteBatch = new SpriteBatch(GraphicsDevice);
+
+ // Create a new input manager
+ Input = new InputManager();
+
+ // Create a new audio controller.
+ Audio = new AudioController();
+ }
+
+ protected override void UnloadContent()
+ {
+ // Dispose of the audio controller.
+ Audio.Dispose();
+
+ base.UnloadContent();
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ // Update the input manager.
+ Input.Update(gameTime);
+
+ // Update the audio controller.
+ Audio.Update();
+
+ if (ExitOnEscape && Input.Keyboard.IsKeyDown(Keys.Escape))
+ {
+ Exit();
+ }
+
+ base.Update(gameTime);
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/15_audio_controller/snippets/game1.cs b/articles/tutorials/building_2d_games/15_audio_controller/snippets/game1.cs
new file mode 100644
index 00000000..77c8d393
--- /dev/null
+++ b/articles/tutorials/building_2d_games/15_audio_controller/snippets/game1.cs
@@ -0,0 +1,366 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using Microsoft.Xna.Framework.Media;
+using MonoGameLibrary;
+using MonoGameLibrary.Graphics;
+using MonoGameLibrary.Input;
+
+namespace DungeonSlime;
+
+public class Game1 : Core
+{
+ // Defines the slime animated sprite.
+ private AnimatedSprite _slime;
+
+ // Defines the bat animated sprite.
+ private AnimatedSprite _bat;
+
+ // Tracks the position of the slime.
+ private Vector2 _slimePosition;
+
+ // Speed multiplier when moving.
+ private const float MOVEMENT_SPEED = 5.0f;
+
+ // Tracks the position of the bat.
+ private Vector2 _batPosition;
+
+ // Tracks the velocity of the bat.
+ private Vector2 _batVelocity;
+
+ // Defines the tilemap to draw.
+ private Tilemap _tilemap;
+
+ // Defines the bounds of the room that the slime and bat are contained within.
+ private Rectangle _roomBounds;
+
+ // The sound effect to play when the bat bounces off the edge of the screen.
+ private SoundEffect _bounceSoundEffect;
+
+ // The sound effect to play when the slime eats a bat.
+ private SoundEffect _collectSoundEffect;
+
+ // The background theme song
+ private Song _themeSong;
+
+ public Game1() : base("Dungeon Slime", 1280, 720, false)
+ {
+
+ }
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+
+ Rectangle screenBounds = GraphicsDevice.PresentationParameters.Bounds;
+
+ _roomBounds = new Rectangle(
+ (int)_tilemap.TileWidth,
+ (int)_tilemap.TileHeight,
+ screenBounds.Width - (int)_tilemap.TileWidth * 2,
+ screenBounds.Height - (int)_tilemap.TileHeight * 2
+ );
+
+ // Initial slime position will be the center tile of the tile map.
+ int centerRow = _tilemap.Rows / 2;
+ int centerColumn = _tilemap.Columns / 2;
+ _slimePosition = new Vector2(centerColumn * _tilemap.TileWidth, centerRow * _tilemap.TileHeight);
+
+ // Initial bat position will the in the top left corner of the room
+ _batPosition = new Vector2(_roomBounds.Left, _roomBounds.Top);
+
+ // Assign the initial random velocity to the bat.
+ AssignRandomBatVelocity();
+
+ // Start playing the background music
+ Audio.PlaySong(_themeSong);
+ }
+
+ protected override void LoadContent()
+ {
+ // Create the texture atlas from the XML configuration file
+ TextureAtlas atlas = TextureAtlas.FromFile(Content, "images/atlas-definition.xml");
+
+ // Create the slime animated sprite from the atlas.
+ _slime = atlas.CreateAnimatedSprite("slime-animation");
+ _slime.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+ _bat.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the tilemap from the XML configuration file.
+ _tilemap = Tilemap.FromFile(Content, "images/tilemap-definition.xml");
+ _tilemap.Scale = new Vector2(4.0f, 4.0f);
+
+ // Load the bounce sound effect
+ _bounceSoundEffect = Content.Load("audio/bounce");
+
+ // Load the collect sound effect
+ _collectSoundEffect = Content.Load("audio/collect");
+
+ // Load the background theme music
+ _themeSong = Content.Load("audio/theme");
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
+ Exit();
+
+ // Update the slime animated sprite.
+ _slime.Update(gameTime);
+
+ // Update the bat animated sprite.
+ _bat.Update(gameTime);
+
+ // Check for keyboard input and handle it.
+ CheckKeyboardInput();
+
+ // Check for gamepad input and handle it.
+ CheckGamePadInput();
+
+ // Creating a bounding circle for the slime
+ Circle slimeBounds = new Circle(
+ (int)(_slimePosition.X + (_slime.Width * 0.5f)),
+ (int)(_slimePosition.Y + (_slime.Height * 0.5f)),
+ (int)(_slime.Width * 0.5f)
+ );
+
+ // Use distance based checks to determine if the slime is within the
+ // bounds of the game screen, and if it is outside that screen edge,
+ // move it back inside.
+ if (slimeBounds.Left < _roomBounds.Left)
+ {
+ _slimePosition.X = _roomBounds.Left;
+ }
+ else if (slimeBounds.Right > _roomBounds.Right)
+ {
+ _slimePosition.X = _roomBounds.Right - _slime.Width;
+ }
+
+ if (slimeBounds.Top < _roomBounds.Top)
+ {
+ _slimePosition.Y = _roomBounds.Top;
+ }
+ else if (slimeBounds.Bottom > _roomBounds.Bottom)
+ {
+ _slimePosition.Y = _roomBounds.Bottom - _slime.Height;
+ }
+
+ // Calculate the new position of the bat based on the velocity
+ Vector2 newBatPosition = _batPosition + _batVelocity;
+
+ // Create a bounding circle for the bat
+ Circle batBounds = new Circle(
+ (int)(newBatPosition.X + (_bat.Width * 0.5f)),
+ (int)(newBatPosition.Y + (_bat.Height * 0.5f)),
+ (int)(_bat.Width * 0.5f)
+ );
+
+ Vector2 normal = Vector2.Zero;
+
+ // Use distance based checks to determine if the bat is within the
+ // bounds of the game screen, and if it is outside that screen edge,
+ // reflect it about the screen edge normal
+ if (batBounds.Left < _roomBounds.Left)
+ {
+ normal.X = Vector2.UnitX.X;
+ newBatPosition.X = _roomBounds.Left;
+ }
+ else if (batBounds.Right > _roomBounds.Right)
+ {
+ normal.X = -Vector2.UnitX.X;
+ newBatPosition.X = _roomBounds.Right - _bat.Width;
+ }
+
+ if (batBounds.Top < _roomBounds.Top)
+ {
+ normal.Y = Vector2.UnitY.Y;
+ newBatPosition.Y = _roomBounds.Top;
+ }
+ else if (batBounds.Bottom > _roomBounds.Bottom)
+ {
+ normal.Y = -Vector2.UnitY.Y;
+ newBatPosition.Y = _roomBounds.Bottom - _bat.Height;
+ }
+
+ // If the normal is anything but Vector2.Zero, this means the bat had
+ // moved outside the screen edge so we should reflect it about the
+ // normal.
+ if (normal != Vector2.Zero)
+ {
+ _batVelocity = Vector2.Reflect(_batVelocity, normal);
+
+ // Play the bounce sound effect
+ Audio.PlaySoundEffect(_bounceSoundEffect);
+ }
+
+ _batPosition = newBatPosition;
+
+ if (slimeBounds.Intersects(batBounds))
+ {
+ // Choose a random row and column based on the total number of each
+ int column = Random.Shared.Next(1, _tilemap.Columns - 1);
+ int row = Random.Shared.Next(1, _tilemap.Rows - 1);
+
+ // Change the bat position by setting the x and y values equal to
+ // the column and row multiplied by the width and height.
+ _batPosition = new Vector2(column * _bat.Width, row * _bat.Height);
+
+ // Assign a new random velocity to the bat
+ AssignRandomBatVelocity();
+
+ // Play the collect sound effect
+ Audio.PlaySoundEffect(_collectSoundEffect);
+ }
+
+ base.Update(gameTime);
+ }
+
+ private void AssignRandomBatVelocity()
+ {
+ // Generate a random angle
+ float angle = (float)(Random.Shared.NextDouble() * Math.PI * 2);
+
+ // Convert angle to a direction vector
+ float x = (float)Math.Cos(angle);
+ float y = (float)Math.Sin(angle);
+ Vector2 direction = new Vector2(x, y);
+
+ // Multiply the direction vector by the movement speed
+ _batVelocity = direction * MOVEMENT_SPEED;
+ }
+
+ private void CheckKeyboardInput()
+ {
+ // If the space key is held down, the movement speed increases by 1.5
+ float speed = MOVEMENT_SPEED;
+ if (Input.Keyboard.IsKeyDown(Keys.Space))
+ {
+ speed *= 1.5f;
+ }
+
+ // If the W or Up keys are down, move the slime up on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.W) || Input.Keyboard.IsKeyDown(Keys.Up))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // if the S or Down keys are down, move the slime down on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.S) || Input.Keyboard.IsKeyDown(Keys.Down))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If the A or Left keys are down, move the slime left on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.A) || Input.Keyboard.IsKeyDown(Keys.Left))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If the D or Right keys are down, move the slime right on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.D) || Input.Keyboard.IsKeyDown(Keys.Right))
+ {
+ _slimePosition.X += speed;
+ }
+
+ // If the M key is pressed, toggle mute state for audio.
+ if (Input.Keyboard.WasKeyJustPressed(Keys.M))
+ {
+ Audio.ToggleMute();
+ }
+
+ // If the + button is pressed, increase the volume.
+ if (Input.Keyboard.WasKeyJustPressed(Keys.OemPlus))
+ {
+ Audio.SongVolume += 0.1f;
+ Audio.SoundEffectVolume += 0.1f;
+ }
+
+ // If the - button was pressed, decrease the volume.
+ if (Input.Keyboard.WasKeyJustPressed(Keys.OemMinus))
+ {
+ Audio.SongVolume -= 0.1f;
+ Audio.SoundEffectVolume -= 0.1f;
+ }
+ }
+
+ private void CheckGamePadInput()
+ {
+ GamePadInfo gamePadOne = Input.GamePads[(int)PlayerIndex.One];
+
+ // If the A button is held down, the movement speed increases by 1.5
+ // and the gamepad vibrates as feedback to the player.
+ float speed = MOVEMENT_SPEED;
+ if (gamePadOne.IsButtonDown(Buttons.A))
+ {
+ speed *= 1.5f;
+ GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f);
+ }
+ else
+ {
+ GamePad.SetVibration(PlayerIndex.One, 0.0f, 0.0f);
+ }
+
+ // Check thumbstick first since it has priority over which gamepad input
+ // is movement. It has priority since the thumbstick values provide a
+ // more granular analog value that can be used for movement.
+ if (gamePadOne.LeftThumbStick != Vector2.Zero)
+ {
+ _slimePosition.X += gamePadOne.LeftThumbStick.X * speed;
+ _slimePosition.Y -= gamePadOne.LeftThumbStick.Y * speed;
+ }
+ else
+ {
+ // If DPadUp is down, move the slime up on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadUp))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // If DPadDown is down, move the slime down on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadDown))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If DPapLeft is down, move the slime left on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadLeft))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If DPadRight is down, move the slime right on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadRight))
+ {
+ _slimePosition.X += speed;
+ }
+ }
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
+
+ // Draw the tilemap
+ _tilemap.Draw(SpriteBatch);
+
+ // Draw the slime sprite.
+ _slime.Draw(SpriteBatch, _slimePosition);
+
+ // Draw the bat sprite.
+ _bat.Draw(SpriteBatch, _batPosition);
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/15_audio_controller/videos/gameplay.webm b/articles/tutorials/building_2d_games/15_audio_controller/videos/gameplay.webm
new file mode 100644
index 00000000..1c1981cb
Binary files /dev/null and b/articles/tutorials/building_2d_games/15_audio_controller/videos/gameplay.webm differ
diff --git a/articles/tutorials/building_2d_games/16_working_with_spritefonts/files/04B_30.ttf b/articles/tutorials/building_2d_games/16_working_with_spritefonts/files/04B_30.ttf
new file mode 100644
index 00000000..4b93740c
Binary files /dev/null and b/articles/tutorials/building_2d_games/16_working_with_spritefonts/files/04B_30.ttf differ
diff --git a/articles/tutorials/building_2d_games/16_working_with_spritefonts/images/font_added.png b/articles/tutorials/building_2d_games/16_working_with_spritefonts/images/font_added.png
new file mode 100644
index 00000000..cc9d7f7c
Binary files /dev/null and b/articles/tutorials/building_2d_games/16_working_with_spritefonts/images/font_added.png differ
diff --git a/articles/tutorials/building_2d_games/16_working_with_spritefonts/index.md b/articles/tutorials/building_2d_games/16_working_with_spritefonts/index.md
new file mode 100644
index 00000000..f13de652
--- /dev/null
+++ b/articles/tutorials/building_2d_games/16_working_with_spritefonts/index.md
@@ -0,0 +1,273 @@
+---
+title: "Chapter 16: Working with SpriteFonts"
+description: "Learn how to create and use SpriteFonts to render text in your MonoGame project, including loading custom fonts and controlling text appearance."
+---
+
+In [Chapter 06](../06_working_with_textures/index.md), you learned how to load and render textures to display sprites in your game. While images are essential for visual elements, most games also need text for things like scores, player instructions, dialogue, and UI elements. MonoGame provides the [**SpriteFont**](xref:Microsoft.Xna.Framework.Graphics.SpriteFont) class to handle text rendering, which works together with the familiar [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) we have been using for drawing textures.
+
+In this chapter, you will:
+
+- Learn how MonoGame handles text rendering with SpriteFonts.
+- Create `SpriteFont` description using the MGCB Editor.
+- Load custom fonts for use in your game.
+- Render text using various parameters to control appearance.
+- Implement text rendering in our game.
+
+We will first start by understanding how text rendering works in MonoGame.
+
+## Understanding SpriteFonts
+
+MonoGame processes fonts through the content pipeline to create a texture atlas of font characters. MonoGame uses the texture atlas approach rather than directly using system fonts for several important reasons:
+
+- **Cross-platform Compatibility**: System fonts cannot be guaranteed to exist on all platforms.
+- **Consistency**: Ensures that the text appears the same across all platforms.
+- **GPU Rendering**: Graphics cards do not understand font formats directly; they can only render textures.
+- **Performance**: Pre-rendering the glyphs to a texture atlas allow for faster rendering at runtime with no texture swapping.
+
+A [**SpriteFont**](xref:Microsoft.Xna.Framework.Graphics.SpriteFont) in MonoGame consists of:
+
+1. A texture atlas containing pre-rendered glyphs (characters).
+2. Data that tracks the position, size, and spacing of each character.
+3. Kerning information for adjusting spacing between specific character pairs.
+
+The texture atlas approach means fonts are rendered as sprites, using the same [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) system you learned about for drawing textures ([Chapter 7: Optimized Texture Rendering](../07_optimizing_texture_rendering/index.md)). When you draw text, MonoGame is actually drawing small portions of the texture atlas for each character assembled together to form complete words and sentences.
+
+## Creating a SpriteFont Description
+
+To use text in your game, you first need to create a SpriteFont Description file and process it through the Content Pipeline, thankfully the MGCB Editor makes this process straightforward.
+
+For example, in the MGCB Editor:
+
+1. Right-click the content project node where the SpriteFont Description will be created and choose *Add* > *New Item...*.
+2. Select `SpriteFont Description (.spritefont)` from the options.
+3. Specify a name for the SpriteFont Description file and click `Create`.
+
+This will create a default SpriteFont Description file that look something like this:
+
+[!code-xml[](./snippets/spritefont_description.spritefont)]
+
+When creating a SpriteFont Description for your game, you will need to make several important decisions about font selection, size, formatting, and licensing. The following sections will guide you through customizing the SpriteFont Description using these considerations.
+
+### Customizing the SpriteFont
+
+The SpriteFont Description file allows you to customize various aspects of how the font will be processed and appear in your game. Here are the key elements you can modify:
+
+#### FontName
+
+The `` element specifies which font to use. By default, it references "Arial". When a font name is specified just by name like this, it is required that the font be installed on the system where the content is built.
+
+> [!IMPORTANT]
+> MonoGame recommends changing the default Arial font if you are targeting any platforms other than Windows. Arial is a legacy from XNA and is only guaranteed to be available in Windows builds. As an alternative, MonoGame currently recommends using [Roboto](https://fonts.google.com/specimen/Roboto).
+
+Alternatively, for better portability across development environments, it is recommended instead to directly reference a TrueType (.ttf) or OpenType (.otf) font file. To do this
+
+1. Download or locate a TTF or OTF font file.
+2. Place it in the **same folder** as the `.spritefont` file.
+
+ > [!IMPORTANT]
+ > You place the font file in the **same folder** as the `.spritefont` file directly, not through the MGCB Editor.
+
+3. Update the `` element to include the exact filename with extension.
+
+> [!TIP]
+> Use fonts with permissive licenses (like [SIL Open Font License](https://openfontlicense.org/)) to ensure you can **legally** use them in your game.
+>
+> **Always check the license of any font you use!**
+
+#### Size
+
+The `` element controls the font size in points. While it might seem straightforward, font sizing requires consideration and can be dependent on several factors. When choosing a font size, consider:
+
+- **Resolution impact**: Fonts that look good at 1080p may appear too small at 4K or too large at 720p.
+- **Font style**: Pixel fonts look best with small sizes to preserve crispness.
+- **Use case**: Different UI elements may require different sizes for proper hierarchy.
+
+You may want to create multiple SpriteFont Description files for different use cases in your game such as:
+
+- A larger font for headings and titles.
+- A medium-sized font for standard UI elements.
+- A smaller font for detailed information.
+
+> [!TIP]
+> Creating multiple SpriteFont Description files, however, can remove some of the benefits of fonts being a texture atlas since you will now have multiple atlases for each size. You will also now have multiple assets to manage both as asset files and references in code.
+>
+> An alternative approach is to create a single SpriteFont Description with a larger than needed size font, then scale it down during runtime in the game. This approach allows you to maintain the single SpriteFont Description file and single texture atlas, however, the size of the texture atlas will now be larger.
+>
+> There are tradeoffs to each approach and you should choose the one that works best for your game.
+
+#### Spacing
+
+The `` element adjusts the space between characters. The default value of 0 uses the font's built-in spacing. Positive values increase spacing, while negative values (though rarely used) can decrease it.
+
+#### UseKerning
+
+The `` element determines whether to use kerning information from the font. Kerning adjusts the spacing between specific pairs of characters for more visually pleasing results. For most fonts, you will want to leave this as `true`.
+
+> [!NOTE]
+> While kerning typically improves text appearance, some fonts (including Arial) may not respond optimally to kerning adjustments. If you notice unusual character spacing with a particular font, try setting this value to `false`.
+
+#### Style
+
+The `
+
+
+
+ ~
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/center_example.cs b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/center_example.cs
new file mode 100644
index 00000000..ed01e160
--- /dev/null
+++ b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/center_example.cs
@@ -0,0 +1,27 @@
+// The text to draw.
+string message = "Hello, MonoGame!";
+
+// Measure the size of the message to get the text dimensions.
+Vector2 textSize = font.MeasureString(message);
+
+// Set the origin to the center of the text dimensions
+Vector2 origin = textSize * 0.5f;
+
+// Position will be the center of the screen
+Vector2 position = new Vector2(
+ GraphicsDevice.PresentationParameters.BackBufferWidth,
+ GraphicsDevice.PresentationParameters.BackBufferHeight
+) * 0.5f;
+
+// Draw centered text
+_spriteBatch.DrawString(
+ font, // font
+ message, // text
+ position, // position
+ Color.White, // color
+ 0.0f, // rotation
+ origin, // origin
+ 1.0f, // scale
+ SpriteEffects.None, // effects
+ 0.0f // layerDepth
+);
diff --git a/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/drawstring_basic.cs b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/drawstring_basic.cs
new file mode 100644
index 00000000..10296585
--- /dev/null
+++ b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/drawstring_basic.cs
@@ -0,0 +1,6 @@
+_spriteBatch.DrawString(
+ font, // font
+ "Hello, MonoGame!", // text
+ Vector2.Zero, // position
+ Color.White // color
+);
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/drawstring_full.cs b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/drawstring_full.cs
new file mode 100644
index 00000000..1fcd1150
--- /dev/null
+++ b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/drawstring_full.cs
@@ -0,0 +1,11 @@
+_spriteBatch.DrawString(
+ font, // font
+ "Hello, MonoGame!", // text
+ Vector2.Zero, // position
+ Color.White, // color
+ 0.0f, // rotation
+ Vector2.Zero, // origin
+ Vector2.One, // scale
+ SpriteEffects.None, // effects
+ 0.0f // layerDepth
+);
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/game1.cs b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/game1.cs
new file mode 100644
index 00000000..458f4a45
--- /dev/null
+++ b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/game1.cs
@@ -0,0 +1,406 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using Microsoft.Xna.Framework.Media;
+using MonoGameLibrary;
+using MonoGameLibrary.Graphics;
+using MonoGameLibrary.Input;
+
+namespace DungeonSlime;
+
+public class Game1 : Core
+{
+ // Defines the slime animated sprite.
+ private AnimatedSprite _slime;
+
+ // Defines the bat animated sprite.
+ private AnimatedSprite _bat;
+
+ // Tracks the position of the slime.
+ private Vector2 _slimePosition;
+
+ // Speed multiplier when moving.
+ private const float MOVEMENT_SPEED = 5.0f;
+
+ // Tracks the position of the bat.
+ private Vector2 _batPosition;
+
+ // Tracks the velocity of the bat.
+ private Vector2 _batVelocity;
+
+ // Defines the tilemap to draw.
+ private Tilemap _tilemap;
+
+ // Defines the bounds of the room that the slime and bat are contained within.
+ private Rectangle _roomBounds;
+
+ // The sound effect to play when the bat bounces off the edge of the screen.
+ private SoundEffect _bounceSoundEffect;
+
+ // The sound effect to play when the slime eats a bat.
+ private SoundEffect _collectSoundEffect;
+
+ // The background theme song
+ private Song _themeSong;
+
+ // The SpriteFont Description used to draw text
+ private SpriteFont _font;
+
+ // Tracks the players score.
+ private int _score;
+
+ // Defines the position to draw the score text at.
+ private Vector2 _scoreTextPosition;
+
+ // Defines the origin used when drawing the score text.
+ private Vector2 _scoreTextOrigin;
+
+
+ public Game1() : base("Dungeon Slime", 1280, 720, false)
+ {
+
+ }
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+
+ Rectangle screenBounds = GraphicsDevice.PresentationParameters.Bounds;
+
+ _roomBounds = new Rectangle(
+ (int)_tilemap.TileWidth,
+ (int)_tilemap.TileHeight,
+ screenBounds.Width - (int)_tilemap.TileWidth * 2,
+ screenBounds.Height - (int)_tilemap.TileHeight * 2
+ );
+
+ // Initial slime position will be the center tile of the tile map.
+ int centerRow = _tilemap.Rows / 2;
+ int centerColumn = _tilemap.Columns / 2;
+ _slimePosition = new Vector2(centerColumn * _tilemap.TileWidth, centerRow * _tilemap.TileHeight);
+
+ // Initial bat position will the in the top left corner of the room
+ _batPosition = new Vector2(_roomBounds.Left, _roomBounds.Top);
+
+ // Assign the initial random velocity to the bat.
+ AssignRandomBatVelocity();
+
+ // Start playing the background music
+ Audio.PlaySong(_themeSong);
+
+ // Set the position of the score text to align to the left edge of the
+ // room bounds, and to vertically be at the center of the first tile.
+ _scoreTextPosition = new Vector2(_roomBounds.Left, _tilemap.TileHeight * 0.5f);
+
+ // Set the origin of the text so it is left-centered.
+ float scoreTextYOrigin = _font.MeasureString("Score").Y * 0.5f;
+ _scoreTextOrigin = new Vector2(0, scoreTextYOrigin);
+ }
+
+ protected override void LoadContent()
+ {
+ // Create the texture atlas from the XML configuration file
+ TextureAtlas atlas = TextureAtlas.FromFile(Content, "images/atlas-definition.xml");
+
+ // Create the slime animated sprite from the atlas.
+ _slime = atlas.CreateAnimatedSprite("slime-animation");
+ _slime.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+ _bat.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the tilemap from the XML configuration file.
+ _tilemap = Tilemap.FromFile(Content, "images/tilemap-definition.xml");
+ _tilemap.Scale = new Vector2(4.0f, 4.0f);
+
+ // Load the bounce sound effect
+ _bounceSoundEffect = Content.Load("audio/bounce");
+
+ // Load the collect sound effect
+ _collectSoundEffect = Content.Load("audio/collect");
+
+ // Load the background theme music
+ _themeSong = Content.Load("audio/theme");
+
+ // Load the font
+ _font = Content.Load("fonts/04B_30");
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
+ Exit();
+
+ // Update the slime animated sprite.
+ _slime.Update(gameTime);
+
+ // Update the bat animated sprite.
+ _bat.Update(gameTime);
+
+ // Check for keyboard input and handle it.
+ CheckKeyboardInput();
+
+ // Check for gamepad input and handle it.
+ CheckGamePadInput();
+
+ // Creating a bounding circle for the slime
+ Circle slimeBounds = new Circle(
+ (int)(_slimePosition.X + (_slime.Width * 0.5f)),
+ (int)(_slimePosition.Y + (_slime.Height * 0.5f)),
+ (int)(_slime.Width * 0.5f)
+ );
+
+ // Use distance based checks to determine if the slime is within the
+ // bounds of the game screen, and if it is outside that screen edge,
+ // move it back inside.
+ if (slimeBounds.Left < _roomBounds.Left)
+ {
+ _slimePosition.X = _roomBounds.Left;
+ }
+ else if (slimeBounds.Right > _roomBounds.Right)
+ {
+ _slimePosition.X = _roomBounds.Right - _slime.Width;
+ }
+
+ if (slimeBounds.Top < _roomBounds.Top)
+ {
+ _slimePosition.Y = _roomBounds.Top;
+ }
+ else if (slimeBounds.Bottom > _roomBounds.Bottom)
+ {
+ _slimePosition.Y = _roomBounds.Bottom - _slime.Height;
+ }
+
+ // Calculate the new position of the bat based on the velocity
+ Vector2 newBatPosition = _batPosition + _batVelocity;
+
+ // Create a bounding circle for the bat
+ Circle batBounds = new Circle(
+ (int)(newBatPosition.X + (_bat.Width * 0.5f)),
+ (int)(newBatPosition.Y + (_bat.Height * 0.5f)),
+ (int)(_bat.Width * 0.5f)
+ );
+
+ Vector2 normal = Vector2.Zero;
+
+ // Use distance based checks to determine if the bat is within the
+ // bounds of the game screen, and if it is outside that screen edge,
+ // reflect it about the screen edge normal
+ if (batBounds.Left < _roomBounds.Left)
+ {
+ normal.X = Vector2.UnitX.X;
+ newBatPosition.X = _roomBounds.Left;
+ }
+ else if (batBounds.Right > _roomBounds.Right)
+ {
+ normal.X = -Vector2.UnitX.X;
+ newBatPosition.X = _roomBounds.Right - _bat.Width;
+ }
+
+ if (batBounds.Top < _roomBounds.Top)
+ {
+ normal.Y = Vector2.UnitY.Y;
+ newBatPosition.Y = _roomBounds.Top;
+ }
+ else if (batBounds.Bottom > _roomBounds.Bottom)
+ {
+ normal.Y = -Vector2.UnitY.Y;
+ newBatPosition.Y = _roomBounds.Bottom - _bat.Height;
+ }
+
+ // If the normal is anything but Vector2.Zero, this means the bat had
+ // moved outside the screen edge so we should reflect it about the
+ // normal.
+ if (normal != Vector2.Zero)
+ {
+ _batVelocity = Vector2.Reflect(_batVelocity, normal);
+
+ // Play the bounce sound effect
+ Audio.PlaySoundEffect(_bounceSoundEffect);
+ }
+
+ _batPosition = newBatPosition;
+
+ if (slimeBounds.Intersects(batBounds))
+ {
+ // Choose a random row and column based on the total number of each
+ int column = Random.Shared.Next(1, _tilemap.Columns - 1);
+ int row = Random.Shared.Next(1, _tilemap.Rows - 1);
+
+ // Change the bat position by setting the x and y values equal to
+ // the column and row multiplied by the width and height.
+ _batPosition = new Vector2(column * _bat.Width, row * _bat.Height);
+
+ // Assign a new random velocity to the bat
+ AssignRandomBatVelocity();
+
+ // Play the collect sound effect
+ Audio.PlaySoundEffect(_collectSoundEffect);
+
+ // Increase the player's score.
+ _score += 100;
+ }
+
+ base.Update(gameTime);
+ }
+
+ private void AssignRandomBatVelocity()
+ {
+ // Generate a random angle
+ float angle = (float)(Random.Shared.NextDouble() * Math.PI * 2);
+
+ // Convert angle to a direction vector
+ float x = (float)Math.Cos(angle);
+ float y = (float)Math.Sin(angle);
+ Vector2 direction = new Vector2(x, y);
+
+ // Multiply the direction vector by the movement speed
+ _batVelocity = direction * MOVEMENT_SPEED;
+ }
+
+ private void CheckKeyboardInput()
+ {
+ // If the space key is held down, the movement speed increases by 1.5
+ float speed = MOVEMENT_SPEED;
+ if (Input.Keyboard.IsKeyDown(Keys.Space))
+ {
+ speed *= 1.5f;
+ }
+
+ // If the W or Up keys are down, move the slime up on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.W) || Input.Keyboard.IsKeyDown(Keys.Up))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // if the S or Down keys are down, move the slime down on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.S) || Input.Keyboard.IsKeyDown(Keys.Down))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If the A or Left keys are down, move the slime left on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.A) || Input.Keyboard.IsKeyDown(Keys.Left))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If the D or Right keys are down, move the slime right on the screen.
+ if (Input.Keyboard.IsKeyDown(Keys.D) || Input.Keyboard.IsKeyDown(Keys.Right))
+ {
+ _slimePosition.X += speed;
+ }
+
+ // If the M key is pressed, toggle mute state for audio.
+ if (Input.Keyboard.WasKeyJustPressed(Keys.M))
+ {
+ Audio.ToggleMute();
+ }
+
+ // If the + button is pressed, increase the volume.
+ if (Input.Keyboard.WasKeyJustPressed(Keys.OemPlus))
+ {
+ Audio.SongVolume += 0.1f;
+ Audio.SoundEffectVolume += 0.1f;
+ }
+
+ // If the - button was pressed, decrease the volume.
+ if (Input.Keyboard.WasKeyJustPressed(Keys.OemMinus))
+ {
+ Audio.SongVolume -= 0.1f;
+ Audio.SoundEffectVolume -= 0.1f;
+ }
+ }
+
+ private void CheckGamePadInput()
+ {
+ GamePadInfo gamePadOne = Input.GamePads[(int)PlayerIndex.One];
+
+ // If the A button is held down, the movement speed increases by 1.5
+ // and the gamepad vibrates as feedback to the player.
+ float speed = MOVEMENT_SPEED;
+ if (gamePadOne.IsButtonDown(Buttons.A))
+ {
+ speed *= 1.5f;
+ GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f);
+ }
+ else
+ {
+ GamePad.SetVibration(PlayerIndex.One, 0.0f, 0.0f);
+ }
+
+ // Check thumbstick first since it has priority over which gamepad input
+ // is movement. It has priority since the thumbstick values provide a
+ // more granular analog value that can be used for movement.
+ if (gamePadOne.LeftThumbStick != Vector2.Zero)
+ {
+ _slimePosition.X += gamePadOne.LeftThumbStick.X * speed;
+ _slimePosition.Y -= gamePadOne.LeftThumbStick.Y * speed;
+ }
+ else
+ {
+ // If DPadUp is down, move the slime up on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadUp))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // If DPadDown is down, move the slime down on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadDown))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If DPapLeft is down, move the slime left on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadLeft))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If DPadRight is down, move the slime right on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadRight))
+ {
+ _slimePosition.X += speed;
+ }
+ }
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ // Clear the back buffer.
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
+
+ // Draw the tilemap
+ _tilemap.Draw(SpriteBatch);
+
+ // Draw the slime sprite.
+ _slime.Draw(SpriteBatch, _slimePosition);
+
+ // Draw the bat sprite.
+ _bat.Draw(SpriteBatch, _batPosition);
+
+ // Draw the score
+ SpriteBatch.DrawString(
+ _font, // spriteFont
+ $"Score: {_score}", // text
+ _scoreTextPosition, // position
+ Color.White, // color
+ 0.0f, // rotation
+ _scoreTextOrigin, // origin
+ 1.0f, // scale
+ SpriteEffects.None, // effects
+ 0.0f // layerDepth
+ );
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/measurestring.cs b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/measurestring.cs
new file mode 100644
index 00000000..1e7d5dec
--- /dev/null
+++ b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/measurestring.cs
@@ -0,0 +1,5 @@
+// The text to measure
+string message = "Hello, MonoGame!";
+
+// Measure the size of the message to get the text dimensions.
+Vector2 textSize = font.MeasureString(message);
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/spritefont.xml b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/spritefont.xml
new file mode 100644
index 00000000..4ba3ae6e
--- /dev/null
+++ b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/spritefont.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+ Arial
+
+
+ 16
+
+
+ 0
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+ ~
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/spritefont_description.spritefont b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/spritefont_description.spritefont
new file mode 100644
index 00000000..bd33ecf3
--- /dev/null
+++ b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/spritefont_description.spritefont
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+ Arial
+
+
+ 12
+
+
+ 0
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+ ~
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/16_working_with_spritefonts/videos/gameplay.webm b/articles/tutorials/building_2d_games/16_working_with_spritefonts/videos/gameplay.webm
new file mode 100644
index 00000000..5838dad3
Binary files /dev/null and b/articles/tutorials/building_2d_games/16_working_with_spritefonts/videos/gameplay.webm differ
diff --git a/articles/tutorials/building_2d_games/17_scenes/files/04B_30_5x.spritefont b/articles/tutorials/building_2d_games/17_scenes/files/04B_30_5x.spritefont
new file mode 100644
index 00000000..7d56d01f
--- /dev/null
+++ b/articles/tutorials/building_2d_games/17_scenes/files/04B_30_5x.spritefont
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+ 04B_30.ttf
+
+
+ 87.5
+
+
+ 0
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+ ~
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/17_scenes/images/font_added.png b/articles/tutorials/building_2d_games/17_scenes/images/font_added.png
new file mode 100644
index 00000000..ee964755
Binary files /dev/null and b/articles/tutorials/building_2d_games/17_scenes/images/font_added.png differ
diff --git a/articles/tutorials/building_2d_games/17_scenes/index.md b/articles/tutorials/building_2d_games/17_scenes/index.md
new file mode 100644
index 00000000..57658750
--- /dev/null
+++ b/articles/tutorials/building_2d_games/17_scenes/index.md
@@ -0,0 +1,400 @@
+---
+title: "Chapter 17: Scene Management"
+description: "Learn how to implement scene management to handle different game screens like menus, gameplay, and transitions between scenes."
+---
+
+In game development, a scene (sometimes called a screen or state) represents a distinct section of the game. Each scene typically has its own update and draw logic, as well as its own set of game objects. Common examples of scenes include title screens, menus, gameplay screens, game over screens, and more. Scenes help organize the game's code by separating different parts of the game into self-contained modules. This makes the code more manageable as the game grows in complexity and offers several advantages:
+
+1. **Improved organization**: Each scene contains only the code and assets relevant to that part of the game.
+2. **Memory management**: Load assets only when needed and unload them when leaving a scene.
+3. **Simplified state handling**: Each scene maintains its own state without affecting others.
+4. **Code reusability**: Create reusable scene templates for common game screens.
+
+Our game logic is currently contained within the single `Game1` class. Adding more screens to it would make the code harder to manage, so instead we need to start thinking about breaking it down into scenes.
+
+In this chapter, you will:
+
+- Learn the concept of scene management and its benefits
+- Create a base Scene class with a consistent lifecycle
+- Implement scene transitions using a manager
+- Create a title scene and gameplay scene for our game
+- Refactor our existing game to use the scene system
+
+We will being by first defining the lifecycle of a scene that will be followed.
+
+## Scene Lifecycle
+
+In Chapter 03, you learned the basic [lifecycle of the `Game` class](../03_the_game1_file/index.md#exploring-the-game1-class). To be consistent, we can borrow from this lifecycle and adapt it for our scenes. The order of operations for this lifecycle will be:
+
+1. A scene is created and set as the active scene.
+2. The first screen is made active and is initialized and content loaded.
+3. The active scene is updated and drawn each cycle.
+4. When transitioning to a new scene, or when the scene ends:
+ 1. The current scene is unloaded and disposed of.
+ 2. The new scene is initialized and content loaded.
+ 3. The new scene becomes the active scene and the cycle begins again until the game is told to exit.
+
+## The Scene Base Class
+
+The base `Scene` class is an abstract class for scenes that provides common functionality for all scenes. In our actual game, we will create concrete implementations of this, like a title scene.
+
+To get started, in the *MonoGameLibrary* project:
+
+1. Create a new folder named `Scenes`.
+2. Add a new class file named `Scene.cs` to the `Scenes` folder you just created.
+3. Add the following code as the initial structure for the class:
+
+ [!code-csharp[](./snippets/scene.cs#declaration)]
+
+ > [!NOTE]
+ > Just like with the `AudioController` in [Chapter 15](../15_audio_controller/index.md#audiocontroller-idisposable-implementation), each `Scene` implements the `IDisposable` interface. This provides a standardized in method to release the resources held by a scene when it is no longer needed.
+
+### Scene Properties
+
+Add the following properties to the `Scene` class:
+
+[!code-csharp[](./snippets/scene.cs#properties)]
+
+- The `Content` property is the scene's personal [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) that can be used to load scene specific content that will be unloaded when the scene ends. This helps manage memory usage by only loading what is needed for a specific scene.
+- The `IsDisposed` property is used to track if the scene has been disposed of since it implements the `IDisposable` interface.
+
+### Scene Constructor
+
+Add the following constructor and finalizer to the `Scene` class:
+
+[!code-csharp[](./snippets/scene.cs#ctors)]
+
+- The constructor initializes the scene's content manager and sets the root folder to match that of the base game's content manager.
+- The finalizer is called by the garbage collector automatically when a scene object is collected which just calls the `Dispose` method to ensure resources are disposed of properly.
+
+### Scene Methods
+
+Add the following methods to the `Scene` class:
+
+[!code-csharp[](./snippets/scene.cs#methods)]
+
+These methods are setup similar to how the `Game` class works to keep the workflow consistent:
+
+- `Initialize` is called only once when the scene becomes the active scene. It can be overridden by the derived class to provide scene specific initialization logic. It also calls the `LoadContent` method the same way the `Game` class is done for consistency.
+- `LoadContent` is called only once, at the end of the `Initialize` method. It can be overridden by the derived class to load scene specific content.
+- `UnloadContent` is called only once when a scene is ending due to a transition to a new scene. It can be overridden by the derived class to perform unloading of any scene specific content.
+- `Update` is called once at the start of every game cycle. It can be overridden to provide the update logic for the scene.
+- `Draw` is called once every game cycle, directly after `Update`. It can be overridden to provide the draw logic for the scene.
+
+#### IDisposable Implementation
+
+Add the following methods to the `Scene` class to complete the implementation of the `IDisposable` interface:
+
+[!code-csharp[](./snippets/scene.cs#disposable)]
+
+This completes our Base scene implementation that we will use to create actual scenes from in our project, next we need a manager that will organise the screens for use in the game.
+
+## Scene Management
+
+With the base `Scene` class defined, the `Core` class needs to be updated to handle management of the scenes, including update, drawing, and changing scenes. Open the `Core.cs` file in the *MonoGameLibrary* project and make the following changes:
+
+[!code-csharp[](./snippets/core.cs?highlight=8,21-25,144-155,160-169,171-179,181-205)]
+
+The key changes here are:
+
+1. The `using MonoGameLibrary.Scenes;` using directive was added so we have access to the `Scene` class.
+2. The fields `_activeScene` and `_nextScene` were added to track which scene is currently active and which scene, if any, to switch to.
+3. In `Update`:
+ 1. A check is made to see if there is a next scene, and if so, `TransitionScene` is called to gracefully switch from the current to the next.
+ 2. A check is made to see if there is an active scene, and if so, updates it.
+4. An override for the `Draw` method was added where a check is made to see if there is an active scene, and if so, draws it.
+5. The `ChangeScene` method was added which can be called when we want to tell the core to change from one scene to another one.
+6. The `TransitionScene` method was add that gracefully transitions from the current scene to the next scene by
+ 1. A check is made to see if there is an active scene, and if so, disposes it.
+ 2. The garbage collector is told to perform a collection to clear out memory from the disposal of the current scene.
+ 3. The next scene is set as the current scene.
+ 4. A check is made to see if there is now a current scene, and if so, initializes it.
+
+> [!TIP]
+> Notice that we use a two-step process for scene transitions with separate `_activeScene` and `_nextScene` fields. This design allows the current scene to complete its update/draw cycle before the transition occurs, preventing potential issues that could arise from changing scenes in the middle of processing. The actual transition happens at a controlled point in the game loop, ensuring clean disposal of the old scene before initializing the new one.
+
+## Updating the Game
+
+With the scene architecture in place, the game can now be updated so that it is broken down into scenes. We will create two scenes; a title scene and a gameplay scene. First, however, we need to add an additional SpriteFont Description that will be used during the title scene to display the title of the game. Open the *Content.mgcb* content project file in the MGCB Editor and perform the following:
+
+1. Right-click the `fonts` folder and choose `Add > New Item...`.
+2. Select `SpriteFont Description (.spritefont)` from the options.
+3. Name the file `04B_30_5x` and click `Create`.
+
+|  |
+| :-------------------------------------------------------------------------------------------------: |
+| **Figure 17-1: The *04B_30_5x.spritefont* file created in the MGCB Editor** |
+
+Next, open the *04B_30_5x.spritefont* file in your code editor and make the following changes:
+
+[!code-xml[](./snippets/04B_30_5x.spritefont?highlight=4-5)]
+
+### The Title Scene
+
+The title scene serves as the game's initial starting point; the first impression the player gets when they launch the game. For our game, the title scene will display the text for the title of the game and a prompt to inform the player what action to take to start the game. We will use a simple trick for the title text in order to draw it with a drop shadow to add a bit of visual flair.
+
+> [!NOTE]
+> As the following screens are specific to our game and are not reusable bits, these will be added to your game project.
+>
+> Although, if you do end up making screens that are completely reusable, there is nothing wrong with putting them in your Game Library, it is completely up to you.
+
+To get started, first:
+
+1. In your Game project, create a new folder named `Scenes`. We will put all of our game specific scenes here.
+2. Add a new class file named `TitleScene.cs` to the `Scenes` folder you just created.
+3. Add the following code as the initial structure for the class.
+
+ [!code-csharp[](./snippets/titlescene.cs#declaration)]
+
+#### Title Scene Fields
+
+Add the following fields to the `TitleScene` class:
+
+[!code-csharp[](./snippets/titlescene.cs#fields)]
+
+- Three `const` fields (`DUNGEON_TEXT`, `SLIME_TEXT`, `PRESS_ENTER_TEXT`) are added for the text that will be displayed on the title screen.
+- The `_font` field stores a reference to the [**SpriteFont**](xref:Microsoft.Xna.Framework.Graphics.SpriteFont) we will use to draw the press enter prompt with.
+- The `_font3x` field stores a reference to the [**SpriteFont**](xref:Microsoft.Xna.Framework.Graphics.SpriteFont) we will use to draw the dungeon and slime text with that will make up the title of the game.
+- The `_dungeonTextPos` and `_dungeonTextOrigin` fields store the position and origin we will use to draw the "Dungeon" text at.
+- The `_slimeTextPos` and `_slimeTextOrigin` fields store the position and origin we will draw the "Slime" text at.
+- The `_pressEnterPos` and `_pressEnterOrigin` fields store the position and origin we will draw the "Press Enter To Start" text at.
+
+#### Title Scene Methods
+
+The `TitleScene` class will override the various methods from the base `Scene` class that it derives from to provide the initialization, content loading, update, and drawing logic.
+
+##### Title Scene Initialize
+
+Add the following override for the `Initialize` method to the `TitleScene` class:
+
+[!code-csharp[](./snippets/titlescene.cs#initialize)]
+
+- We set the `Core.ExitOnEscape` to true to allow players to exit the game when on the title screen by pressing the escape key.
+- The position and origin for the "Dungeon", "Slime", and "Press Enter To Start" texts are set.
+
+> [!NOTE]
+> You can see here we are using the [**MeasureString**](xref:Microsoft.Xna.Framework.Graphics.SpriteFont.MeasureString(System.String)) method for the font to work out how long the text to draw is, we then multiply this by `0.5` to work out the middle of the text so that we can properly set the origin of the text to its middle.
+
+##### Title Scene LoadContent
+
+Add the following override for the `LoadContent` method to the `TitleScene` class:
+
+[!code-csharp[](./snippets/titlescene.cs#loadcontent)]
+
+- The [**SpriteFont**](xref:Microsoft.Xna.Framework.Graphics.SpriteFont) used to draw the "Press Enter To Start" text is loaded using the global content manager.
+- The [**SpriteFont**](xref:Microsoft.Xna.Framework.Graphics.SpriteFont) used to draw the "Dungeon" and "Slime" text is loaded using the scene's content manager.
+
+> [!TIP]
+> Recall from [Chapter 05](../05_content_pipeline/index.md#contentmanager-methods) that when a [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) loads an asset for the first time, it caches it internally and the subsequent calls to load that asset will return the cached one instead of performing another disk read.
+>
+> By using a global content manager here to load assets that are used in multiple scenes, when they loaded in a different scene later, the cached version is returned instead of having to do another disk read, making the content loading more efficient.
+
+##### Title Scene Update
+
+Add the following override for the `Update` method to the `TitleScene` class:
+
+[!code-csharp[](./snippets/titlescene.cs#update)]
+
+- A check is made to see if the enter key is pressed, and if so, the `Core` is told to change to the game scene.
+
+> [!NOTE]
+> Your editor might show an error here since we have not created the `GameScene` class yet. We will create it in a moment after finishing the title scene.
+
+> [!TIP]
+> You will also notice the Title screen is only checking if the player hits the enter key to start the game, but we are not checking if they hit escape to quit the game, that is because it is already handled in the `Core` class `Update` method, if `ExitOnEscape` is true (as it is here), the game will automatically exit.
+
+##### Title Scene Draw
+
+For the final act, add the following override for the `Draw` method to the `TitleScene` class:
+
+[!code-csharp[](./snippets/titlescene.cs#draw)]
+
+- The back buffer is cleared.
+- A `dropShadowColor` is created which is the color black with half transparency.
+- The "Dungeon" text is drawn, first 10px down and to the left of the actual position using the drop shadow color, then again at its normal position overtop. Layering this way creates the drop shadow effect.
+- The "Slime" text is drawn, again offset from its position first using the drop shadow color and then drawn again at its normal position overtop.
+- Finally, the "Press Enter To Start" text is drawn.
+
+With our Title screen in place, it is time to get started with the Game Scene, lets play.
+
+### The Game Scene
+
+The Game Scene will contain our actual gameplay logic. This scene will handle updating and rendering the slime that the player controls, the bat the slime can eat, collision detection, score tracking, and input handling. Most of this logic has already been implemented in our `Game1` class in previous chapters, but now we will move it into a dedicated scene class. In the *Scenes* folder:
+
+1. Add a new class file named `GameScene.cs` in the Game projects `Scenes` folder.
+2. Add the following code as the initial structure for the class:
+
+ [!code-csharp[](./snippets/gamescene.cs#declaration)]
+
+The following code is effectively replacing the code we have already written in the original `Game1.cs` class, so it should look very familiar. Once complete, we can return to `Game1` and clear out all the redundant code because it is all nicely tidied up in the new `GameScene` class.
+
+#### Game Scene Fields
+
+Add the following fields to the `GameScene` class:
+
+[!code-csharp[](./snippets/gamescene.cs#fields)]
+
+- The `_slime` and `_bat` fields store the animated sprites for the player controlled slime and the bat.
+- The `_slimePosition` and `_batPosition` fields track the current position of the slime and bat.
+- The `MOVEMENT_SPEED` constant defines the base movement speed for both the slime and bat.
+- The `_batVelocity` field tracks the current velocity of the bat as it moves around the screen.
+- The `_tilemap` field stores the tilemap that we will load and draw for the level background environment.
+- The `_roomBounds` field defines a rectangular boundary that represents the boundary of the room that the slime and bat stays within.
+- The `_bounceSoundEffect` and `_collectSoundEffect` fields store the sound effects to play when the bat bounces off a screen edge or is eaten by the slime.
+- The `_font` field stores the font used to display the player's score.
+- The `_score` field tracks the player's current score, which increases when the slime eats a bat.
+- The `_scoreTextPosition` and `_scoreTextOrigin` defines the position and origin to use when drawing the score text.
+
+#### Game Scene Methods
+
+The `GameScene` class will override the various methods from the base `Scene` class that it derives from to provide the initialization, content loading, update, and drawing logic.
+
+##### Game Scene Initialize
+
+Add the following override for the `Initialize` method to the `GameScene` class:
+
+[!code-csharp[](./snippets/gamescene.cs#initialize)]
+
+- We set `Core.ExitOnEscape` to false because in the gameplay scene, we want to handle the escape key differently; instead of exiting the game, it will return to the title screen.
+- The room bounds is calculated using the bounds of the screen and adjusting that so that it shrinks by one tile width and height on each edge, which will match with the tilemap wall boundary.
+- The slime's initial position is set to be the center tile by calculating the center row and column.
+- The bat's initial position is placed at the top left of the room bounds.
+- The position and origin of the score text is precalculated. The height of the text is measured to properly calculate the center origin for vertical positioning.
+- The `AssignRandomBatVelocity` method is called to give the bat its initial velocity.
+
+##### Game Scene LoadContent
+
+Add the following override for the `LoadContent` method to the `GameScene` class:
+
+[!code-csharp[](./snippets/gamescene.cs#loadcontent)]
+
+- The texture atlas is loaded using the global content manager, and the slime and bat animated sprites are created from it.
+- The tilemap is loaded using the scene's content manager since they are specific to the gameplay scene.
+- The sound effects are loaded using the scene's content manager since they are specific to the gameplay scene.
+- The font is loaded using the global content manager since it is used in multiple scenes.
+
+> [!TIP]
+> Notice how we are following a consistent pattern across scenes: global assets are loaded with `Core.Instance.Content` while scene-specific assets are loaded with the scene's `Content` property.
+
+##### Game Scene Update
+
+Add the following override for the `Update` method to the `GameScene` class:
+
+[!code-csharp[](./snippets/gamescene.cs#update)]
+
+- The animated sprites for the slime and bat are updated.
+- Input from keyboard and gamepad is checked with dedicated methods `CheckKeyboardInput` and `CheckGamePadInput`.
+- Collision detection is performed to:
+ - Keep the slime within the room bounds.
+ - Make the bat bounce off edges of the room bounds.
+ - Detect when the slime eats the bat.
+- When the slime eats the bat, the bat respawns in a random location within the room bounds, given a new velocity, the collect sound is played, and the score is increased.
+
+##### Game Scene Helper Methods
+
+Next, add these helper methods to the `GameScene` class:
+
+[!code-csharp[](./snippets/gamescene.cs#helpers)]
+
+- `AssignRandomBatVelocity`: Calculates a random direction and applies it to the bat's velocity.
+- `CheckKeyboardInput`: Handles keyboard controls for moving the slime, toggling audio settings, and returning to the title screen.
+- `CheckGamePadInput`: Handles gamepad controls for moving the slime.
+
+##### Game Scene Draw
+
+Finally, add the following override for the `Draw` method to the `GameScene` class:
+
+[!code-csharp[](./snippets/gamescene.cs#draw)]
+
+- The back buffer is cleared.
+- The tilemap is drawn.
+- The slime and bat animated sprites are drawn at their current positions.
+- The player's score is drawn at using its precalculated position and origin so that it is in the top left of the room bounds centered on the wall sprite.
+
+This concludes the `GameScene` class. With all the logic for our actual gameplay now housed in a single place, we can clean up our project to use it.
+
+### Updating the Game1 Class
+
+With our scene system and scene classes in place, we can now simplify our main `Game1` class to just initialize the game and start with the title scene. Open the `Game1.cs` file and replace its content with the following:
+
+[!code-csharp[](./snippets/game1.cs)]
+
+> [!NOTE]
+> Feel free to check your homework and compare the original `Game1` class with the updated version, as well as checking the `GameScene` class did not lose any functionality (it has not, but you have to be sure!). Refactoring code to be cleaner and more organised is a careful task.
+
+The `Game1` class is now much simpler as most of the game logic has been moved to the appropriate scene classes.
+
+The updates include:
+
+1. Sets up the game window with the constructor parameters.
+2. Overrides the `Initialize` method to set the title scene as the starting scene.
+3. Overrides the `LoadContent` method to load the background theme song and start playing it.
+
+Running the game now, we can see that once the game screen comes up, the title scene is displayed with the animated slime and the press enter prompt. The background music starts playing on this scene as well. Pressing enter from here will switch to the game scene where the game starts and we can play the game implemented thus far.
+
+|  |
+| :------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 17-2: The game launching with the title screen first, then transitioning to the game play screen when enter is pressed** |
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+- Learned about scene management and why it is important for organizing game code.
+- Created an abstract `Scene` base class that provides a consistent lifecycle similar to the MonoGame [**Game**](xref:Microsoft.Xna.Framework.Game) class.
+- Implemented the `IDisposable` interface to properly handle resource cleanup.
+- Extended the `Core` class to handle scene transitions and management.
+- Created a `TitleScene` for the main menu with text prompts and animations.
+- Created a `GameScene` that encapsulates the gameplay mechanics.
+- Refactored the main `Game1` class to be much simpler by using the scene system.
+
+The approach we have taken follows a common pattern in game development, where each scene has control over its own lifecycle and resources. This pattern simplify state management by isolating different game states from one another. As your game grows in complexity, you could easily extend this system to include additional scenes like a pause menu or a game over screen.
+
+In the next chapter, we will explore [**RenderTarget2D**](xref:Microsoft.Xna.Framework.Graphics.RenderTarget2D) and how we can use it to add different types of transitions when switching scenes.
+
+## Test Your Knowledge
+
+1. What are the main benefits of implementing a scene management system in a game?
+
+ :::question-answer
+ The main benefits include:
+
+ - Improved organization by separating different parts of the game into self-contained modules.
+ - Better memory management by loading assets only when needed and unloading them when leaving a scene.
+ - Simplified state handling as each scene maintains its own state without affecting others.
+ - Increased code reusability through the ability to create reusable scene templates.
+ :::
+
+2. How does the scene lifecycle in our implementation mirror the MonoGame Game class lifecycle?
+
+ :::question-answer
+ The scene lifecycle mirrors the MonoGame Game class lifecycle by implementing similar methods in the same order:
+
+ - `Initialize` is called once when the scene becomes active.
+ - `LoadContent` is called at the end of the `Initialize` method.
+ - `Update` is called every frame to update game logic.
+ - `Draw` is called every frame to render the scene.
+ - `UnloadContent` is called when transitioning away from the scene.
+ :::
+
+3. What is the purpose of having a separate [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) for each scene?
+
+ :::question-answer
+ Having a separate [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) for each scene:
+
+ - Allows scene-specific content to be automatically unloaded when the scene is disposed.
+ - Provides better organization of which assets belong to which scenes.
+ - Improves memory efficiency by only loading assets that are currently needed.
+ - Makes it clear which assets are meant to be used globally versus locally to a scene.
+ :::
+
+4. When implementing scene transitions, why do we use a two-step process with `_nextScene` and `_activeScene`?
+
+ :::question-answer
+ The two-step process with `_nextScene` and `_activeScene` is used because:
+
+ - It allows the current scene to complete its update/draw cycle before the transition occurs.
+ - It provides a clean way to handle the disposal of the current scene before initializing the new one.
+ - It ensures that scene transitions happen at a safe point in the game loop.
+ - It prevents potential issues that could occur from immediately changing scenes in the middle of an update or draw operation.
+ :::
diff --git a/articles/tutorials/building_2d_games/17_scenes/snippets/04B_30_5x.spritefont b/articles/tutorials/building_2d_games/17_scenes/snippets/04B_30_5x.spritefont
new file mode 100644
index 00000000..dd239a53
--- /dev/null
+++ b/articles/tutorials/building_2d_games/17_scenes/snippets/04B_30_5x.spritefont
@@ -0,0 +1,16 @@
+
+
+
+ 04B_30.ttf
+ 87.5
+ 0
+ true
+
+
+
+
+ ~
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/17_scenes/snippets/core.cs b/articles/tutorials/building_2d_games/17_scenes/snippets/core.cs
new file mode 100644
index 00000000..a9eda94e
--- /dev/null
+++ b/articles/tutorials/building_2d_games/17_scenes/snippets/core.cs
@@ -0,0 +1,206 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary.Audio;
+using MonoGameLibrary.Input;
+using MonoGameLibrary.Scenes;
+
+namespace MonoGameLibrary;
+
+public class Core : Game
+{
+ internal static Core s_instance;
+
+ ///
+ /// Gets a reference to the Core instance.
+ ///
+ public static Core Instance => s_instance;
+
+ // The scene that is currently active.
+ private static Scene s_activeScene;
+
+ // The next scene to switch to, if there is one.
+ private static Scene s_nextScene;
+
+ ///
+ /// Gets the graphics device manager to control the presentation of graphics.
+ ///
+ public static GraphicsDeviceManager Graphics { get; private set; }
+
+ ///
+ /// Gets the graphics device used to create graphical resources and perform primitive rendering.
+ ///
+ public static new GraphicsDevice GraphicsDevice { get; private set; }
+
+ ///
+ /// Gets the sprite batch used for all 2D rendering.
+ ///
+ public static SpriteBatch SpriteBatch { get; private set; }
+
+ ///
+ /// Gets the content manager used to load global assets.
+ ///
+ public static new ContentManager Content { get; private set; }
+
+ ///
+ /// Gets a reference to to the input management system.
+ ///
+ public static InputManager Input { get; private set; }
+
+ ///
+ /// Gets or Sets a value that indicates if the game should exit when the esc key on the keyboard is pressed.
+ ///
+ public static bool ExitOnEscape { get; set; }
+
+ ///
+ /// Gets a reference to the audio control system.
+ ///
+ public static AudioController Audio { get; private set; }
+
+ ///
+ /// Creates a new Core instance.
+ ///
+ /// The title to display in the title bar of the game window.
+ /// The initial width, in pixels, of the game window.
+ /// The initial height, in pixels, of the game window.
+ /// Indicates if the game should start in fullscreen mode.
+ public Core(string title, int width, int height, bool fullScreen)
+ {
+ // Ensure that multiple cores are not created.
+ if (s_instance != null)
+ {
+ throw new InvalidOperationException($"Only a single Core instance can be created");
+ }
+
+ // Store reference to engine for global member access.
+ s_instance = this;
+
+ // Create a new graphics device manager.
+ Graphics = new GraphicsDeviceManager(this);
+
+ // Set the graphics defaults
+ Graphics.PreferredBackBufferWidth = width;
+ Graphics.PreferredBackBufferHeight = height;
+ Graphics.IsFullScreen = fullScreen;
+
+ // Apply the graphic presentation changes
+ Graphics.ApplyChanges();
+
+ // Set the window title
+ Window.Title = title;
+
+ // Set the core's content manager to a reference of hte base Game's
+ // content manager.
+ Content = base.Content;
+
+ // Set the root directory for content
+ Content.RootDirectory = "Content";
+
+ // Mouse is visible by default
+ IsMouseVisible = true;
+ }
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+
+ // Set the core's graphics device to a reference of the base Game's
+ // graphics device.
+ GraphicsDevice = base.GraphicsDevice;
+
+ // Create the sprite batch instance.
+ SpriteBatch = new SpriteBatch(GraphicsDevice);
+
+ // Create a new input manager
+ Input = new InputManager();
+
+ // Create a new audio controller.
+ Audio = new AudioController();
+ }
+
+ protected override void UnloadContent()
+ {
+ // Dispose of the audio controller.
+ Audio.Dispose();
+
+ base.UnloadContent();
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ // Update the input manager.
+ Input.Update(gameTime);
+
+ // Update the audio controller.
+ Audio.Update();
+
+ if (ExitOnEscape && Input.Keyboard.WasKeyJustPressed(Keys.Escape))
+ {
+ Exit();
+ }
+
+ // if there is a next scene waiting to be switch to, then transition
+ // to that scene
+ if (s_nextScene != null)
+ {
+ TransitionScene();
+ }
+
+ // If there is an active scene, update it.
+ if (s_activeScene != null)
+ {
+ s_activeScene.Update(gameTime);
+ }
+
+ base.Update(gameTime);
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ // If there is an active scene, draw it.
+ if (s_activeScene != null)
+ {
+ s_activeScene.Draw(gameTime);
+ }
+
+ base.Draw(gameTime);
+ }
+
+ public static void ChangeScene(Scene next)
+ {
+ // Only set the next scene value if it is not the same
+ // instance as the currently active scene.
+ if (s_activeScene != next)
+ {
+ s_nextScene = next;
+ }
+ }
+
+ private static void TransitionScene()
+ {
+ // If there is an active scene, dispose of it
+ if (s_activeScene != null)
+ {
+ s_activeScene.Dispose();
+ }
+
+ // Force the garbage collector to collect to ensure memory is cleared
+ GC.Collect();
+
+ // Change the currently active scene to the new scene
+ s_activeScene = s_nextScene;
+
+ // Null out the next scene value so it does not trigger a change over and over.
+ s_nextScene = null;
+
+ // If the active scene now is not null, initialize it.
+ // Remember, just like with Game, the Initialize call also calls the
+ // Scene.LoadContent
+ if (s_activeScene != null)
+ {
+ s_activeScene.Initialize();
+ }
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/17_scenes/snippets/game1.cs b/articles/tutorials/building_2d_games/17_scenes/snippets/game1.cs
new file mode 100644
index 00000000..2b8fffb4
--- /dev/null
+++ b/articles/tutorials/building_2d_games/17_scenes/snippets/game1.cs
@@ -0,0 +1,33 @@
+using DungeonSlime.Scenes;
+using Microsoft.Xna.Framework.Media;
+using MonoGameLibrary;
+
+namespace DungeonSlime;
+
+public class Game1 : Core
+{
+ // The background theme song
+ private Song _themeSong;
+
+ public Game1() : base("Dungeon Slime", 1280, 720, false)
+ {
+
+ }
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+
+ // Start playing the background music
+ Audio.PlaySong(_themeSong);
+
+ // Start the game with the title scene.
+ ChangeScene(new TitleScene());
+ }
+
+ protected override void LoadContent()
+ {
+ // Load the background theme music
+ _themeSong = Content.Load("audio/theme");
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/17_scenes/snippets/gamescene.cs b/articles/tutorials/building_2d_games/17_scenes/snippets/gamescene.cs
new file mode 100644
index 00000000..930a4c05
--- /dev/null
+++ b/articles/tutorials/building_2d_games/17_scenes/snippets/gamescene.cs
@@ -0,0 +1,416 @@
+#region declaration
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary;
+using MonoGameLibrary.Graphics;
+using MonoGameLibrary.Input;
+using MonoGameLibrary.Scenes;
+
+namespace DungeonSlime.Scenes;
+
+public class GameScene : Scene
+{
+
+}
+#endregion
+{
+ #region fields
+ // Defines the slime animated sprite.
+ private AnimatedSprite _slime;
+
+ // Defines the bat animated sprite.
+ private AnimatedSprite _bat;
+
+ // Tracks the position of the slime.
+ private Vector2 _slimePosition;
+
+ // Speed multiplier when moving.
+ private const float MOVEMENT_SPEED = 5.0f;
+
+ // Tracks the position of the bat.
+ private Vector2 _batPosition;
+
+ // Tracks the velocity of the bat.
+ private Vector2 _batVelocity;
+
+ // Defines the tilemap to draw.
+ private Tilemap _tilemap;
+
+ // Defines the bounds of the room that the slime and bat are contained within.
+ private Rectangle _roomBounds;
+
+ // The sound effect to play when the bat bounces off the edge of the screen.
+ private SoundEffect _bounceSoundEffect;
+
+ // The sound effect to play when the slime eats a bat.
+ private SoundEffect _collectSoundEffect;
+
+ // The SpriteFont Description used to draw text
+ private SpriteFont _font;
+
+ // Tracks the players score.
+ private int _score;
+
+ // Defines the position to draw the score text at.
+ private Vector2 _scoreTextPosition;
+
+ // Defines the origin used when drawing the score text.
+ private Vector2 _scoreTextOrigin;
+ #endregion
+
+ #region initialize
+ public override void Initialize()
+ {
+ // LoadContent is called during base.Initialize().
+ base.Initialize();
+
+ // During the game scene, we want to disable exit on escape. Instead,
+ // the escape key will be used to return back to the title screen
+ Core.ExitOnEscape = false;
+
+ Rectangle screenBounds = Core.GraphicsDevice.PresentationParameters.Bounds;
+
+ _roomBounds = new Rectangle(
+ (int)_tilemap.TileWidth,
+ (int)_tilemap.TileHeight,
+ screenBounds.Width - (int)_tilemap.TileWidth * 2,
+ screenBounds.Height - (int)_tilemap.TileHeight * 2
+ );
+
+ // Initial slime position will be the center tile of the tile map.
+ int centerRow = _tilemap.Rows / 2;
+ int centerColumn = _tilemap.Columns / 2;
+ _slimePosition = new Vector2(centerColumn * _tilemap.TileWidth, centerRow * _tilemap.TileHeight);
+
+ // Initial bat position will the in the top left corner of the room
+ _batPosition = new Vector2(_roomBounds.Left, _roomBounds.Top);
+
+ // Set the position of the score text to align to the left edge of the
+ // room bounds, and to vertically be at the center of the first tile.
+ _scoreTextPosition = new Vector2(_roomBounds.Left, _tilemap.TileHeight * 0.5f);
+
+ // Set the origin of the text so it is left-centered.
+ float scoreTextYOrigin = _font.MeasureString("Score").Y * 0.5f;
+ _scoreTextOrigin = new Vector2(0, scoreTextYOrigin);
+
+ // Assign the initial random velocity to the bat.
+ AssignRandomBatVelocity();
+ }
+ #endregion
+
+ #region loadcontent
+ public override void LoadContent()
+ {
+ // Create the texture atlas from the XML configuration file
+ TextureAtlas atlas = TextureAtlas.FromFile(Core.Content, "images/atlas-definition.xml");
+
+ // Create the slime animated sprite from the atlas.
+ _slime = atlas.CreateAnimatedSprite("slime-animation");
+ _slime.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+ _bat.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the tilemap from the XML configuration file.
+ _tilemap = Tilemap.FromFile(Content, "images/tilemap-definition.xml");
+ _tilemap.Scale = new Vector2(4.0f, 4.0f);
+
+ // Load the bounce sound effect
+ _bounceSoundEffect = Content.Load("audio/bounce");
+
+ // Load the collect sound effect
+ _collectSoundEffect = Content.Load("audio/collect");
+
+ // Load the font
+ _font = Core.Content.Load("fonts/04B_30");
+ }
+ #endregion
+
+ #region update
+ public override void Update(GameTime gameTime)
+ {
+ // Update the slime animated sprite.
+ _slime.Update(gameTime);
+
+ // Update the bat animated sprite.
+ _bat.Update(gameTime);
+
+ // Check for keyboard input and handle it.
+ CheckKeyboardInput();
+
+ // Check for gamepad input and handle it.
+ CheckGamePadInput();
+
+ // Creating a bounding circle for the slime
+ Circle slimeBounds = new Circle(
+ (int)(_slimePosition.X + (_slime.Width * 0.5f)),
+ (int)(_slimePosition.Y + (_slime.Height * 0.5f)),
+ (int)(_slime.Width * 0.5f)
+ );
+
+ // Use distance based checks to determine if the slime is within the
+ // bounds of the game screen, and if it is outside that screen edge,
+ // move it back inside.
+ if (slimeBounds.Left < _roomBounds.Left)
+ {
+ _slimePosition.X = _roomBounds.Left;
+ }
+ else if (slimeBounds.Right > _roomBounds.Right)
+ {
+ _slimePosition.X = _roomBounds.Right - _slime.Width;
+ }
+
+ if (slimeBounds.Top < _roomBounds.Top)
+ {
+ _slimePosition.Y = _roomBounds.Top;
+ }
+ else if (slimeBounds.Bottom > _roomBounds.Bottom)
+ {
+ _slimePosition.Y = _roomBounds.Bottom - _slime.Height;
+ }
+
+ // Calculate the new position of the bat based on the velocity
+ Vector2 newBatPosition = _batPosition + _batVelocity;
+
+ // Create a bounding circle for the bat
+ Circle batBounds = new Circle(
+ (int)(newBatPosition.X + (_bat.Width * 0.5f)),
+ (int)(newBatPosition.Y + (_bat.Height * 0.5f)),
+ (int)(_bat.Width * 0.5f)
+ );
+
+ Vector2 normal = Vector2.Zero;
+
+ // Use distance based checks to determine if the bat is within the
+ // bounds of the game screen, and if it is outside that screen edge,
+ // reflect it about the screen edge normal
+ if (batBounds.Left < _roomBounds.Left)
+ {
+ normal.X = Vector2.UnitX.X;
+ newBatPosition.X = _roomBounds.Left;
+ }
+ else if (batBounds.Right > _roomBounds.Right)
+ {
+ normal.X = -Vector2.UnitX.X;
+ newBatPosition.X = _roomBounds.Right - _bat.Width;
+ }
+
+ if (batBounds.Top < _roomBounds.Top)
+ {
+ normal.Y = Vector2.UnitY.Y;
+ newBatPosition.Y = _roomBounds.Top;
+ }
+ else if (batBounds.Bottom > _roomBounds.Bottom)
+ {
+ normal.Y = -Vector2.UnitY.Y;
+ newBatPosition.Y = _roomBounds.Bottom - _bat.Height;
+ }
+
+ // If the normal is anything but Vector2.Zero, this means the bat had
+ // moved outside the screen edge so we should reflect it about the
+ // normal.
+ if (normal != Vector2.Zero)
+ {
+ _batVelocity = Vector2.Reflect(_batVelocity, normal);
+
+ // Play the bounce sound effect
+ Core.Audio.PlaySoundEffect(_bounceSoundEffect);
+ }
+
+ _batPosition = newBatPosition;
+
+ if (slimeBounds.Intersects(batBounds))
+ {
+ // Choose a random row and column based on the total number of each
+ int column = Random.Shared.Next(1, _tilemap.Columns - 1);
+ int row = Random.Shared.Next(1, _tilemap.Rows - 1);
+
+ // Change the bat position by setting the x and y values equal to
+ // the column and row multiplied by the width and height.
+ _batPosition = new Vector2(column * _bat.Width, row * _bat.Height);
+
+ // Assign a new random velocity to the bat
+ AssignRandomBatVelocity();
+
+ // Play the collect sound effect
+ Core.Audio.PlaySoundEffect(_collectSoundEffect);
+
+ // Increase the player's score.
+ _score += 100;
+ }
+ }
+ #endregion
+
+ #region helpers
+ private void AssignRandomBatVelocity()
+ {
+ // Generate a random angle
+ float angle = (float)(Random.Shared.NextDouble() * Math.PI * 2);
+
+ // Convert angle to a direction vector
+ float x = (float)Math.Cos(angle);
+ float y = (float)Math.Sin(angle);
+ Vector2 direction = new Vector2(x, y);
+
+ // Multiply the direction vector by the movement speed
+ _batVelocity = direction * MOVEMENT_SPEED;
+ }
+
+ private void CheckKeyboardInput()
+ {
+ // Get a reference to the keyboard inof
+ KeyboardInfo keyboard = Core.Input.Keyboard;
+
+ // If the escape key is pressed, return to the title screen
+ if (Core.Input.Keyboard.WasKeyJustPressed(Keys.Escape))
+ {
+ Core.ChangeScene(new TitleScene());
+ }
+
+ // If the space key is held down, the movement speed increases by 1.5
+ float speed = MOVEMENT_SPEED;
+ if (keyboard.IsKeyDown(Keys.Space))
+ {
+ speed *= 1.5f;
+ }
+
+ // If the W or Up keys are down, move the slime up on the screen.
+ if (keyboard.IsKeyDown(Keys.W) || keyboard.IsKeyDown(Keys.Up))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // if the S or Down keys are down, move the slime down on the screen.
+ if (keyboard.IsKeyDown(Keys.S) || keyboard.IsKeyDown(Keys.Down))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If the A or Left keys are down, move the slime left on the screen.
+ if (keyboard.IsKeyDown(Keys.A) || keyboard.IsKeyDown(Keys.Left))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If the D or Right keys are down, move the slime right on the screen.
+ if (keyboard.IsKeyDown(Keys.D) || keyboard.IsKeyDown(Keys.Right))
+ {
+ _slimePosition.X += speed;
+ }
+
+ // If the M key is pressed, toggle mute state for audio.
+ if (keyboard.WasKeyJustPressed(Keys.M))
+ {
+ Core.Audio.ToggleMute();
+ }
+
+ // If the + button is pressed, increase the volume.
+ if (keyboard.WasKeyJustPressed(Keys.OemPlus))
+ {
+ Core.Audio.SongVolume += 0.1f;
+ Core.Audio.SoundEffectVolume += 0.1f;
+ }
+
+ // If the - button was pressed, decrease the volume.
+ if (keyboard.WasKeyJustPressed(Keys.OemMinus))
+ {
+ Core.Audio.SongVolume -= 0.1f;
+ Core.Audio.SoundEffectVolume -= 0.1f;
+ }
+ }
+
+ private void CheckGamePadInput()
+ {
+ // Get the gamepad info for gamepad one.
+ GamePadInfo gamePadOne = Core.Input.GamePads[(int)PlayerIndex.One];
+
+ // If the A button is held down, the movement speed increases by 1.5
+ // and the gamepad vibrates as feedback to the player.
+ float speed = MOVEMENT_SPEED;
+ if (gamePadOne.IsButtonDown(Buttons.A))
+ {
+ speed *= 1.5f;
+ GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f);
+ }
+ else
+ {
+ GamePad.SetVibration(PlayerIndex.One, 0.0f, 0.0f);
+ }
+
+ // Check thumbstick first since it has priority over which gamepad input
+ // is movement. It has priority since the thumbstick values provide a
+ // more granular analog value that can be used for movement.
+ if (gamePadOne.LeftThumbStick != Vector2.Zero)
+ {
+ _slimePosition.X += gamePadOne.LeftThumbStick.X * speed;
+ _slimePosition.Y -= gamePadOne.LeftThumbStick.Y * speed;
+ }
+ else
+ {
+ // If DPadUp is down, move the slime up on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadUp))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // If DPadDown is down, move the slime down on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadDown))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If DPapLeft is down, move the slime left on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadLeft))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If DPadRight is down, move the slime right on the screen.
+ if (gamePadOne.IsButtonDown(Buttons.DPadRight))
+ {
+ _slimePosition.X += speed;
+ }
+ }
+ }
+ #endregion
+
+ #region draw
+ public override void Draw(GameTime gameTime)
+ {
+ // Clear the back buffer.
+ Core.GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ // Begin the sprite batch to prepare for rendering.
+ Core.SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
+
+ // Draw the tilemap
+ _tilemap.Draw(Core.SpriteBatch);
+
+ // Draw the slime sprite.
+ _slime.Draw(Core.SpriteBatch, _slimePosition);
+
+ // Draw the bat sprite.
+ _bat.Draw(Core.SpriteBatch, _batPosition);
+
+ // Draw the score
+ Core.SpriteBatch.DrawString(
+ _font, // spriteFont
+ $"Score: {_score}", // text
+ _scoreTextPosition, // position
+ Color.White, // color
+ 0.0f, // rotation
+ _scoreTextOrigin, // origin
+ 1.0f, // scale
+ SpriteEffects.None, // effects
+ 0.0f // layerDepth
+ );
+
+ // Always end the sprite batch when finished.
+ Core.SpriteBatch.End();
+ }
+ #endregion
+}
diff --git a/articles/tutorials/building_2d_games/17_scenes/snippets/scene.cs b/articles/tutorials/building_2d_games/17_scenes/snippets/scene.cs
new file mode 100644
index 00000000..b31698f0
--- /dev/null
+++ b/articles/tutorials/building_2d_games/17_scenes/snippets/scene.cs
@@ -0,0 +1,117 @@
+#region declaration
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+
+namespace MonoGameLibrary.Scenes;
+
+public abstract class Scene : IDisposable
+{
+
+}
+#endregion
+{
+ #region properties
+ ///
+ /// Gets the ContentManager used for loading scene-specific assets.
+ ///
+ ///
+ /// Assets loaded through this ContentManager will be automatically unloaded when this scene ends.
+ ///
+ protected ContentManager Content { get; }
+
+ ///
+ /// Gets a value that indicates if the scene has been disposed of.
+ ///
+ public bool IsDisposed { get; private set; }
+ #endregion
+
+ #region ctors
+ ///
+ /// Creates a new scene instance.
+ ///
+ public Scene()
+ {
+ // Create a content manager for the scene
+ Content = new ContentManager(Core.Content.ServiceProvider);
+
+ // Set the root directory for content to the same as the root directory
+ // for the game's content.
+ Content.RootDirectory = Core.Content.RootDirectory;
+ }
+
+ // Finalizer, called when object is cleaned up by garbage collector.
+ ~Scene() => Dispose(false);
+ #endregion
+
+ #region methods
+ ///
+ /// Initializes the scene.
+ ///
+ ///
+ /// When overriding this in a derived class, ensure that base.Initialize()
+ /// still called as this is when LoadContent is called.
+ ///
+ public virtual void Initialize()
+ {
+ LoadContent();
+ }
+
+ ///
+ /// Override to provide logic to load content for the scene.
+ ///
+ public virtual void LoadContent() { }
+
+ ///
+ /// Unloads scene-specific content.
+ ///
+ public virtual void UnloadContent()
+ {
+ Content.Unload();
+ }
+
+ ///
+ /// Updates this scene.
+ ///
+ /// A snapshot of the timing values for the current frame.
+ public virtual void Update(GameTime gameTime) { }
+
+ ///
+ /// Draws this scene.
+ ///
+ /// A snapshot of the timing values for the current frame.
+ public virtual void Draw(GameTime gameTime) { }
+ #endregion
+
+ #region disposable
+ ///
+ /// Disposes of this scene.
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Disposes of this scene.
+ ///
+ /// '
+ /// Indicates whether managed resources should be disposed. This value is only true when called from the main
+ /// Dispose method. When called from the finalizer, this will be false.
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (IsDisposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ UnloadContent();
+ Content.Dispose();
+ }
+ }
+ #endregion
+}
diff --git a/articles/tutorials/building_2d_games/17_scenes/snippets/titlescene.cs b/articles/tutorials/building_2d_games/17_scenes/snippets/titlescene.cs
new file mode 100644
index 00000000..68baea08
--- /dev/null
+++ b/articles/tutorials/building_2d_games/17_scenes/snippets/titlescene.cs
@@ -0,0 +1,127 @@
+#region declaration
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary;
+using MonoGameLibrary.Scenes;
+
+namespace DungeonSlime.Scenes;
+
+public class TitleScene : Scene
+{
+
+}
+#endregion
+{
+ #region fields
+ private const string DUNGEON_TEXT = "Dungeon";
+ private const string SLIME_TEXT = "Slime";
+ private const string PRESS_ENTER_TEXT = "Press Enter To Start";
+
+ // The font to use to render normal text.
+ private SpriteFont _font;
+
+ // The font used to render the title text.
+ private SpriteFont _font5x;
+
+ // The position to draw the dungeon text at.
+ private Vector2 _dungeonTextPos;
+
+ // The origin to set for the dungeon text.
+ private Vector2 _dungeonTextOrigin;
+
+ // The position to draw the slime text at.
+ private Vector2 _slimeTextPos;
+
+ // The origin to set for the slime text.
+ private Vector2 _slimeTextOrigin;
+
+ // The position to draw the press enter text at.
+ private Vector2 _pressEnterPos;
+
+ // The origin to set for the press enter text when drawing it.
+ private Vector2 _pressEnterOrigin;
+ #endregion
+
+ #region initialize
+ public override void Initialize()
+ {
+ // LoadContent is called during base.Initialize().
+ base.Initialize();
+
+ // While on the title screen, we can enable exit on escape so the player
+ // can close the game by pressing the escape key.
+ Core.ExitOnEscape = true;
+
+ // Set the position and origin for the Dungeon text.
+ Vector2 size = _font5x.MeasureString(DUNGEON_TEXT);
+ _dungeonTextPos = new Vector2(640, 100);
+ _dungeonTextOrigin = size * 0.5f;
+
+ // Set the position and origin for the Slime text.
+ size = _font5x.MeasureString(SLIME_TEXT);
+ _slimeTextPos = new Vector2(757, 207);
+ _slimeTextOrigin = size * 0.5f;
+
+ // Set the position and origin for the press enter text.
+ size = _font.MeasureString(PRESS_ENTER_TEXT);
+ _pressEnterPos = new Vector2(640, 620);
+ _pressEnterOrigin = size * 0.5f;
+ }
+ #endregion
+
+ #region loadcontent
+ public override void LoadContent()
+ {
+ // Load the font for the standard text.
+ _font = Core.Content.Load("fonts/04B_30");
+
+ // Load the font for the title text
+ _font5x = Content.Load("fonts/04B_30_5x");
+ }
+ #endregion
+
+ #region update
+ public override void Update(GameTime gameTime)
+ {
+ // If the user presses enter, switch to the game scene.
+ if (Core.Input.Keyboard.WasKeyJustPressed(Keys.Enter))
+ {
+ Core.ChangeScene(new GameScene());
+ }
+ }
+ #endregion
+
+ #region draw
+ public override void Draw(GameTime gameTime)
+ {
+ Core.GraphicsDevice.Clear(new Color(32, 40, 78, 255));
+
+ // Begin the sprite batch to prepare for rendering.
+ Core.SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
+
+ Color dropShadowColor = new Color(19, 23, 46, 175);
+ dropShadowColor = Color.Black * 0.5f;
+
+ // Draw the Dungeon text slightly offset from it is original position and
+ // with a transparent color to give it a drop shadow
+ Core.SpriteBatch.DrawString(_font5x, DUNGEON_TEXT, _dungeonTextPos + new Vector2(10, 10), dropShadowColor, 0.0f, _dungeonTextOrigin, 1.0f, SpriteEffects.None, 1.0f);
+
+ // Draw the Dungeon text on top of that at its original position
+ Core.SpriteBatch.DrawString(_font5x, DUNGEON_TEXT, _dungeonTextPos, Color.White, 0.0f, _dungeonTextOrigin, 1.0f, SpriteEffects.None, 1.0f);
+
+ // Draw the Slime text slightly offset from it is original position and
+ // with a transparent color to give it a drop shadow
+ Core.SpriteBatch.DrawString(_font5x, SLIME_TEXT, _slimeTextPos + new Vector2(10, 10), dropShadowColor, 0.0f, _slimeTextOrigin, 1.0f, SpriteEffects.None, 1.0f);
+
+ // Draw the Slime text on top of that at its original position
+ Core.SpriteBatch.DrawString(_font5x, SLIME_TEXT, _slimeTextPos, Color.White, 0.0f, _slimeTextOrigin, 1.0f, SpriteEffects.None, 1.0f);
+
+ // Draw the press enter text
+ Core.SpriteBatch.DrawString(_font, PRESS_ENTER_TEXT, _pressEnterPos, Color.White, 0.0f, _pressEnterOrigin, 1.0f, SpriteEffects.None, 0.0f);
+
+ // Always end the sprite batch when finished.
+ Core.SpriteBatch.End();
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/17_scenes/videos/gameplay.webm b/articles/tutorials/building_2d_games/17_scenes/videos/gameplay.webm
new file mode 100644
index 00000000..1ffd8e37
Binary files /dev/null and b/articles/tutorials/building_2d_games/17_scenes/videos/gameplay.webm differ
diff --git a/articles/tutorials/building_2d_games/18_texture_sampling/images/address-mode-border.png b/articles/tutorials/building_2d_games/18_texture_sampling/images/address-mode-border.png
new file mode 100644
index 00000000..7a1dcc06
Binary files /dev/null and b/articles/tutorials/building_2d_games/18_texture_sampling/images/address-mode-border.png differ
diff --git a/articles/tutorials/building_2d_games/18_texture_sampling/images/address-mode-clamped.png b/articles/tutorials/building_2d_games/18_texture_sampling/images/address-mode-clamped.png
new file mode 100644
index 00000000..1553d4b5
Binary files /dev/null and b/articles/tutorials/building_2d_games/18_texture_sampling/images/address-mode-clamped.png differ
diff --git a/articles/tutorials/building_2d_games/18_texture_sampling/images/address-mode-mirror.png b/articles/tutorials/building_2d_games/18_texture_sampling/images/address-mode-mirror.png
new file mode 100644
index 00000000..d6a3ca98
Binary files /dev/null and b/articles/tutorials/building_2d_games/18_texture_sampling/images/address-mode-mirror.png differ
diff --git a/articles/tutorials/building_2d_games/18_texture_sampling/images/address-mode-wrap.png b/articles/tutorials/building_2d_games/18_texture_sampling/images/address-mode-wrap.png
new file mode 100644
index 00000000..eacb8aa3
Binary files /dev/null and b/articles/tutorials/building_2d_games/18_texture_sampling/images/address-mode-wrap.png differ
diff --git a/articles/tutorials/building_2d_games/18_texture_sampling/images/background-pattern.png b/articles/tutorials/building_2d_games/18_texture_sampling/images/background-pattern.png
new file mode 100644
index 00000000..2d8d878e
Binary files /dev/null and b/articles/tutorials/building_2d_games/18_texture_sampling/images/background-pattern.png differ
diff --git a/articles/tutorials/building_2d_games/18_texture_sampling/images/filter-mode-anisotropic-comparison.png b/articles/tutorials/building_2d_games/18_texture_sampling/images/filter-mode-anisotropic-comparison.png
new file mode 100644
index 00000000..e87c3baa
Binary files /dev/null and b/articles/tutorials/building_2d_games/18_texture_sampling/images/filter-mode-anisotropic-comparison.png differ
diff --git a/articles/tutorials/building_2d_games/18_texture_sampling/images/filter-mode-anisotropic-linear-comparison.png b/articles/tutorials/building_2d_games/18_texture_sampling/images/filter-mode-anisotropic-linear-comparison.png
new file mode 100644
index 00000000..80064877
Binary files /dev/null and b/articles/tutorials/building_2d_games/18_texture_sampling/images/filter-mode-anisotropic-linear-comparison.png differ
diff --git a/articles/tutorials/building_2d_games/18_texture_sampling/images/filter-mode-linear.png b/articles/tutorials/building_2d_games/18_texture_sampling/images/filter-mode-linear.png
new file mode 100644
index 00000000..6f9c0331
Binary files /dev/null and b/articles/tutorials/building_2d_games/18_texture_sampling/images/filter-mode-linear.png differ
diff --git a/articles/tutorials/building_2d_games/18_texture_sampling/images/filter-mode-point.png b/articles/tutorials/building_2d_games/18_texture_sampling/images/filter-mode-point.png
new file mode 100644
index 00000000..55073bbe
Binary files /dev/null and b/articles/tutorials/building_2d_games/18_texture_sampling/images/filter-mode-point.png differ
diff --git a/articles/tutorials/building_2d_games/18_texture_sampling/images/mgcb-editor.png b/articles/tutorials/building_2d_games/18_texture_sampling/images/mgcb-editor.png
new file mode 100644
index 00000000..cb3bcef7
Binary files /dev/null and b/articles/tutorials/building_2d_games/18_texture_sampling/images/mgcb-editor.png differ
diff --git a/articles/tutorials/building_2d_games/18_texture_sampling/index.md b/articles/tutorials/building_2d_games/18_texture_sampling/index.md
new file mode 100644
index 00000000..1a7ee158
--- /dev/null
+++ b/articles/tutorials/building_2d_games/18_texture_sampling/index.md
@@ -0,0 +1,241 @@
+---
+title: "Chapter 18: Texture Sampling and Tiling Backgrounds"
+description: "Learn how to use texture sampling states and add a scrolling background effect to the game."
+---
+
+In previous chapters, we have [drawn individual sprites and textures](../08_the_sprite_class/index.md#using-the-sprite-class) with the sprite batch, but for creating repeating background patterns, we need a more efficient approach than manually drawing the same texture multiple times. We could reuse the [tilemap system](../13_working_with_tilemaps/index.md#the-tilemap-class) that was created to make repeated background patterns, but this has a limitation in that the tiles are stationary and would require constantly updating the tiles and positions if we wanted to animate it. Instead, this chapter introduces texture sampling states, specifically focusing on how to create and animate tiled backgrounds using [**SamplerState.PointWrap**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.PointWrap).
+
+In this chapter, you will:
+
+- Learn about texture sampling and sampler states in MonoGame.
+- Understand how wrap mode allows efficient texture tiling.
+- Implement a scrolling tiled background for the title screen.
+
+## Understanding Texture Sampling
+
+When a texture is drawn to the screen, MonoGame uses a process called "sampling" to determine which pixels from the texture should be displayed. Sampling is the process by which a graphics pipeline determines what color value to use from a texture when mapping it onto a surface. Think of it like placing a grid over an image and selecting which pixels to use when that image needs to be transformed in some way. When textures are drawn at their exact pixel size and position, with no rotation, sampling is straightforward, a direct 1:1 mapping for each pixel. However, when a texture is scaled, rotated, or only partially visible, the graphics hardware needs to decide how to interpret the texture data.
+
+For example, if you draw a texture twice its size, there are not enough pixels to fill the new larger space, so the graphics hardware must determine how to fill those gaps. Similarly, if you were to scale down a texture, multiple source pixels might map to a single output pixel, requiring the hardware to decide which ones to use or how to blend them. The rules that govern these decisions are defined by sampler states.
+
+### Texture Coordinates
+
+In graphics programming, textures are addressed using a normalized coordinate system ranging from 0.0 to 1.0, regardless of the texture's actual pixel dimensions:
+
+- The top-left corner of a texture is (0.0, 0.0)
+- The bottom-right corner is (1.0, 1.0)
+- The center is (0.5, 0.5)
+
+This normalized system means that regardless of whether your texture is 32×32 pixels or 2048×2048 pixels, the coordinates to access the entire texture always range from 0.0 to 1.0. The graphics hardware automatically converts these normalized coordinates to the actual pixel locations within the texture.
+
+When you use [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) to draw a texture with a source rectangle specified in pixels, MonoGame internally converts those pixel coordinates to normalized texture coordinates before sending them to the GPU. Similarly, when you specify a destination rectangle, MonoGame determines how the normalized texture coordinates should map to screen coordinates.
+
+### What is a SampleState
+
+A SamplerState controls how textures are sampled during rendering. It determines several aspects of texture rendering:
+
+- How textures are filtered when scaled (point/linear/anisotropic filtering).
+- How texture coordinates outside the 0.0 to 1.0 range are handled (wrap/clamp/mirror).
+- How mipmap levels are selected and blended.
+
+In MonoGame, these sampler states are represented by the [**SamplerState**](xref:Microsoft.Xna.Framework.Graphics.SamplerState) class, which provides several predefined states for common scenarios.
+
+### Filtering Modes
+
+One aspect of sampler states if the filtering mode. Filtering in computer graphics refers to how the graphics hardware decides to blend or select pixels when a texture is displayed at a different size than its original dimensions. The filter mode determines how pixels are interpolated (calculated and combined) when a texture is scaled up or down.
+
+Think of filtering as the graphics hardware's strategy for filling in missing information when a texture is transformed. When you enlarge a texture, the system needs to create new pixels that did not exist in the original. When you shrink a texture, multiple original pixels must be combined into fewer output pixels. The filtering mode controls how this process happens.
+
+There are three filtering modes available in MonoGame:
+
+* Point
+* Linear
+* Anisotropic.
+
+Each mode offers a different balance between performance and visual quality.
+
+#### Point Filtering Mode
+
+Point mode uses what is called nearest neighbor sampling. This means that when a texture is scaled, the closest pixel is selected resulting in a pixelated appearance when scaled up. This is typically the ideal mode to use for pixel-art games when you want to preserve the exact pixel appearance of a scaled texture. Point filtering is the least computationally expensive filtering mode of the three since it only samples a single pixel without any blending calculations. This makes it the fastest option, especially on lower-end hardware or when rendering many textures simultaneously.
+
+|  |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 18-1: Illustration of using Point filtering mode. Left: MonoGame logo at 32x32 pixels. Right: MonoGame logo at 128x128 pixels** |
+
+#### Linear Filtering Mode
+
+Linear filtering mode blends neighboring pixels when the texture is scaled. This creates a smoother, but potentially blurrier appearance. This is better for realistic or high-resolution textures. Linear filtering requires more processing power than point filtering since it needs to sample multiple pixels and calculated weighted averages between them. However, on modern hardware, this performance difference is usually negligible for 2D games, making it a good balance between quality and performance for most non-pixel art games or assets.
+
+|  |
+| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 18-2: Illustration of using Linear filtering mode. Left: MonoGame logo at 32x32 pixels. Right: MonoGame logo at 128x128 pixels** |
+
+#### Anisotropic Filtering Mode
+
+Anisotropic filtering mode provides higher-quality filter for textures viewed from oblique angles. This is primarily used in 3D rendering. It helps textures look more detailed by reducing blur and aliasing that occurs when a surface is angled away from the viewer. Anisotropic filtering is the most computationally intensive option, as it samples many more pixels and performs complex calculations to determine the appropriate blending. The performance cost increases with the anisotropic level (typically 2x, 4x, 8x, or 16x), which determines how many samples are taken. This can significantly impact frame rates in complex 3D scenes, especially on mobile or lower-end devices, so it should be used selectively where visual quality at angles is most important.
+
+|  |  |
+| :----------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 18-3: Illustration of the MonoGame Fuel cell demo using Linear filtering** | **Figure 18-4: Illustration of the MonoGame Fuel cell demo using Anisotropic filtering** |
+
+### Addressing Modes
+
+The other aspect is the addressing mode, which determines what happens when texture coordinates fall outside the normal 0.0 to 1.0 range. When drawing textures, sometimes the calculated texture coordinates end up being less than 0.0 or greater than 1.0. The addressing mode tells the graphics hardware what to do in these situations; whether to repeat the texture, mirror it, stretch the edge pixels, or use a specific border color. Think of it as instructions for what to display in areas where the texture does not naturally exist. These modes are particularly important for creating effects like seamless tiling backgrounds, scrolling texture, or handling the edges of transformed sprites properly. There are four addressing modes available; Wrap, Mirror, Clamp, and Border Color:
+
+#### Wrap Mode
+
+When using Wrap mode, at every whole integer of the texture coordinates (0.0 and 1.0), the texture coordinate is wrapped to stay within the 0.0 to 1.0 range (i.e. if the texture coordinate is 1.2, then that wraps to become 0.2). This creates a tiled pattern.
+
+For example, if we were to take the MonoGame logo at 128x128 pixels and draw it to a destination rectangle that was three times the size at 384x384 pixels, then the texture coordinates of the destination rectangle become (0.0, 0.0) (top-left), (3.0, 0.0) (top-right), (0.0) (bottom-left), and (3.0, 3.0) bottom-right. The MonoGame logo texture would repeat three times horizontally and vertically within the destination.
+
+|  |
+| :-------------------------------------------------------------------------------------------------------------------: |
+| **Figure 18-5: Illustration of the MonoGame logo drawn using wrapped addressing mode** |
+
+#### Mirror Mode
+
+Mirror mode is similar to Wrap mode. However instead of repeating the texture at every whole integer of the texture coordinates (0.0 and 1.0), the texture is flipped, creating a mirror effect.
+
+Using the same example as above, taking the MonoGame logo at 128x128 pixels and drawing it to a destination rectangle three times the size with Mirror mode would produce the following:
+
+|  |
+| :--------------------------------------------------------------------------------------------------------------------: |
+| **Figure 18-6: Illustration of the MonoGame logo drawn using mirror addressing mode** |
+
+#### Clamp Mode
+
+When using Clamp mode, the texture coordinates are clamped to the 0.0 and 1.0 range. Texture coordinates that would go beyond this (edge pixels) are smeared.
+
+The simplest demonstration of this is to use a checkerboard pattern. If we were to take a texture that was a checkerboard pattern at 128x128 pixels and draw it to a destination rectangle three times the size with Clamped mode, then any pixels that extend outside the clamped range would smeared, producing the following:
+
+|  |
+| :---------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 18-7: Illustration of a checkerboard pattern drawn using clamped addressing mode** |
+
+#### Border Color
+
+When using Border Color mode, similar to Clamped mode, the texture coordinates are clamped to the 0.0 and 1.0f range. However, in Border Color mode, texture coordinates that would go beyond this (edge pixels) are instead drawn using the color set as the border color for the sampler state.
+
+For example, if we use the checkerboard pattern again, using Border Color mode with a border color of green, then it would produce the following:
+
+|  |
+| :------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 18-8: Illustration of a checkerboard pattern drawn using border addressing mode with the border color set to green** |
+
+## Using SamplerStates
+
+MonoGame offers several predefined sampler states as part of the [**SamplerState**](xref:Microsoft.Xna.Framework.Graphics.SamplerState) class that cover common scenarios:
+
+| SamplerState | Description | Common Use Case |
+| ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
+| [**AnisotropicClamp**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.AnisotropicClamp) | Combines Anisotropic filter mode with the Clamp addressing mode. | 3D textures viewed at oblique angles, like ground textures in a 3D world where you want high-quality filtering but no repeating patterns. |
+| [**AnisotropicWrap**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.AnisotropicWrap) | Combines Anisotropic filter mode with the Wrap addressing mode. | Terrain textures in 3D games where you need high-quality filtering and repeating patterns over large surfaces. |
+| [**LinearClamp**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.LinearClamp) | Combines Linear filter mode with the Clamp addressing mode. | UI elements and single sprites where you want smooth scaling but no repeating patterns. Good for realistic graphics that need to scale. |
+| [**LinearWrap**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.LinearWrap) | Combines Linear filter mode with the Wrap addressing mode. | Scrolling backgrounds with smooth transitions, like water or cloud textures that need to tile seamlessly with blended edges. |
+| [**PointClamp**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.PointClamp) | Combines Point filter mode with the Clamp addressing mode. | Pixel art sprites and UI elements where you want to preserve crisp pixel edges without any blurring when scaled. Default for most 2D games. |
+| [**PointWrap**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.PointWrap) | Combines Point filter mode with the Wrap addressing mode. | Tiled pixel art backgrounds and patterns where you want crisp pixels and repeating patterns |
+
+When using the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch), you specify which sampler state you want to use as the `samplerState` parameter for the [**SpriteBatch.Begin**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch.Begin(Microsoft.Xna.Framework.Graphics.SpriteSortMode,Microsoft.Xna.Framework.Graphics.BlendState,Microsoft.Xna.Framework.Graphics.SamplerState,Microsoft.Xna.Framework.Graphics.DepthStencilState,Microsoft.Xna.Framework.Graphics.RasterizerState,Microsoft.Xna.Framework.Graphics.Effect,System.Nullable{Microsoft.Xna.Framework.Matrix})) method
+
+```cs
+// Example of using the Point Clamp sampler state
+spriteBatch.Begin(samplerState: SamplerState.PointClamp);
+```
+
+> [!NOTE]
+> The default sampler state for [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) is [**SamplerState.LinearClamp**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.LinearClamp) in MonoGame, though [**SamplerState.PointClamp**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.PointClamp) is often preferred for pixel art games to prevent blurring.
+
+## Adding a Scrolling Background to the Title Scene
+
+We will now update title scene of our game by adding a scrolling background pattern using [**SamplerState.PointWrap**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.PointWrap). By using the Wrap addressing mode, we can create a large scrolling background using only a small texture. When the texture is drawn with a destination rectangle larger than the texture itself, the Wrap mode will automatically tile the texture to fill the space. By adjusting the source rectangle over time, we can create a scrolling effect with minimal effort.
+
+First, download the following image of a repeatable background pattern by right-clicking it and saving it as `background-pattern.png` in the `Content/images` folder of the game project:
+
+|  |
+| :---------------------------------------------------------------------------------------------------------------: |
+| **Figure 18-9: The repeatable background pattern we will use for the title screen** |
+
+Next, add this texture to your content project using the MGCB Editor:
+
+1. Open the `Content.mgcb` content project file in the MGCB Editor.
+2. Right-click the images folder and choose `Add > Existing item...`.
+3. Navigate to and select the `background-pattern.png` file.
+4. Save the changes and close the MGCB Editor.
+
+|  |
+| :-------------------------------------------------------------------------------------------------: |
+| **Figure 18-10: The MGCB Editor with the *background-pattern* image added** |
+
+### Updating the Title Scene
+
+Now that we have the background pattern texture added, we can update the `TitleScene` class to implement the scrolling background. Open the `TitleScene.cs` file in the game project and update it to the following
+
+[!code-csharp[](./snippets/titlescene.cs?highlight=39-50,76-81,92-93,104-113,120-123)]
+
+The key changes here are
+
+- The `_backgroundPattern` field was added to store a reference to the texture of the background pattern once its loaded.
+- The `_backgroundDestination` field was added to define the destination rectangle to draw the background pattern to.
+- The `_backgroundOffset` field was added to apply an offset to the source rectangle when rendering the background pattern to give it the appearance that it is scrolling.
+- The `_scrollSpeed` field was added to set the speed at which the background pattern scrolls.
+- In `Initialize`, the initial offset of the background is set to [**Vector2.Zero**](xref:Microsoft.Xna.Framework.Vector2.Zero) and the background destination rectangle is set to the bounds of the screen.
+- In `LoadContent`, the *background-pattern* texture is loaded and stored in `_backgroundPattern`.
+- In `Update`, the X and Y offset for the background source rectangle is calculated by adjusting the based on the scroll speed multiplied by the delta time. Modulo division is then used to ensure that the new offset calculations remain within the width and height bounds of the background texture so that the wrap is seamless.
+- In `Draw`, a new sprite batch begin/end block is added that uses [**SamplerState.PointWrap**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.PointWrap) and draws the background pattern to the destination rectangle using a source rectangle with the offset calculations.
+
+> [!NOTE]
+> We use two separate sprite batch begin/end blocks for this. The first uses [**SamplerState.PointWrap**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.PointWrap) to draw the background and the second uses [**SamplerState.PointClamp**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.PointClamp) to draw the rest of the scene.
+>
+> This separation is necessary because changing the sampler state requires ending the current sprite batch and beginning a new one.
+
+Running the game now with these changes, the title screen now has a scroll background that adds more visual depth and interest to it than just the plain colored background we had before.
+
+|  |
+| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 18-11: The title screen now with the repeating background texture of the slime and bat scrolling diagonally down and to the right** |
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+- Learned about texture coordinates and how they map from normalized 0.0 to 1.0 space to actual pixel locations.
+- Understood the difference between various filtering modes (Point, Linear, Anisotropic) and their visual impact.
+- Explored different addressing modes (Wrap, Mirror, Clamp, Border) and when to use each.
+- Discovered how to use predefined sampler states to simplify common rendering tasks.
+- Implemented a scrolling background pattern using [**SamplerState.PointWrap**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.PointWrap).
+
+## Test Your Knowledge
+
+1. What is the difference between normalized texture coordinates and pixel coordinates?
+
+ :::question-answer
+ Normalized texture coordinates always range from 0.0 to 1.0 regardless of the texture's actual pixel dimensions. The top-left corner is (0.0, 0.0) and the bottom-right is (1.0, 1.0). Pixel coordinates, on the other hand, directly reference specific pixel locations within the texture using integer values based on the actual texture dimensions. MonoGame automatically converts between these coordinate systems when drawing textures.
+ :::
+
+2. Which filtering mode would be most appropriate for a pixel art game, and why?
+
+ :::question-answer
+ Point filtering (also called nearest neighbor) is most appropriate for pixel art games. It selects the closest pixel when scaling rather than blending neighboring pixels, which preserves the crisp, pixelated aesthetic that defines pixel art. Linear or Anisotropic filtering would blur the intentionally sharp edges of pixel art graphics.
+ :::
+
+3. Why do we use modulo (%) operation on the background offset values when implementing the scrolling background?
+
+ :::question-answer
+ The modulo operation ensures that the offset values always remain within the bounds of the original texture dimensions. This prevents potential graphical artifacts that could appear when the offset exceeds the texture size, and it guarantees seamless wrapping as the background continuously scrolls. Without this, the background pattern might show visible seams or discontinuities when it repeats.
+ :::
+
+4. Why do we need to use two separate sprite batch begin/end blocks when drawing the background and the other elements in the title scene?
+
+ :::question-answer
+ We need separate blocks because changing the sampler state requires ending the current batch and beginning a new one. Since we want to use [**SamplerState.PointWrap**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.PointWrap) for the tiling background but [**SamplerState.PointClamp**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.PointClamp) for the UI elements, we must use two distinct sprite batch blocks with different sampler state settings. Using a single batch would apply the same sampler state to all drawn elements.
+ :::
+
+5. How does using a tiled background with [**SamplerState.PointWrap**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.PointWrap) compare to manually drawing multiple copies of a texture to create a background?
+
+ :::question-answer
+ Using [**SamplerState.PointWrap**](xref:Microsoft.Xna.Framework.Graphics.SamplerState.PointWrap) offers several advantages:
+
+ - It requires only a single draw call instead of multiple calls for each tile.
+ - No need to calculate positions for each individual tile
+ - Manually drawing multiple copies would be more code-intensive, less performant, and harder to maintain, especially for animations.
+
+ :::
diff --git a/articles/tutorials/building_2d_games/18_texture_sampling/snippets/titlescene.cs b/articles/tutorials/building_2d_games/18_texture_sampling/snippets/titlescene.cs
new file mode 100644
index 00000000..e3d9c982
--- /dev/null
+++ b/articles/tutorials/building_2d_games/18_texture_sampling/snippets/titlescene.cs
@@ -0,0 +1,151 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameLibrary;
+using MonoGameLibrary.Scenes;
+
+namespace DungeonSlime.Scenes;
+
+public class TitleScene : Scene
+{
+ private const string DUNGEON_TEXT = "Dungeon";
+ private const string SLIME_TEXT = "Slime";
+ private const string PRESS_ENTER_TEXT = "Press Enter To Start";
+
+ // The font to use to render normal text.
+ private SpriteFont _font;
+
+ // The font used to render the title text.
+ private SpriteFont _font5x;
+
+ // The position to draw the dungeon text at.
+ private Vector2 _dungeonTextPos;
+
+ // The origin to set for the dungeon text.
+ private Vector2 _dungeonTextOrigin;
+
+ // The position to draw the slime text at.
+ private Vector2 _slimeTextPos;
+
+ // The origin to set for the slime text.
+ private Vector2 _slimeTextOrigin;
+
+ // The position to draw the press enter text at.
+ private Vector2 _pressEnterPos;
+
+ // The origin to set for the press enter text when drawing it.
+ private Vector2 _pressEnterOrigin;
+
+ // The texture used for the background pattern.
+ private Texture2D _backgroundPattern;
+
+ // The destination rectangle for the background pattern to fill.
+ private Rectangle _backgroundDestination;
+
+ // The offset to apply when drawing the background pattern so it appears to
+ // be scrolling.
+ private Vector2 _backgroundOffset;
+
+ // The speed that the background pattern scrolls.
+ private float _scrollSpeed = 50.0f;
+
+ public override void Initialize()
+ {
+ // LoadContent is called during base.Initialize().
+ base.Initialize();
+
+ // While on the title screen, we can enable exit on escape so the player
+ // can close the game by pressing the escape key.
+ Core.ExitOnEscape = true;
+
+ // Set the position and origin for the Dungeon text.
+ Vector2 size = _font5x.MeasureString(DUNGEON_TEXT);
+ _dungeonTextPos = new Vector2(640, 100);
+ _dungeonTextOrigin = size * 0.5f;
+
+ // Set the position and origin for the Slime text.
+ size = _font5x.MeasureString(SLIME_TEXT);
+ _slimeTextPos = new Vector2(757, 207);
+ _slimeTextOrigin = size * 0.5f;
+
+ // Set the position and origin for the press enter text.
+ size = _font.MeasureString(PRESS_ENTER_TEXT);
+ _pressEnterPos = new Vector2(640, 620);
+ _pressEnterOrigin = size * 0.5f;
+
+ // Initialize the offset of the background pattern at zero
+ _backgroundOffset = Vector2.Zero;
+
+ // Set the background pattern destination rectangle to fill the entire
+ // screen background
+ _backgroundDestination = Core.GraphicsDevice.PresentationParameters.Bounds;
+ }
+
+ public override void LoadContent()
+ {
+ // Load the font for the standard text.
+ _font = Core.Content.Load("fonts/04B_30");
+
+ // Load the font for the title text
+ _font5x = Content.Load("fonts/04B_30_5x");
+
+ // Load the background pattern texture.
+ _backgroundPattern = Content.Load("images/background-pattern");
+ }
+
+ public override void Update(GameTime gameTime)
+ {
+ // If the user presses enter, switch to the game scene.
+ if (Core.Input.Keyboard.WasKeyJustPressed(Keys.Enter))
+ {
+ Core.ChangeScene(new GameScene());
+ }
+
+ // Update the offsets for the background pattern wrapping so that it
+ // scrolls down and to the right.
+ float offset = _scrollSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
+ _backgroundOffset.X -= offset;
+ _backgroundOffset.Y -= offset;
+
+ // Ensure that the offsets do not go beyond the texture bounds so it is
+ // a seamless wrap
+ _backgroundOffset.X %= _backgroundPattern.Width;
+ _backgroundOffset.Y %= _backgroundPattern.Height;
+ }
+
+ public override void Draw(GameTime gameTime)
+ {
+ Core.GraphicsDevice.Clear(new Color(32, 40, 78, 255));
+
+ // Draw the background pattern first using the PointWrap sampler state.
+ Core.SpriteBatch.Begin(samplerState: SamplerState.PointWrap);
+ Core.SpriteBatch.Draw(_backgroundPattern, _backgroundDestination, new Rectangle(_backgroundOffset.ToPoint(), _backgroundDestination.Size), Color.White * 0.5f);
+ Core.SpriteBatch.End();
+
+ // Begin the sprite batch to prepare for rendering.
+ Core.SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
+
+ // The color to use for the drop shadow text.
+ Color dropShadowColor = Color.Black * 0.5f;
+
+ // Draw the Dungeon text slightly offset from it is original position and
+ // with a transparent color to give it a drop shadow
+ Core.SpriteBatch.DrawString(_font5x, DUNGEON_TEXT, _dungeonTextPos + new Vector2(10, 10), dropShadowColor, 0.0f, _dungeonTextOrigin, 1.0f, SpriteEffects.None, 1.0f);
+
+ // Draw the Dungeon text on top of that at its original position
+ Core.SpriteBatch.DrawString(_font5x, DUNGEON_TEXT, _dungeonTextPos, Color.White, 0.0f, _dungeonTextOrigin, 1.0f, SpriteEffects.None, 1.0f);
+
+ // Draw the Slime text slightly offset from it is original position and
+ // with a transparent color to give it a drop shadow
+ Core.SpriteBatch.DrawString(_font5x, SLIME_TEXT, _slimeTextPos + new Vector2(10, 10), dropShadowColor, 0.0f, _slimeTextOrigin, 1.0f, SpriteEffects.None, 1.0f);
+
+ // Draw the Slime text on top of that at its original position
+ Core.SpriteBatch.DrawString(_font5x, SLIME_TEXT, _slimeTextPos, Color.White, 0.0f, _slimeTextOrigin, 1.0f, SpriteEffects.None, 1.0f);
+
+ // Draw the press enter text
+ Core.SpriteBatch.DrawString(_font, PRESS_ENTER_TEXT, _pressEnterPos, Color.White, 0.0f, _pressEnterOrigin, 1.0f, SpriteEffects.None, 0.0f);
+
+ // Always end the sprite batch when finished.
+ Core.SpriteBatch.End();
+ }
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/18_texture_sampling/videos/titlescreen.webm b/articles/tutorials/building_2d_games/18_texture_sampling/videos/titlescreen.webm
new file mode 100644
index 00000000..20aee437
Binary files /dev/null and b/articles/tutorials/building_2d_games/18_texture_sampling/videos/titlescreen.webm differ
diff --git a/articles/tutorials/building_2d_games/19_user_interface_fundamentals/images/user_interface_anchor.svg b/articles/tutorials/building_2d_games/19_user_interface_fundamentals/images/user_interface_anchor.svg
new file mode 100644
index 00000000..f3f5deff
--- /dev/null
+++ b/articles/tutorials/building_2d_games/19_user_interface_fundamentals/images/user_interface_anchor.svg
@@ -0,0 +1,2 @@
+
diff --git a/articles/tutorials/building_2d_games/19_user_interface_fundamentals/images/user_interface_anchor_positioning.svg b/articles/tutorials/building_2d_games/19_user_interface_fundamentals/images/user_interface_anchor_positioning.svg
new file mode 100644
index 00000000..f0e3f221
--- /dev/null
+++ b/articles/tutorials/building_2d_games/19_user_interface_fundamentals/images/user_interface_anchor_positioning.svg
@@ -0,0 +1,2 @@
+
diff --git a/articles/tutorials/building_2d_games/19_user_interface_fundamentals/images/user_interface_dock.svg b/articles/tutorials/building_2d_games/19_user_interface_fundamentals/images/user_interface_dock.svg
new file mode 100644
index 00000000..b54e937f
--- /dev/null
+++ b/articles/tutorials/building_2d_games/19_user_interface_fundamentals/images/user_interface_dock.svg
@@ -0,0 +1,10 @@
+
diff --git a/articles/tutorials/building_2d_games/19_user_interface_fundamentals/index.md b/articles/tutorials/building_2d_games/19_user_interface_fundamentals/index.md
new file mode 100644
index 00000000..760290b3
--- /dev/null
+++ b/articles/tutorials/building_2d_games/19_user_interface_fundamentals/index.md
@@ -0,0 +1,150 @@
+---
+title: "Chapter 19: User Interface Fundamentals"
+description: "Learn the core principles of game user interface design."
+---
+
+A game's user interface (UI) allows players to interact with the game beyond just controlling the character. UI elements include menus, buttons, panels, labels, and various other interactive components that provide information and control options to the player.
+
+In this chapter you will:
+
+- Learn the basics of user interface design in games.
+- Understand different UI types and their purposes.
+- Explore UI layout approaches and positioning strategies.
+- Understand the parent-child relationship for UI elements.
+- Learn about accessibility considerations in game UI design.
+
+We will first start by understanding what a user interface is and how it functions in game development.
+
+## Understanding Game User Interfaces
+
+A user interface in games serves as a bridge between the player and the game's systems. Well designed UIs help players navigate the game's mechanics, understand their current status, and make informed decisions. For new game developers, understanding UI principles is crucial because even the most mechanically sound game can fail if players ca not effectively interact with it.
+
+Game UIs consist of various visual elements that serve different purposes:
+
+1. **Information Display**: Elements like health bars, score counters, or minimap displays provide players with game state information. These elements help players understand their progress, resources, and current status without interrupting gameplay.
+2. **Interactive Controls**: Buttons, sliders, checkboxes, and other interactive elements allow players to make choices, adjust settings, or navigate through different sections of the game. These elements should provide clear visual feedback when interacted with to confirm the player's actions.
+3. **Feedback Mechanisms**: Visual effects like highlighting, color changes, or animations that respond to player actions help confirm that input was received. This feedback loop creates an intuitive and responsive feel for the UI in your game.
+
+User interfaces for games can be categorized into two main types, each with their own design considerations:
+
+- **Diegetic UI**: These elements exist within the game world itself and are often part of the narrative. Examples include a health meter integrated into a character's suit, ammunition displayed on a weapon's holographic sight, or the dashboard instruments in the cockpit of a racing game. A Diegetic UI can enhance immersion by making interface elements feel like natural parts of the game world.
+- **Non-diegetic UI**: These elements exist outside the game world, overlaid on top of the gameplay. Traditional menus, health bars in the corner of the screen, and score displays are common examples. While less immersive than a diegetic UI, non-diegetic elements are often clearer and easier to read.
+
+## UI Layout Systems
+
+When designing and implementing game UI systems, developers must decide how UI elements will be positioned on the screen. Two primary approaches exist, each with distinct advantages and trade-offs:
+
+1. **Absolute Positioning**: In this approach, each UI element is placed at specific coordinates on the screen. Elements are positioned using exact locations, which gives precise control over the layout. This approach is straightforward to implement and works well for static layouts where elements do not need to adjust based on screen size or content changes. The main disadvantage of absolute positioning is its lack of flexibility, as iterating on design can be more difficult since one change may have cascading effects on other elements. If the screen resolution changes or if an element's size changes, manual adjustments to positions are often necessary to maintain the desired layout.
+
+2. **Layout engines**: These system position UI elements relative to one another using rules and constraints. Elements might be positioned using concepts like "center", "align to parent", or "flow horizontally with spacing". Layout engines add complexity but provide flexibility. The advantage of layout engines is adaptability to different screen sizes and content changes. However, they require more initial setup and can be more complex to implement from scratch.
+
+## Parent-Child Relationships
+
+Parent-child relationships are a part of many UI system. This relationship is implemented with UI elements containing other UI elements, creating a tree-like structure. This hierarchial approach mirrors how interface elements naturally group together in designs.
+
+For example, a settings panel might contain multiple buttons, labels, and sliders. By making these elements children of the panel, they can be managed as a cohesive unit. This organizational structure provides several significant advantages:
+
+- **Inheritance of Properties**: Child elements can automatically inherit certain properties from their parents. For instance, if a parent element is hidden or disabled, all its children can be hidden or disabled as well. This cascading behavior simplifies state management across complex interfaces.
+- **Relative Positioning**: Child elements can be positioned relative to their parents rather than relative to the screen. This means you can place elements within a container and then move the entire container as a unit without having to update each child's position individually.
+- **Simplified State Management**: Actions on parent elements can automatically propagate to their children. For example, disabling a menu panel can automatically disable all buttons within it, preventing interaction with elements that should be active.
+- **Batch Operations**: Operations like drawing and updating can be performed on a parent element and automatically cascade to all children, reducing the need for repetitive code.
+- **Logical Grouping**: The hierarchy naturally models the conceptual grouping of UI elements, making the code structure more intuitive and easier to maintain.
+
+## Anchoring and Docking
+
+In UI systems, two important concepts help with positioning elements: anchoring and docking.
+
+### Anchoring
+
+Anchoring allows you to position UI elements relative to specific reference points on their parents. The following diagram demonstrates common anchor points:
+
+|  |
+| :--------------------------------------------------------------------------------------: |
+| **Figure 19-1: Diagram showing common anchor points** |
+
+When you set an anchor point, the elements' position coordinates become relative to that anchor point. For example with a "Right" anchor and an X value of *-5*, you element would position itself *5 pixels* to the left of the parent's right edge, creating a consistent margin regardless of the parent's size, as demonstrated in the following diagram:
+
+|  |
+| :-------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| **Figure 19-2: Diagram showing anchored element positioning relative to anchor regardless of parent size** |
+
+### Docking
+
+Docking takes anchoring a step further by also adjusting an element's size to fill available space. The following diagram demonstrates common docking options:
+
+|  |
+| :------------------------------------------------------------------------------------------: |
+| **Figure 19-3: Diagram showing the common docking options** |
+
+These positioning strategies enable you to create layouts that can maintain their visual relationships even when parent elements change size or position.
+
+## Accessibility in Game UI
+
+Creating accessible user interfaces is an essential aspect of inclusive game design. Accessibility ensures that your game can be played by a broader audience, including players with visual acuity or other specific needs. When designing your UI system, consider some of these key accessibility principles:
+
+### Visual Accessibility
+
+- **Color contrast**: Ensure sufficient contrast between text and backgrounds.
+- **Use shapes**: Do not rely solely on color to convey important information; add shapes, patterns, or text labels as well. For example, if displaying warning text, also use something such as the common warning sign ⚠️.
+- **Text size and scaling**: Allow players to adjust text size or implement a UI scaling option.
+- **Internationalization (i18n)**: Consider how your UI might be interpreted across different cultures and regions. Number formatting can vary significantly - some regions use periods for thousands separators (1.000.000) while others use commas (1,000,000). Control symbolism also differs culturally; for example, on console controllers, the Cross button typically means "Select" in Western regions but "Cancel" in Japan, with Circle having the opposite meaning.
+
+### Input Accessibility
+
+- **Input redundancy**: Support multiple input methods for the same action. This ensures players can interact with UI elements using their preferred input devices.
+- **Reduce input precision requirements**: Implement generous hitboxes for clickable UI elements to help players with motor control difficulties.
+
+### Testing for Accessibility
+
+The most effect way to ensure accessibility is through testing under different circumstances and with diverse users:
+
+- Test your Ui using only keyboard navigation.
+- Try playing without sound.
+- Check your UI with a color blindness simulator.
+- Adjust the display scale to simulate low vision.
+- Get feedback from player with different abilities.
+
+By considering accessibility early in development rather than as an afterthought, you create games that can be enjoyed by more players while also often improving the experience for everyone.
+
+## Conclusion
+
+In this chapter, you learned the fundamentals of user interface design for games. You explored the different types of UI elements and their purposes, understood the benefits of parent-child relationships in UI hierarchies, and learned about different positioning strategies like anchoring and docking.
+
+You also discovered the importance of accessible UI design and how to implement practices that make your game playable for a wider audience. These foundational concepts will serve as the building blocks for implementing the UI in our game.
+
+In the next chapter, we will put these concepts into practice by implementing a UI system using Gum, a specialized UI framework that will help us create interactive menus, buttons, and other UI elements for our game.
+
+## Test Your Knowledge
+
+1. What are the two main types of game user interfaces, and how do they differ?
+
+ :::question-answer
+ The two main types are:
+ - **Diegetic UI**: Elements that exist within the game world itself and are part of the narrative (like health meters integrated into a character's suit or cockpit instruments in racing games). These enhance immersion by making UI feel like a natural part of the game world.
+ - **Non-diegetic UI**: Elements that exist outside the game world, overlaid on top of gameplay (like traditional menus, health bars in screen corners, score displays). While less immersive, they are often clearer and easier to read.
+ :::
+
+2. What are the some advantages of using a parent-child relationship in UI systems?
+
+ :::question-answer
+ - **Inheritance of properties**: visual states cascade parent to children.
+ - **Relative positioning**: Child elements are positioned relative to their parents.
+ - **Simplified state management**: Parent states affect children automatically.
+ - **Batch operations**: Update and draw calls propagate through the hierarchy.
+ - **Logical grouping**: Mirrors the conceptual organization of UI elements.
+ :::
+
+3. How do anchoring and docking differ in UI layout systems?
+
+ :::question-answer
+ - **Anchoring**: Positions UI elements relative to specific reference points on their parent (like TopLeft, Center, BottomRight) without changing the element's size. An element's position coordinates become relative to the chosen anchor point.
+ - **Docking**: Takes anchoring further by also adjusting an element's size to fill available space. For example, docking to the top means the element fills the parent horizontally while staying at the top, while "Fill" docking means the element expands to fill the entire parent area.
+ :::
+
+4. What are some accessibility considerations that should be implemented in game UI systems?
+
+ :::question-answer
+ - **Visual accessibility**: High contrast colors, not relying solely on color for information, adjustable text size and UI scaling, and internationalization support.
+ - **Input accessibility**: Support for multiple input methods and reduced precision requirements.
+ - **Testing practices**: Ensure the UI works with keyboard only navigation, without sound, and with simulated visual impairments.
+ :::
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/files/ui.wav b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/files/ui.wav
new file mode 100644
index 00000000..63e8941e
Binary files /dev/null and b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/files/ui.wav differ
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/images/mgcb-editor.png b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/images/mgcb-editor.png
new file mode 100644
index 00000000..81e51874
Binary files /dev/null and b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/images/mgcb-editor.png differ
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/images/pause-unstyled.png b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/images/pause-unstyled.png
new file mode 100644
index 00000000..3a592da4
Binary files /dev/null and b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/images/pause-unstyled.png differ
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/images/title-unstyled.png b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/images/title-unstyled.png
new file mode 100644
index 00000000..9e4ad987
Binary files /dev/null and b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/images/title-unstyled.png differ
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/index.md b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/index.md
new file mode 100644
index 00000000..e947c249
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/index.md
@@ -0,0 +1,688 @@
+---
+title: "Chapter 20: Implementing UI with Gum"
+description: "Learn how to integrate and use the Gum UI framework to create functional menus, buttons, and sliders for your MonoGame projects."
+---
+
+In the [previous chapter](../19_user_interface_fundamentals/index.md) we explored the fundamental concepts of user interface design. Now we are ready to put these principles into practice by implementing a UI system for our game. While it is possible to build a UI system from scratch, we will take advantage of Gum, a specialized UI framework that simplifies many of the complex aspects of UI implementation.
+
+In this chapter you will:
+
+* Install and configure the Gum NuGet package.
+* Learn about Gum's core concepts including Forms and Visuals
+* Implement UI elements for our game's title scene.
+* Create a pause menu for the gameplay scene.
+* Handle input from keyboard, mouse, and gamepads
+* Integrate the UI system with our existing game architecture.
+
+> [!IMPORTANT]
+> While GUM is used in this tutorial it is only one of many UI libraries available to the MonoGame community, some notable others are [EmptyKeys](https://github.com/EmptyKeys/UI_Engines), [GeonBit.UI](https://github.com/RonenNess/GeonBit.UI), as well as entire Game Frameworks/Engines like [Nez}(https://github.com/prime31/Nez) that have their own built in UI systems.
+>
+> Check out the [MonoGame Resources](https://monogame.net/resources/) page, as well as [awesome-monogame](https://github.com/aloisdeniel/awesome-monogame) from [Alois Deniel](https://github.com/aloisdeniel) for even more community offerings.
+
+## What is Gum?
+
+Gum is a powerful UI layout engine and framework. It provides a flexible, efficient system capable of producing virtually any UI layout you might need in your games. While originally developed alongside the FlatRedBall game engine, Gum has evolved to work seamlessly with multiple platforms, including MonoGame, which we will be using in this tutorial.
+
+### Why Use Gum?
+
+Creating a UI system from scratch requires solving many complex problems:
+
+1. **Layout Management**: Calculating positions for elements that need to adapt to different screen sizes or content changes.
+2. **Input Handling**: Detecting and responding to mouse, keyboard, and gamepad inputs across multiple UI elements.
+3. **Visual State Management**: Changing appearances based on user interactions (hovering, focusing, clicking).
+4. **Component Hierarchy**: Managing parent-child relationships between elements.
+
+Gum addresses these challenges with ready-made solutions, allowing us to focus on the specific needs of our game rather than reinventing the UI wheel. While MonoGame provides the basic tools for drawing graphics and detecting input, it does not include high-level UI abstractions; this is where tools like Gum fill the gap.
+
+> [!IMPORTANT]
+> This tutorial uses the Gum NuGet package to help with layout and responding to user interactions. This tutorial does not require the use of the Gum editor, we will be doing everything in code.
+>
+> Keep in mind, that while it is possible to build a full UI system without any external dependencies, creating a layout engine is complicated and beyond the scope of this tutorial. Instead, we will be taking advantage of the Gum NuGet package.
+>
+> Gum is a powerful system enabling the creation of virtually any game UI, and we will be covering some of the basics of its use in this tutorial. The full Gum documentation can be found here: [https://docs.flatredball.com/gum/code/monogame](https://docs.flatredball.com/gum/code/monogame)
+
+## Gum Concepts
+
+Before we dive into implementation, we will explore the core concepts that Gum provides. Gum simplifies UI development by providing ready-made controls and layout systems that would otherwise require significant effort to build from scratch.
+
+### Understanding Gum's Structure
+
+Gum organizes UI elements in a hierarchical tree structure, similar to how HTML organizes web elements or how GUI frameworks like WPF or JavaFX structure their interfaces. This hierarchy consists of:
+
+1. **The Root Element**: The topmost container in the hierarchy that serves as the entry point for all UI elements.
+2. **Containers**: Elements that can hold other elements (like panels or screens).
+3. **Controls**: Interactive elements that respond to user input (like buttons or sliders).
+4. **Visuals**: The actual graphical representations of UI elements (like text, images, or shapes).
+
+When a game using Gum runs, this hierarchy is maintained in memory, with each element knowing its parent and children. The framework automatically handles the flow of events through this hierarchy and manages the drawing of elements according to their positions in the tree.
+
+### Gum Root Element
+
+All Gum elements must be directly or indirectly added to Gum's root container. This can be done directly wth the `AddToRoot()` method:
+
+```cs
+// Creating a panel and adding it to the root
+Panel mainMenuPanel = new Panel();
+mainMenuPanel.AddToRoot();
+```
+
+Or it can be done indirectly by adding a control as a child of an element that has been added to Gum's root container:
+
+```cs
+// Creating a panel and adding it to the root
+Panel mainMenuPanel = new Panel();
+mainMenuPanel.AddToRoot();
+
+// Creating a button and adding it as a child element of the panel
+// which indirectly connects it to Gum's root container
+Button startButton = new Button();
+mainMenuPanel.AddChild(startButton);
+```
+
+Gum's root element can also be cleared at any time to remove all UI elements:
+
+```cs
+// Clear all children from Gum's root container.
+GumService.Default.Root.Children.Clear();
+```
+
+This can be useful when navigating between different scenes to ensure UI elements do not persist from previous scenes:
+
+```cs
+public class GameScene
+{
+ public override void Initialize()
+ {
+ // Clear all children from Gum's root container that may have been added
+ // during the previous scene
+ GumService.Default.Root.Children.Clear();
+
+ // Now that it has been cleared, initialize the UI for this scene
+ InitializeUI();
+ }
+}
+```
+
+### Anchoring
+
+In the previous chapter we discussed [anchoring](../19_user_interface_fundamentals/index.md#anchoring), a fundamental UI concept that allows you to position elements relative to specific reference points of their parents. Gum supports anchoring of Forms controls through the `Anchor()` method.
+
+```cs
+// Creating a panel and adding it to the root
+Panel mainMenuPanel = new Panel();
+mainMenuPanel.AddToRoot();
+
+// Creating a button and adding it as a child of the panel
+// anchored ot the bottom-left of the panel
+Button startButton = new Button();
+startButton.Anchor(Gum.Wireframe.Anchor.BottomLeft);
+mainMenuPanel.AddChild(startButton);
+```
+
+The following anchor types are supported by Gum:
+
+| Anchor | Gum Value |
+| ----------- | ---------------------------------- |
+| TopLeft | `Gum.Wireframe.Anchor.TopLeft` |
+| Top | `Gum.Wireframe.Anchor.Top` |
+| TopRight | `Gum.Wireframe.Anchor.TopRight` |
+| Left | `Gum.Wireframe.Anchor.Left` |
+| Center | `Gum.Wireframe.Anchor.Center` |
+| Right | `Gum.Wireframe.Anchor.Right` |
+| BottomLeft | `Gum.Wireframe.Anchor.BottomLeft` |
+| Bottom | `Gum.Wireframe.Anchor.Bottom` |
+| BottomRight | `Gum.Wireframe.Anchor.BottomRight` |
+
+### Docking
+
+In the previous chapter, we also discussed [docking](../19_user_interface_fundamentals/index.md#docking), a fundamental UI concept that adjusts an element's size to fill the available space. Gum supports the docking of Forms controls through their `Dock()` method.
+
+```cs
+// Creating a panel and adding it to the root
+Panel mainMenuPanel = new Panel();
+mainMenuPanel.AddToRoot();
+
+// Docking the panel to fill the entire root space
+mainMenuPanel.Dock(Gum.Wireframe.Dock.Fill);
+```
+
+The following docking modes are supported by Gum:
+
+| Anchor | Gum Value | Description |
+| ---------------- | ------------------------------------- | ----------------------------------------------------------------------------------- |
+| Top | `Gum.Wireframe.Dock.Top` | Anchors to the top edge and fills horizontally. |
+| Left | `Gum.Wireframe.Dock.Left` | Anchors to the left edge and fills vertically. |
+| Right | `Gum.Wireframe.Dock.Right` | Anchors to the ridge edge and fills vertically. |
+| Bottom | `Gum.Wireframe.Dock.Bottom` | Anchors to the bottom edge and fills horizontally. |
+| Fill | `Gum.Wireframe.Dock.Fill` | Anchors to the center and fills the entire parent area vertically and horizontally. |
+| FillHorizontally | `Gum.Wireframe.Dock.FillHorizontally` | Stretches across the parent's width, filling horizontally. |
+| FillVertically | `Gum.Wireframe.Dock.FillVertically` | Stretches across the parent's height, filling vertically. |
+| SizeToChildren | `Gum.Wireframe.Dock.SizeToChildren` | Automatically sizes vertically and horizontally based on contained child element. |
+
+### Forms and Visuals
+
+Gum provides two types of objects: **Forms** and **Visuals**.
+
+* Forms controls are typical interactive UI elements such as buttons, sliders, and text boxes that handle user interaction through mouse, gamepad, and keyboard inputs. These controls come with built-in functionality; a button responds visually when focused, while a slider changes its value when clicked on its *track*. By using these standardized components, you can maintain consistency throughout your UI implementation.
+
+* Forms controls provide customization through their `Visual` property, which serves as a gateway to modifying their appearance and layout. With this property, you can move, resize, restyle, and even completely replace visuals through code. As we will see when building our UI in the next chapter, this separation between functionality and presentation allows us to create consistent behaviors while adapting the visual style to match our game's aesthetic.
+
+For now, we will examine some of the Forms control types we will use in this chapter.
+
+#### Panel
+
+Panels serve as invisible containers that group related UI elements together. Unlike visible elements that display graphics, panels focus on organization and layout management.
+
+A panel provides several key functions:
+
+* Groups related elements for easier management.
+* Controls visibility for entire sections of UI at once.
+* Establishes a coordinate system for child elements.
+* Provides a foundation for layout management.
+
+Panels are especially useful for creating distinct UI screens, by toggling the visibility of different panels you can implement complete UI state changes with minimal code:
+
+```cs
+// Change the state of the UI by hiding one panel and showing another.
+mainMenuPanel.IsVisible = false;
+optionsPanel.IsVisible = true;
+```
+
+A common pattern is to set a panel's docking to `Fill`, which makes it span the entire available area:
+
+```cs
+// Make the panel fill the entire screen
+mainMenuPanel.Dock(Gum.Wireframe.Dock.Fill);
+```
+
+This creates a consistent coordinate space for all child elements, allowing them to be positioned relative to the screen.
+
+#### Button
+
+The `Button` Forms control type is the primary interactive control for triggering actions in your UI.
+
+Buttons provide:
+
+* Responses to clicks from mouse, touch, keyboard, or gamepad input.
+* Visual feedback when focused or hovered.
+* Raises a `Click` event when activated.
+
+Buttons can be positioned using anchoring to create layouts that adapt to different screen sizes:
+
+```cs
+// Creating a button that is anchored to the bottom left.
+Button startButton = new Button;
+startButton.Anchor(Gum.Wireframe.Anchor.BottomLeft);
+
+// Set the X and Y position so it is 20px from the left edge
+// and 20px from the bottom edge.
+startButton.Visual.X = 20;
+startButton.Visual.Y = -20;
+```
+
+The `Click` event is raised whenever the button is activated and provides a standard way to respond regardless of input device:
+
+```cs
+startButton.Click += (sender, args) =>
+{
+ // Handle button click...
+ StartGame();
+};
+```
+
+> [!NOTE]
+> The `Click` event for a button is triggered automatically when it is left-clicked by a mouse or pushed via touch controls. For keyboard and gamepad input, it can only be triggered when the button has focus, which typically happens when the player navigates to it using the tab key or controller.
+
+#### Slider
+
+The `Slider` Forms control type allows users to select a numeric value from a continuous range. A slider:
+
+* Displays and modifies a `Value` property constrained between a `Minimum` and `Maximum` value.
+* Responds to mouse clicks on its track or by dragging its thumb.
+* Supports keyboard and gamepad input for incremental adjustments.
+* Raises events when its value changes.
+
+Basic slider setup includes defining its range and establishing event handlers:
+
+```cs
+Slider volumeSlider = new Slider();
+volumeSlider.Minimum = 0.0f;
+volumeSlider.Maximum = 1.0f;
+volumeSlider.Value = 0.5f;
+volumeSlider.SmallChange = 0.1f;
+volumeSlider.LargeChange = 0.2f;
+```
+
+The `SmallChange` property sets the increment for keyboard and gamepad adjustments, while the `LargeChange` property determines the increment when clicking directly on the slider *track*.
+
+Sliders provide several events for different interaction scenarios:
+
+* `ValueChanged`: Fires continuously as the value changes (useful for live previews).
+* `ValueChangeCompleted`: Fires once when the user finishes adjusting the value (useful for applying final settings).
+
+```cs
+volumeSlider.ValueChanged += (sender, arg) =>
+{
+ // Handle value changed event...
+ UpdateVolume(volumeSlider.Value);
+};
+
+volumeSlider.ValueChangedCompleted += (sender, arg) =>
+{
+ // Handle value change completed event...
+ UpdateVolume(volumeSlider.Value);
+
+ // Useful to do things like this here since this fires once
+ // the slider value change has completed so it is not constantly
+ // triggering ui sound effects.
+ PlayUISoundEffect();
+};
+```
+
+### Property Changes vs States
+
+Gum allows you to customize visuals in two ways:
+
+* Direct property assignment
+* Using states.
+
+With simple property changes, you can directly assign values in code. For example, the following code example changes the width of a button:
+
+```cs
+startButton.Visual.Width = 100;
+```
+
+Direct property assignment works well for initial setup, such as positioning elements or setting their dimensions when first creating your UI. However, when you need visual elements to respond to user interactions (like highlighting a button when it is focused), a different approach is required.
+
+For these dynamic changes, Gum uses a system of **states** (implemented as `StateSave` objects), each Forms control maintains a collection of named states that are automatically applied in response to specific user interactions. When a button becomes focused, for instance, Gum looks for an applies a state named "Focused" to alter its appearance.
+
+> [!NOTE]
+> In the next chapter during the customization pass, we will create states to visually indicate when controls are focused, providing clear feedback to the player.
+
+## Updating Our Game To Use Gum
+
+Now that we have covered the core UI concepts and how Gum will help implement them, we can integrate Gum into our game project. We will add the framework, initialize it, and prepare it for use in our scenes.
+
+For now we will use the default styling in Gum to quickly iterate and build the UI and do a customization styling pass in the next chapter.
+
+### Adding the Gum NuGet Package
+
+Before we can use Gum in our project, we first need to add it using NuGet. NuGet is a package manager for .NET projects that allows you to add third-party libraries into your project, similar to how we [created and added our own class library](../04_creating_a_class_library/index.md).
+
+To add the Gum NuGet package to our game project, follow the instructions below based on your development environment:
+
+#### [Visual Studio Code](#tab/vscode)
+
+To add the Gum NuGet package in Visual Studio Code:
+
+1. In the [*Solution Explorer*](../02_getting_started/index.md#install-the-c-dev-kit-extension) panel, right-click the `DungeonSlime` project.
+2. Choose `Add NuGet Package` from the context menu.
+3. Enter `Gum.MonoGame` in the `Add NuGet Package` search prompt and press Enter.
+4. When the search finishes, select the `Gum.MonoGame` package in the results
+5. When prompted for a version choose version `2025.5.1.1`.
+
+#### [Visual Studio 2022](#tab/vs2022)
+
+To Add the Gum NuGet package in Visual Studio 2022:
+
+1. In the *Solution Explorer* panel, right-click the *DungeonSlime* project.
+2. Choose `Manage Nuget Packages...` from the context menu.
+3. In the NuGet Package Manager window, select the `Browse` tab if it is not already selected.
+4. In the search box, enter `Gum.MonoGame`.
+5. Select the "Gum.MonoGame" package from the search results.
+6. On the right, in the version dropdown, select version `2025.5.1.1` and click the "Install" button.
+
+#### [dotnet CLI](#tab/dotnetcli)
+
+To add the Gum NuGet package using the dotnet CLI:
+
+1. Open a Command Prompt or Terminal window in the same folder as the `DungeonSlime.csproj` project file.
+2. Enter the following command:
+
+ ```sh
+ dotnet add DungeonSlime.csproj package Gum.MonoGame --version 2025.5.1.1
+ ```
+
+---
+
+> [!TIP]
+> You can verify the package was successfully added by examining your `DungeonSlime.csproj` file, which should now contain a reference like:
+>
+> ```xml
+>
+> ```
+
+> [!IMPORTANT]
+> This tutorial uses version `2025.5.5.1` of Gum, which is the latest version of Gum as of this writing. That exact version is specified to use in the section above when installing the NuGet package to ensure compatibility throughout this tutorial. If there are newer versions of Gum available, please consult the [Gum documentation](https://docs.flatredball.com/gum/gum-tool/breaking-changes) before updating in case there are any breaking changes from the code that is presented in this tutorial.
+
+### Adding UI Sound Effect
+
+To make our UI more responsive and engaging, we will add audio feedback that plays when players interact with buttons and other UI elements. Sound effects provide immediate confirmation that an input has been recognized, creating a more engaging experience.
+
+First, download the UI sound effect by right-clicking the following link and saving it as `ui.wav` in the game project's `Content/audio` folder:
+
+* [ui.wav](./files/ui.wav){download}
+
+Next, add this sound effect to your content project using the MGCB Editor:
+
+1. Open the `Content.mgcb` content project file in the MGCB Editor.
+2. Right-click the `audio` folder and choose `Add > Existing Item...`.
+3. Navigate to and select the `ui.wav` file you just downloaded.
+4. In the Properties panel, verify that the `Processor` is set to `Sound Effect`.
+5. Save the changes and close the MGCB Editor.
+
+|  |
+| :---------------------------------------------------------------------------------------------: |
+| **Figure 20-1: The MGCB Editor with ui.wav added to the audio folder** |
+
+We will load and use this sound effect in our UI implementation to provide auditory feedback when players interact with buttons and sliders.
+
+### Initializing Gum
+
+With the Gum NuGet package added to our project, we need to initialize Gum in our game, this will enable the UI system and configure input handling for our controls. Since this is an initialization that only needs to happen once, we can make the necessary changes to the `Game1` class.
+
+First, open the `Game1.cs` file and add the following new using statements to the top:
+
+[!code-csharp[](./snippets/game1/usings.cs?highlight=4-5)]
+
+Next, add the following method to the `Game1` class to encapsulate the initializations of the Gum UI service:
+
+[!code-csharp[](./snippets/game1/initializegum.cs)]
+
+Finally, update the [**Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize) method to call the `InitializeGum` method we just created:
+
+[!code-csharp[](./snippets/game1/initialize.cs?highlight=8-9)]
+
+The following is a breakdown of this initialization process:
+
+1. **Basic Initialization**: `GumService.Default.Initialize(this)` sets up the Gum system with our game instance. This is required for any gum project.
+
+ > [!NOTE]
+ > We only need to pass our [**Game**](xref:Microsoft.Xna.Framework.Game) instance since we are using Gum as a code-first approach. Gum also offers a visual editor that creates Gum project files. When using the editor, you will need to also pass the Gum Project file here.
+
+2. **Content Loading**: Gum needs to be made aware of which content manager to use to load assets through the content pipeline. By setting `GumService.Default.ContentLoader.XnaContentManager = Core.Content`, we tell Gum to use our game's content manager when loading assets. By using the game's existing content manager, Gum also gets the benefit of the caching that the content manager performs when loading assets.
+3. **Input Configuration**:
+ * By default, all Forms controls automatically respond to mouse and touch screen input devices. We need to explicitly register keyboard and gamepad input devices by using th `FrameworkElement.KeyboardsForUiControl` and `Framework.GamePadsForUiControl` properties.
+ * By default, Forms controls will automatically respond to tab and shift-tab for navigation. By using the `FrameworkElement.TabKeyCombos` and `FrameworkElement.TabReverseKeyCombos` properties, we can add additional key combinations for tabbing. Here map the Up arrow for reverse tabbing and the Down arrow for forward tabbing.
+
+ > [!TIP]
+ > If you prefer different navigation keys, you can remove the built-in Tab/Shift+Tab navigation.
+ >
+ > Simply call these methods before adding your custom combinations:
+ >
+ > ```cs
+ > FrameworkElement.TabKeyCombos.Clear();
+ > FrameworkElement.TabReverseKeyCombos.Clear();
+ > ```
+
+4. **UI Scaling**: Gum allows us to independently scale the UI regardless of the resolution of the game. Our game is set to a resolution of 1280x720, however as we will see during the styling section later, the UI assets created were done at one-fourth the size to reduce the size of the texture atlas. Here, we use the `GumService.Default.CanvasWidth` and `GumService.Default.CanvasHeight` properties to set the canvas size to one-fourth that of our game's resolution. Then using by setting the `GumService.Default.Renderer.Camera.Zoom` property to four, we effectively make it render the UI at full resolution.
+
+Gum is now fully initialized and we can use it in our scenes to add UI to our game.
+
+### Adding TitleScene UI
+
+With Gum added and initialized in our game, we can now implement UI elements for our title scene. We will create panels for both the main menu and options menu, implement the necessary event handlers, and integrate everything with our existing title scene.
+
+> [!NOTE]
+> When adding these sections one by one, you may see compiler errors until all sections are in place. This is normal, as some parts of the code will reference fields or methods that haven't been added yet. Once all sections are complete, these errors will resolve.
+
+First, open the *TitleScene.cs* file in the game project and add the following using declarations to the top of the `TitleScene` class:
+
+[!code-csharp[](./snippets/titlescene/usings.cs?highlight=1,3,6-8)]
+
+Next, add the following fields to the `TitleScene` class:
+
+[!code-csharp[](./snippets/titlescene/fields.cs)]
+
+#### Creating the Title Panel
+
+First, create a new method that builds our main menu panel with start and options buttons.
+
+Add the following method to the `TitleScene` class:
+
+[!code-csharp[](./snippets/titlescene/createtitlepanel.cs)]
+
+Our title panel includes two buttons positioned at the bottom corners of the screen. The "Start" button will allow players to begin the game while the "Options" button will hide the main menu and display the options menu.
+
+> [!NOTE]
+> Notice how we use `Anchor` to position the buttons relative to the panel's edges, with the "Start" button anchored at the bottom left and the "Options" button anchored at the bottom right. Then the positioning of the elements is adjusted relative to its anchor point.
+
+Each button registers a `Click` event handler to respond when the players selects it, we should implement the event handler method for these buttons next. First we will implement the handler for the "Start" button. Add the following method to the `TitleScene` class after the `CreateTitlePanel` method:
+
+[!code-csharp[](./snippets/titlescene/handlestartclicked.cs)]
+
+When the "Start" button is clicked and this method is called, it will play the UI sound effect for auditory feedback then change the scene tot he game scene so the player can start playing the game.
+
+Next is the handler for the "Options" button. Add the following method to the `TitleScene` class after the `HandleStartClicked` method:
+
+[!code-csharp[](./snippets/titlescene/handleoptionsclicked.cs)]
+
+When the "Options" button is clicked and this method is called, it will play the UI sound effect for auditory feedback then hide the title panel and show the options panel.
+
+#### Creating the Options Panel
+
+Next, we will create the options panel with sliders to adjust the volume for music and sound effects.
+
+Add the following method to the `TitleScene` class:
+
+[!code-csharp[](./snippets/titlescene/createoptionspanel.cs)]
+
+This panel includes a text label, two sliders for adjusting audio volumes, and a back button for returning to the main menu. The panel is initially invisible since we start on the main menu. Both the "Music Volume" slider and the "Sound Effects Volume" slider register events to be called when the value of the sliders change and when the value change has been completed. The "Back" button registers a click event similar to the ones from the main menu.
+
+Now we should implement the event handlers for these controls. First, we will implement the handler for when the value of the sound effect volume slider changes. Add the following method to the `TitleScene` class after the `CreateOptionsPanel` method:
+
+[!code-csharp[](./snippets/titlescene/handlesfxsliderchanged.cs)]
+
+When the value of the "Sound Effects Volume" slider changes and this method is called, a reference to the slider is captured and then the the global sound effect volume is adjusted based on the value of the slider.
+
+Next is the handler when the "Sound Effects Volume" slider has completed a value change. Add the following method to the `TitleScene` class after the `HandleSfxSliderChanged` method:
+
+[!code-csharp[](./snippets/titlescene/handlesfxsliderchangecompleted.cs)]
+
+When the value of the "Sound Effects Volume" slider has completed a change and this method is called, it plays the UI sound effect to provide auditory feedback so the player can hear the difference in volume.
+
+After this is the handler for when the "Music Volume" slider value changes. Add the following method to the `TitleScene` class after the `HandleSfxSliderChangeCompleted` method:
+
+[!code-csharp[](./snippets/titlescene/handlemusicslidervaluechanged.cs)]
+
+Similar to how we handled the "Sound Effect Volume" slider value changes, when the "Music Volume" slider value changes and this method is called, a reference to the slider is captured and then the global music volume is adjusted based on the value of the slider.
+
+Next is the handler when the "Music Volume" slider value has completed a value change. Add the following method to the `TitleScene` class after the `HandleMusicSliderValueChanged` method:
+
+[!code-csharp[](./snippets/titlescene/handlemusicslidervaluechangecompleted.cs)]
+
+When the value of the "Music Volume" slider has completed a change, the UI sound effect is played to provide auditory feedback.
+
+Finally, we need to add the handler for when the "Back" button is clicked on the options panel. Add the following method to the `TitleScene` class after the `HandleMusicSliderValueChangeCompleted` method:
+
+[!code-csharp[](./snippets/titlescene/handleoptionsbuttonback.cs)]
+
+This method plays the UI sound effect for auditory feedback, then hides the options panel and shows the title panel.
+
+> [!TIP]
+> Notice that for both sliders, we registered a method for the `ValueChangeCompleted` event. This is so we can play the UI sound effect only when the player has finished adjusting the slider value. If we had instead played the UI sound effect in the `ValueChanged` event, then the UI sound effect would trigger constantly while the slider is being adjusted if using a mouse to drag it.
+
+#### Initializing the UI
+
+Now that we have implemented the methods that will create both the main menu panel and the options menu panel, we need to implement the main UI initializations method that will call them. Add the following method to the `TitleScene` class after the `HandleOptionsButtonBack` method:
+
+[!code-csharp[](./snippets/titlescene/initializeui.cs)]
+
+This method first clears any existing UI elements from Gum's root container to prevent duplication, then calls our panel creation methods to build the complete interface.
+
+#### Integrating with the Game Loop
+
+Finally, we need to integrate our UI initialization, update, and draw with the scene's lifecycle. First, add the call to `InitializeUI()` in the `Initialize` method by updating it to the following:
+
+[!code[](./snippets/titlescene/initialize.cs?highlight=27)]
+
+Next, update the `LoadContent` method to load the sound effect that will be used as auditory feedback for the UI:
+
+[!code[](./snippets/titlescene/loadcontent.cs?highlight=12-13)]
+
+Next modify the `Update` method to include Gum's update logic:
+
+[!code[](./snippets/titlescene/update.cs?highlight=14)]
+
+Finally, the `Draw` method needs to be updated to:
+
+1. Only show the text for the game title when the title panel is visible
+2. Add Gum's drawing call to draw the user interface
+
+Update the `Draw` method to the following:
+
+[!code[](./snippets/titlescene/draw.cs?highlight=10-34,36)]
+
+With these changes, our UI system is now fully integrated into the scene's game loop. Gum updates its controls in the `Update` method and draws them in the `Draw` method. This produces a fully functional title screen with buttons that allows players to start the game or adjust audio settings.
+
+|  |
+| :--------------------------------------------------------------------------------: |
+| **Figure 20-2: Title screen with default Gum buttons** |
+
+> [!NOTE]
+> You may notice that the UI elements currently use Gum's default styling, which does not match our game's visual theme. We will explore customizing these controls to match our game's visual style in the next chapter.
+
+### Adding GameScene UI
+
+Now that we have setup the UI for the title scene, we will add a pause menu to our game scene. This UI will start invisible but will be shown when the player presses the escape key. For consistency, we will implement the UI for the game scene in the same order that we implemented the UI for the title scene.
+
+> [!NOTE]
+> When adding these sections one by one, you may see compiler errors until all sections are in place. This is normal, as some parts of the code will reference fields or methods that haven't been added yet. Once all sections are complete, these errors will resolve.
+
+First, open the *GameScene.cs* file in the game project and add the following using declarations to the top of the `GameScene` class.
+
+[!code-csharp[](./snippets/gamescene/usings.cs?highlight=2-3,8-10)]
+
+Next, add the following fields to the `GameScene` class:
+
+[!code-csharp[](./snippets/gamescene/fields.cs)]
+
+#### Pausing the Game
+
+To pause the game, first we will create a method that makes the pause panel visible. Add the following method to the `GameScene` class after the `CheckGamePadInput` method:
+
+[!code-csharp[](./snippets/gamescene/pausegame.cs)]
+
+Next, update the `CheckKeyboardInput` method so that when the escape key is pressed, instead of returning to the title scene, we now pause the game:
+
+[!code-csharp[](./snippets/gamescene/checkkeyboardinput.cs?highlight=6-10)]
+
+Finally, update the `CheckGamePadInput` method so that when the start button is pressed, it pauses the game:
+
+[!code-csharp[](./snippets/gamescene/checkgamepadinput.cs?highlight=6-10)]
+
+#### Creating the Pause Panel
+
+Next, we will create a method that builds our pause panel with resume and quit buttons. Add the following method to the `GameScene` class after the `LoadContent` method:
+
+[!code-csharp[](./snippets/gamescene/createpausepanel.cs)]
+
+Now we should implement the event handlers for these controls. First, we will implement the handler for the "Resume" button. Add the following method to the `GameScene` class after the `CreatePausePanel` method:
+
+[!code-csharp[](./snippets/gamescene/handleresumebuttonclicked.cs)]
+
+This method plays the UI sound effect for auditory feedback and then hides the pause panel so that the game can resume.
+
+Next is the handler for the "Quit" button. Add the following method to the `GameScene` class after the `HandleResumeButtonClicked` method:
+
+[!code-csharp[](./snippets/gamescene/handlequitbuttonclicked.cs)]
+
+This method as well plays the UI sound effect for auditory feedback, then quits the game by changing scenes back to the title scene.
+
+#### Initializing the Game UI
+
+Now that we have implemented the method to create the pause panel, we can implement the main UI initializations method that will call them. Add the following method to the `GameScene` class after the `HandleQuitButtonClicked` method:
+
+[!code-csharp[](./snippets/gamescene/initializeui.cs)]
+
+Just like with the `TitleScene`, we first clear any existing UI elements from Gum's root before creating the UI elements for this scene.
+
+#### Integrating with the Game Loop for the GameScreen
+
+Finally, we need to integrate our UI initialization, update, and draw with the scene's lifecycle. First add the call to `InitializeUI()` in the `Initialize` method by updating it to the following:
+
+[!code-csharp[](./snippets/gamescene/initialize.cs?highlight=38)]
+
+Next, update the `LoadContent` method to load the sound effect that will be used as auditory feedback for the UI:
+
+[!code-csharp[](./snippets/gamescene/loadcontent.cs?highlight=27-28)]
+
+Next, modify the `Update` method to include Gum's update logic and to only update the game if it is not paused. We will use the visibility of the pause menu to determine if the game is paused or not:
+
+[!code-csharp[](./snippets/gamescene/update.cs?highlight=3-10)]
+
+Finally, add Gum's drawing call to the end fo the `Draw` method:
+
+[!code-csharp[](./snippets/gamescene/draw.cs?highlight=9-10)]
+
+With these changes, the pause menu is now fully integrated into the game scene's game loop. Gum updates its controls during the `Update` method and draws them during the `Draw` method. If the game is paused, as determined by the `IsVisible` property of the pause menu, then updating the actual game logic is skipped.
+
+|  |
+| :---------------------------------------------------------------------------------------------------------: |
+| **Figure 20-3: The pause menu during the game scene with default Gum buttons** |
+
+## Conclusion
+
+In this chapter, you accomplished the following:
+
+* Add and configure the Gum NuGet package in your project.
+* Understand key Gum concepts like Forms controls and Visuals.
+* Create and position UI elements using anchoring and docking.
+* Implement interactive controls like buttons and sliders.
+* Handle user input from various input devices.
+* Create transitions between different UI screens.
+* Integrate the UI system with the game's scene architecture.
+
+While this UI is now functional, you may have noticed that it uses Gum's default styling which does not match our game's visual theme. In the next chapter, we will learn how to customize the appearance of our UI elements to create a cohesive visual style that complements our game's aesthetic.
+
+## Test Your Knowledge
+
+1. What are the two main types of objects in Gum, and how do they differ?
+
+ :::question-answer
+ The two main types are:
+
+ * **Forms**: Interactive UI elements like buttons, sliders, and panels that handle user input. They provide built-in functionality for common UI interactions.
+ * **Visuals**: Display elements like TextRuntime, ColoredRectangleRuntime, and NineSliceRuntime that are used to render graphics. They have no built-in interaction behavior but can be customized visually.
+
+ Forms controls contain Visuals, accessible through the `Visual` property, creating a separation between functionality and presentation.
+ :::
+
+2. How does Gum handle the parent-child relationship of UI elements, and why is this important?
+
+ :::question-answer
+ Gum implements parent-child relationships through a hierarchical structure where:
+
+ * UI elements must be connected to the root container to be visible
+ * Children can be added directly to a parent's Visual.Children collection
+ * Position coordinates of child elements are relative to their parent
+ * Property changes like visibility cascade from parent to children
+
+ This relationship is important because it allows for organizing related UI elements as groups, controlling entire sections of UI with a single property change, and positioning elements relative to their container rather than absolute screen coordinates.
+ :::
+
+3. What are the two ways to customize the appearance of Gum UI elements?
+
+ :::question-answer
+ The two ways to customize Gum UI elements are:
+
+ 1. **Direct property assignment**: Setting properties directly in code (like `MyButton.Visual.Width = 100`). This works well for initial setup and static properties.
+ 2. **States**: Using Gum's state system (`StateSave` objects) to define different visual states that can be applied in response to specific conditions or events. States are automatically applied by Forms controls in response to user interactions (like focus or highlighting).
+
+ States are useful for dynamic changes that occur during gameplay, as they separate visual response logic from game logic.
+ :::
+
+4. What steps are necessary to integrate Gum's UI system with MonoGame's game loop?
+
+ :::question-answer
+ To integrate Gum with MonoGame's game loop:
+
+ 1. Initialize Gum in the game's Initialize method with `GumService.Default.Initialize(this)`
+ 2. Configure content loading by setting `GumService.Default.ContentLoader.XnaContentManager`
+ 3. Set up input handling by adding keyboards and gamepads to `FrameworkElement.KeyboardsForUiControl` and `FrameworkElement.GamePadsForUiControl`
+ 4. Call `GumService.Default.Update()` in the game's Update method
+ 5. Call `GumService.Default.Draw()` in the game's Draw method
+ 6. For scene transitions, clear existing UI elements with `GumService.Default.Root.Children.Clear()`
+
+ This ensures Gum can update and render UI elements in sync with the game's main loop.
+ :::
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/game1/initialize.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/game1/initialize.cs
new file mode 100644
index 00000000..f12e23ca
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/game1/initialize.cs
@@ -0,0 +1,13 @@
+protected override void Initialize()
+{
+ base.Initialize();
+
+ // Start playing the background music
+ Audio.PlaySong(_themeSong);
+
+ // Initialize the Gum UI service
+ InitializeGum();
+
+ // Start the game with the title scene.
+ ChangeScene(new TitleScene());
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/game1/initializegum.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/game1/initializegum.cs
new file mode 100644
index 00000000..53a7cca5
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/game1/initializegum.cs
@@ -0,0 +1,32 @@
+private void InitializeGum()
+{
+ // Initialize the Gum service
+ GumService.Default.Initialize(this);
+
+ // Tell the Gum service which content manager to use. We will tell it to
+ // use the global content manager from our Core.
+ GumService.Default.ContentLoader.XnaContentManager = Core.Content;
+
+ // Register keyboard input for UI control.
+ FrameworkElement.KeyboardsForUiControl.Add(GumService.Default.Keyboard);
+
+ // Register gamepad input for Ui control.
+ FrameworkElement.GamePadsForUiControl.AddRange(GumService.Default.Gamepads);
+
+ // Customize the tab reverse UI navigation to also trigger when the keyboard
+ // Up arrow key is pushed.
+ FrameworkElement.TabReverseKeyCombos.Add(
+ new KeyCombo() { PushedKey = Microsoft.Xna.Framework.Input.Keys.Up });
+
+ // Customize the tab UI navigation to also trigger when the keyboard
+ // Down arrow key is pushed.
+ FrameworkElement.TabKeyCombos.Add(
+ new KeyCombo() { PushedKey = Microsoft.Xna.Framework.Input.Keys.Down });
+
+ // The assets created for the UI were done so at 1/4th the size to keep the size of the
+ // texture atlas small. So we will set the default canvas size to be 1/4th the size of
+ // the game's resolution then tell gum to zoom in by a factor of 4.
+ GumService.Default.CanvasWidth = GraphicsDevice.PresentationParameters.BackBufferWidth / 4.0f;
+ GumService.Default.CanvasHeight = GraphicsDevice.PresentationParameters.BackBufferHeight / 4.0f;
+ GumService.Default.Renderer.Camera.Zoom = 4.0f;
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/game1/usings.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/game1/usings.cs
new file mode 100644
index 00000000..6b518e38
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/game1/usings.cs
@@ -0,0 +1,5 @@
+using DungeonSlime.Scenes;
+using Microsoft.Xna.Framework.Media;
+using MonoGameLibrary;
+using MonoGameGum;
+using MonoGameGum.Forms.Controls;
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/checkgamepadinput.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/checkgamepadinput.cs
new file mode 100644
index 00000000..0654bb9e
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/checkgamepadinput.cs
@@ -0,0 +1,13 @@
+private void CheckGamePadInput()
+{
+ // Get the gamepad info for gamepad one.
+ GamePadInfo gamePadOne = Core.Input.GamePads[(int)PlayerIndex.One];
+
+ // If the start button is pressed, pause the game
+ if (gamePadOne.WasButtonJustPressed(Buttons.Start))
+ {
+ PauseGame();
+ }
+
+ // Existing gamepad input code
+ // ...
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/checkkeyboardinput.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/checkkeyboardinput.cs
new file mode 100644
index 00000000..9952b252
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/checkkeyboardinput.cs
@@ -0,0 +1,13 @@
+ private void CheckKeyboardInput()
+ {
+ // Get a reference to the keyboard info
+ KeyboardInfo keyboard = Core.Input.Keyboard;
+
+ // If the escape key is pressed, pause the game.
+ if (Core.Input.Keyboard.WasKeyJustPressed(Keys.Escape))
+ {
+ PauseGame();
+ }
+
+ // Existing keyboard input code
+ // ...
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/createpausepanel.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/createpausepanel.cs
new file mode 100644
index 00000000..914ff97a
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/createpausepanel.cs
@@ -0,0 +1,41 @@
+private void CreatePausePanel()
+{
+ _pausePanel = new Panel();
+ _pausePanel.Anchor(Anchor.Center);
+ _pausePanel.Visual.WidthUnits = DimensionUnitType.Absolute;
+ _pausePanel.Visual.HeightUnits = DimensionUnitType.Absolute;
+ _pausePanel.Visual.Height = 70;
+ _pausePanel.Visual.Width = 264;
+ _pausePanel.IsVisible = false;
+ _pausePanel.AddToRoot();
+
+ var background = new ColoredRectangleRuntime();
+ background.Dock(Dock.Fill);
+ background.Color = Color.DarkBlue;
+ _pausePanel.AddChild(background);
+
+ var textInstance = new TextRuntime();
+ textInstance.Text = "PAUSED";
+ textInstance.X = 10f;
+ textInstance.Y = 10f;
+ _pausePanel.AddChild(textInstance);
+
+ _resumeButton = new Button();
+ _resumeButton.Text = "RESUME";
+ _resumeButton.Anchor(Anchor.BottomLeft);
+ _resumeButton.Visual.X = 9f;
+ _resumeButton.Visual.Y = -9f;
+ _resumeButton.Visual.Width = 80;
+ _resumeButton.Click += HandleResumeButtonClicked;
+ _pausePanel.AddChild(_resumeButton);
+
+ var quitButton = new Button();
+ quitButton.Text = "QUIT";
+ quitButton.Anchor(Anchor.BottomRight);
+ quitButton.Visual.X = -9f;
+ quitButton.Visual.Y = -9f;
+ quitButton.Width = 80;
+ quitButton.Click += HandleQuitButtonClicked;
+
+ _pausePanel.AddChild(quitButton);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/draw.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/draw.cs
new file mode 100644
index 00000000..e27645a3
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/draw.cs
@@ -0,0 +1,11 @@
+public override void Draw(GameTime gameTime)
+{
+ // Existing game draw code
+ // ...
+
+ // Always end the sprite batch when finished
+ Core.SpriteBatch.End();
+
+ // Draw the Gum UI
+ GumService.Default.Draw();
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/fields.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/fields.cs
new file mode 100644
index 00000000..da461fdc
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/fields.cs
@@ -0,0 +1,10 @@
+// A reference to the pause panel UI element so we can set its visibility
+// when the game is paused.
+private Panel _pausePanel;
+
+// A reference to the resume button UI element so we can focus it
+// when the game is paused.
+private Button _resumeButton;
+
+// The UI sound effect to play when a UI event is triggered.
+private SoundEffect _uiSoundEffect;
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/handlequitbuttonclicked.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/handlequitbuttonclicked.cs
new file mode 100644
index 00000000..7ab07c21
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/handlequitbuttonclicked.cs
@@ -0,0 +1,8 @@
+private void HandleQuitButtonClicked(object sender, EventArgs e)
+{
+ // A UI interaction occurred, play the sound effect
+ Core.Audio.PlaySoundEffect(_uiSoundEffect);
+
+ // Go back to the title scene.
+ Core.ChangeScene(new TitleScene());
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/handleresumebuttonclicked.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/handleresumebuttonclicked.cs
new file mode 100644
index 00000000..b840511e
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/handleresumebuttonclicked.cs
@@ -0,0 +1,8 @@
+private void HandleResumeButtonClicked(object sender, EventArgs e)
+{
+ // A UI interaction occurred, play the sound effect
+ Core.Audio.PlaySoundEffect(_uiSoundEffect);
+
+ // Make the pause panel invisible to resume the game.
+ _pausePanel.IsVisible = false;
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/initialize.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/initialize.cs
new file mode 100644
index 00000000..3225701a
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/initialize.cs
@@ -0,0 +1,39 @@
+public override void Initialize()
+{
+ // LoadContent is called during base.Initialize().
+ base.Initialize();
+
+ // During the game scene, we want to disable exit on escape. Instead,
+ // the escape key will be used to return back to the title screen
+ Core.ExitOnEscape = false;
+
+ Rectangle screenBounds = Core.GraphicsDevice.PresentationParameters.Bounds;
+
+ _roomBounds = new Rectangle(
+ (int)_tilemap.TileWidth,
+ (int)_tilemap.TileHeight,
+ screenBounds.Width - (int)_tilemap.TileWidth * 2,
+ screenBounds.Height - (int)_tilemap.TileHeight * 2
+ );
+
+ // Initial slime position will be the center tile of the tile map.
+ int centerRow = _tilemap.Rows / 2;
+ int centerColumn = _tilemap.Columns / 2;
+ _slimePosition = new Vector2(centerColumn * _tilemap.TileWidth, centerRow * _tilemap.TileHeight);
+
+ // Initial bat position will the in the top left corner of the room
+ _batPosition = new Vector2(_roomBounds.Left, _roomBounds.Top);
+
+ // Set the position of the score text to align to the left edge of the
+ // room bounds, and to vertically be at the center of the first tile.
+ _scoreTextPosition = new Vector2(_roomBounds.Left, _tilemap.TileHeight * 0.5f);
+
+ // Set the origin of the text so it is left-centered.
+ float scoreTextYOrigin = _font.MeasureString("Score").Y * 0.5f;
+ _scoreTextOrigin = new Vector2(0, scoreTextYOrigin);
+
+ // Assign the initial random velocity to the bat.
+ AssignRandomBatVelocity();
+
+ InitializeUI();
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/initializeui.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/initializeui.cs
new file mode 100644
index 00000000..ad704c03
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/initializeui.cs
@@ -0,0 +1,6 @@
+private void InitializeUI()
+{
+ GumService.Default.Root.Children.Clear();
+
+ CreatePausePanel();
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/loadcontent.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/loadcontent.cs
new file mode 100644
index 00000000..1b681aef
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/loadcontent.cs
@@ -0,0 +1,29 @@
+public override void LoadContent()
+{
+ // Create the texture atlas from the XML configuration file
+ TextureAtlas atlas = TextureAtlas.FromFile(Core.Content, "images/atlas-definition.xml");
+
+ // Create the slime animated sprite from the atlas.
+ _slime = atlas.CreateAnimatedSprite("slime-animation");
+ _slime.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+ _bat.Scale = new Vector2(4.0f, 4.0f);
+
+ // Create the tilemap from the XML configuration file.
+ _tilemap = Tilemap.FromFile(Content, "images/tilemap-definition.xml");
+ _tilemap.Scale = new Vector2(4.0f, 4.0f);
+
+ // Load the bounce sound effect
+ _bounceSoundEffect = Content.Load("audio/bounce");
+
+ // Load the collect sound effect
+ _collectSoundEffect = Content.Load("audio/collect");
+
+ // Load the font
+ _font = Core.Content.Load("fonts/04B_30");
+
+ // Load the sound effect to play when ui actions occur.
+ _uiSoundEffect = Core.Content.Load("audio/ui");
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/pausegame.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/pausegame.cs
new file mode 100644
index 00000000..a772e59a
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/pausegame.cs
@@ -0,0 +1,8 @@
+private void PauseGame()
+{
+ // Make the pause panel UI element visible.
+ _pausePanel.IsVisible = true;
+
+ // Set the resume button to have focus
+ _resumeButton.IsFocused = true;
+}
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/update.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/update.cs
new file mode 100644
index 00000000..0c7b5296
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/update.cs
@@ -0,0 +1,13 @@
+ public override void Update(GameTime gameTime)
+ {
+ // Ensure the UI is always updated
+ GumService.Default.Update(gameTime);
+
+ // If the game is paused, do not continue
+ if (_pausePanel.IsVisible)
+ {
+ return;
+ }
+
+ // Existing game update code
+ // ...
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/usings.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/usings.cs
new file mode 100644
index 00000000..1ed2b803
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/gamescene/usings.cs
@@ -0,0 +1,14 @@
+using System;
+using Gum.DataTypes;
+using Gum.Wireframe;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameGum;
+using MonoGameGum.Forms.Controls;
+using MonoGameGum.GueDeriving;
+using MonoGameLibrary;
+using MonoGameLibrary.Graphics;
+using MonoGameLibrary.Input;
+using MonoGameLibrary.Scenes;
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/createoptionspanel.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/createoptionspanel.cs
new file mode 100644
index 00000000..e88fbc8c
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/createoptionspanel.cs
@@ -0,0 +1,45 @@
+private void CreateOptionsPanel()
+{
+ _optionsPanel = new Panel();
+ _optionsPanel.Dock(Gum.Wireframe.Dock.Fill);
+ _optionsPanel.IsVisible = false;
+ _optionsPanel.AddToRoot();
+
+ var optionsText = new TextRuntime();
+ optionsText.X = 10;
+ optionsText.Y = 10;
+ optionsText.Text = "OPTIONS";
+ _optionsPanel.AddChild(optionsText);
+
+ var musicSlider = new Slider();
+ musicSlider.Anchor(Gum.Wireframe.Anchor.Top);
+ musicSlider.Visual.Y = 30f;
+ musicSlider.Minimum = 0;
+ musicSlider.Maximum = 1;
+ musicSlider.Value = Core.Audio.SongVolume;
+ musicSlider.SmallChange = .1;
+ musicSlider.LargeChange = .2;
+ musicSlider.ValueChanged += HandleMusicSliderValueChanged;
+ musicSlider.ValueChangeCompleted += HandleMusicSliderValueChangeCompleted;
+ _optionsPanel.AddChild(musicSlider);
+
+ var sfxSlider = new Slider();
+ sfxSlider.Anchor(Gum.Wireframe.Anchor.Top);
+ sfxSlider.Visual.Y = 93;
+ sfxSlider.Minimum = 0;
+ sfxSlider.Maximum = 1;
+ sfxSlider.Value = Core.Audio.SoundEffectVolume;
+ sfxSlider.SmallChange = .1;
+ sfxSlider.LargeChange = .2;
+ sfxSlider.ValueChanged += HandleSfxSliderChanged;
+ sfxSlider.ValueChangeCompleted += HandleSfxSliderChangeCompleted;
+ _optionsPanel.AddChild(sfxSlider);
+
+ _optionsBackButton = new Button();
+ _optionsBackButton.Text = "BACK";
+ _optionsBackButton.Anchor(Gum.Wireframe.Anchor.BottomRight);
+ _optionsBackButton.X = -28f;
+ _optionsBackButton.Y = -10f;
+ _optionsBackButton.Click += HandleOptionsButtonBack;
+ _optionsPanel.AddChild(_optionsBackButton);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/createtitlepanel.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/createtitlepanel.cs
new file mode 100644
index 00000000..45b26b3e
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/createtitlepanel.cs
@@ -0,0 +1,27 @@
+private void CreateTitlePanel()
+{
+ // Create a container to hold all of our buttons
+ _titleScreenButtonsPanel = new Panel();
+ _titleScreenButtonsPanel.Dock(Gum.Wireframe.Dock.Fill);
+ _titleScreenButtonsPanel.AddToRoot();
+
+ var startButton = new Button();
+ startButton.Anchor(Gum.Wireframe.Anchor.BottomLeft);
+ startButton.Visual.X = 50;
+ startButton.Visual.Y = -12;
+ startButton.Visual.Width = 70;
+ startButton.Text = "Start";
+ startButton.Click += HandleStartClicked;
+ _titleScreenButtonsPanel.AddChild(startButton);
+
+ _optionsButton = new Button();
+ _optionsButton.Anchor(Gum.Wireframe.Anchor.BottomRight);
+ _optionsButton.Visual.X = -50;
+ _optionsButton.Visual.Y = -12;
+ _optionsButton.Visual.Width = 70;
+ _optionsButton.Text = "Options";
+ _optionsButton.Click += HandleOptionsClicked;
+ _titleScreenButtonsPanel.AddChild(_optionsButton);
+
+ startButton.IsFocused = true;
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/draw.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/draw.cs
new file mode 100644
index 00000000..4c80d16c
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/draw.cs
@@ -0,0 +1,37 @@
+public override void Draw(GameTime gameTime)
+{
+ Core.GraphicsDevice.Clear(new Color(32, 40, 78, 255));
+
+ // Draw the background pattern first using the PointWrap sampler state.
+ Core.SpriteBatch.Begin(samplerState: SamplerState.PointWrap);
+ Core.SpriteBatch.Draw(_backgroundPattern, _backgroundDestination, new Rectangle(_backgroundOffset.ToPoint(), _backgroundDestination.Size), Color.White * 0.5f);
+ Core.SpriteBatch.End();
+
+ if (_titleScreenButtonsPanel.IsVisible)
+ {
+ // Begin the sprite batch to prepare for rendering.
+ Core.SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
+
+ // The color to use for the drop shadow text.
+ Color dropShadowColor = Color.Black * 0.5f;
+
+ // Draw the Dungeon text slightly offset from it is original position and
+ // with a transparent color to give it a drop shadow
+ Core.SpriteBatch.DrawString(_font5x, DUNGEON_TEXT, _dungeonTextPos + new Vector2(10, 10), dropShadowColor, 0.0f, _dungeonTextOrigin, 1.0f, SpriteEffects.None, 1.0f);
+
+ // Draw the Dungeon text on top of that at its original position
+ Core.SpriteBatch.DrawString(_font5x, DUNGEON_TEXT, _dungeonTextPos, Color.White, 0.0f, _dungeonTextOrigin, 1.0f, SpriteEffects.None, 1.0f);
+
+ // Draw the Slime text slightly offset from it is original position and
+ // with a transparent color to give it a drop shadow
+ Core.SpriteBatch.DrawString(_font5x, SLIME_TEXT, _slimeTextPos + new Vector2(10, 10), dropShadowColor, 0.0f, _slimeTextOrigin, 1.0f, SpriteEffects.None, 1.0f);
+
+ // Draw the Slime text on top of that at its original position
+ Core.SpriteBatch.DrawString(_font5x, SLIME_TEXT, _slimeTextPos, Color.White, 0.0f, _slimeTextOrigin, 1.0f, SpriteEffects.None, 1.0f);
+
+ // Always end the sprite batch when finished.
+ Core.SpriteBatch.End();
+ }
+
+ GumService.Default.Draw();
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/fields.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/fields.cs
new file mode 100644
index 00000000..db6a8697
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/fields.cs
@@ -0,0 +1,5 @@
+private SoundEffect _uiSoundEffect;
+private Panel _titleScreenButtonsPanel;
+private Panel _optionsPanel;
+private Button _optionsButton;
+private Button _optionsBackButton;
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handlemusicslidervaluechangecompleted.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handlemusicslidervaluechangecompleted.cs
new file mode 100644
index 00000000..09a5c3d5
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handlemusicslidervaluechangecompleted.cs
@@ -0,0 +1,5 @@
+private void HandleMusicSliderValueChangeCompleted(object sender, EventArgs args)
+{
+ // A UI interaction occurred, play the sound effect
+ Core.Audio.PlaySoundEffect(_uiSoundEffect);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handlemusicslidervaluechanged.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handlemusicslidervaluechanged.cs
new file mode 100644
index 00000000..6b1f3143
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handlemusicslidervaluechanged.cs
@@ -0,0 +1,12 @@
+private void HandleMusicSliderValueChanged(object sender, EventArgs args)
+{
+ // Intentionally not playing the UI sound effect here so that it is not
+ // constantly triggered as the user adjusts the slider's thumb on the
+ // track.
+
+ // Get a reference to the sender as a Slider.
+ var slider = (Slider)sender;
+
+ // Set the global song volume to the value of the slider.
+ Core.Audio.SongVolume = (float)slider.Value;
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handleoptionsbuttonback.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handleoptionsbuttonback.cs
new file mode 100644
index 00000000..f8a1d01a
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handleoptionsbuttonback.cs
@@ -0,0 +1,15 @@
+private void HandleOptionsButtonBack(object sender, EventArgs e)
+{
+ // A UI interaction occurred, play the sound effect
+ Core.Audio.PlaySoundEffect(_uiSoundEffect);
+
+ // Set the title panel to be visible.
+ _titleScreenButtonsPanel.IsVisible = true;
+
+ // Set the options panel to be invisible.
+ _optionsPanel.IsVisible = false;
+
+ // Give the options button on the title panel focus since we are coming
+ // back from the options screen.
+ _optionsButton.IsFocused = true;
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handleoptionsclicked.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handleoptionsclicked.cs
new file mode 100644
index 00000000..3ace9d29
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handleoptionsclicked.cs
@@ -0,0 +1,14 @@
+private void HandleOptionsClicked(object sender, EventArgs e)
+{
+ // A UI interaction occurred, play the sound effect
+ Core.Audio.PlaySoundEffect(_uiSoundEffect);
+
+ // Set the title panel to be invisible.
+ _titleScreenButtonsPanel.IsVisible = false;
+
+ // Set the options panel to be visible.
+ _optionsPanel.IsVisible = true;
+
+ // Give the back button on the options panel focus.
+ _optionsBackButton.IsFocused = true;
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handlesfxsliderchangecompleted.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handlesfxsliderchangecompleted.cs
new file mode 100644
index 00000000..ae9bfcb5
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handlesfxsliderchangecompleted.cs
@@ -0,0 +1,5 @@
+private void HandleSfxSliderChangeCompleted(object sender, EventArgs e)
+{
+ // Play the UI Sound effect so the player can hear the difference in audio.
+ Core.Audio.PlaySoundEffect(_uiSoundEffect);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handlesfxsliderchanged.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handlesfxsliderchanged.cs
new file mode 100644
index 00000000..dc296455
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handlesfxsliderchanged.cs
@@ -0,0 +1,12 @@
+ private void HandleSfxSliderChanged(object sender, EventArgs args)
+ {
+ // Intentionally not playing the UI sound effect here so that it is not
+ // constantly triggered as the user adjusts the slider's thumb on the
+ // track.
+
+ // Get a reference to the sender as a Slider.
+ var slider = (Slider)sender;
+
+ // Set the global sound effect volume to the value of the slider.;
+ Core.Audio.SoundEffectVolume = (float)slider.Value;
+ }
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handlestartclicked.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handlestartclicked.cs
new file mode 100644
index 00000000..d9b6feaa
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/handlestartclicked.cs
@@ -0,0 +1,8 @@
+private void HandleStartClicked(object sender, EventArgs e)
+{
+ // A UI interaction occurred, play the sound effect
+ Core.Audio.PlaySoundEffect(_uiSoundEffect);
+
+ // Change to the game scene to start the game.
+ Core.ChangeScene(new GameScene());
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/initialize.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/initialize.cs
new file mode 100644
index 00000000..645e4ad3
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/initialize.cs
@@ -0,0 +1,28 @@
+public override void Initialize()
+{
+ // LoadContent is called during base.Initialize().
+ base.Initialize();
+
+ // While on the title screen, we can enable exit on escape so the player
+ // can close the game by pressing the escape key.
+ Core.ExitOnEscape = true;
+
+ // Set the position and origin for the Dungeon text.
+ Vector2 size = _font5x.MeasureString(DUNGEON_TEXT);
+ _dungeonTextPos = new Vector2(640, 100);
+ _dungeonTextOrigin = size * 0.5f;
+
+ // Set the position and origin for the Slime text.
+ size = _font5x.MeasureString(SLIME_TEXT);
+ _slimeTextPos = new Vector2(757, 207);
+ _slimeTextOrigin = size * 0.5f;
+
+ // Initialize the offset of the background pattern at zero
+ _backgroundOffset = Vector2.Zero;
+
+ // Set the background pattern destination rectangle to fill the entire
+ // screen background
+ _backgroundDestination = Core.GraphicsDevice.PresentationParameters.Bounds;
+
+ InitializeUI();
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/initializeui.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/initializeui.cs
new file mode 100644
index 00000000..eda5376a
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/initializeui.cs
@@ -0,0 +1,9 @@
+private void InitializeUI()
+{
+ // Clear out any previous UI in case we came here from
+ // a different screen:
+ GumService.Default.Root.Children.Clear();
+
+ CreateTitlePanel();
+ CreateOptionsPanel();
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/loadcontent.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/loadcontent.cs
new file mode 100644
index 00000000..5e79274c
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/loadcontent.cs
@@ -0,0 +1,14 @@
+public override void LoadContent()
+{
+ // Load the font for the standard text.
+ _font = Core.Content.Load("fonts/04B_30");
+
+ // Load the font for the title text
+ _font5x = Content.Load("fonts/04B_30_5x");
+
+ // Load the background pattern texture.
+ _backgroundPattern = Content.Load("images/background-pattern");
+
+ // Load the sound effect to play when ui actions occur.
+ _uiSoundEffect = Core.Content.Load("audio/ui");
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/update.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/update.cs
new file mode 100644
index 00000000..d1f32cf4
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/update.cs
@@ -0,0 +1,15 @@
+public override void Update(GameTime gameTime)
+{
+ // Update the offsets for the background pattern wrapping so that it
+ // scrolls down and to the right.
+ float offset = _scrollSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
+ _backgroundOffset.X -= offset;
+ _backgroundOffset.Y -= offset;
+
+ // Ensure that the offsets do not go beyond the texture bounds so it is
+ // a seamless wrap
+ _backgroundOffset.X %= _backgroundPattern.Width;
+ _backgroundOffset.Y %= _backgroundPattern.Height;
+
+ GumService.Default.Update(gameTime);
+}
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/usings.cs b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/usings.cs
new file mode 100644
index 00000000..228e8dc1
--- /dev/null
+++ b/articles/tutorials/building_2d_games/20_implementing_ui_with_gum/snippets/titlescene/usings.cs
@@ -0,0 +1,10 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGameGum;
+using MonoGameGum.Forms.Controls;
+using MonoGameGum.GueDeriving;
+using MonoGameLibrary;
+using MonoGameLibrary.Scenes;
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/21_customizing_gum_ui/files/04b_30.fnt b/articles/tutorials/building_2d_games/21_customizing_gum_ui/files/04b_30.fnt
new file mode 100644
index 00000000..3c1a1c50
--- /dev/null
+++ b/articles/tutorials/building_2d_games/21_customizing_gum_ui/files/04b_30.fnt
@@ -0,0 +1,99 @@
+info face="04b30" size=35 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
+common lineHeight=35 base=31 scaleW=256 scaleH=512 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4
+page id=0 file="../images/atlas.png"
+chars count=95
+char id=32 x=30 y=152 width=3 height=1 xoffset=-1 yoffset=34 xadvance=29 page=0 chnl=15
+char id=33 x=240 y=30 width=14 height=29 xoffset=1 yoffset=2 xadvance=19 page=0 chnl=15
+char id=34 x=102 y=232 width=25 height=15 xoffset=1 yoffset=4 xadvance=29 page=0 chnl=15
+char id=35 x=184 y=0 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=36 x=250 y=150 width=3 height=1 xoffset=-1 yoffset=34 xadvance=29 page=0 chnl=15
+char id=37 x=0 y=34 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=38 x=30 y=150 width=3 height=1 xoffset=-1 yoffset=34 xadvance=29 page=0 chnl=15
+char id=39 x=245 y=202 width=10 height=15 xoffset=1 yoffset=4 xadvance=14 page=0 chnl=15
+char id=40 x=106 y=150 width=16 height=29 xoffset=1 yoffset=2 xadvance=21 page=0 chnl=15
+char id=41 x=123 y=150 width=16 height=29 xoffset=1 yoffset=2 xadvance=21 page=0 chnl=15
+char id=42 x=128 y=232 width=14 height=15 xoffset=1 yoffset=2 xadvance=19 page=0 chnl=15
+char id=43 x=94 y=0 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=44 x=143 y=232 width=10 height=14 xoffset=1 yoffset=19 xadvance=14 page=0 chnl=15
+char id=45 x=154 y=232 width=25 height=11 xoffset=1 yoffset=12 xadvance=29 page=0 chnl=15
+char id=46 x=231 y=228 width=10 height=10 xoffset=1 yoffset=19 xadvance=14 page=0 chnl=15
+char id=47 x=60 y=60 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=48 x=90 y=60 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=49 x=46 y=150 width=19 height=29 xoffset=1 yoffset=2 xadvance=23 page=0 chnl=15
+char id=50 x=150 y=60 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=51 x=180 y=60 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=52 x=210 y=60 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=53 x=0 y=94 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=54 x=180 y=30 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=55 x=60 y=90 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=56 x=90 y=90 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=57 x=120 y=90 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=58 x=234 y=202 width=10 height=25 xoffset=1 yoffset=4 xadvance=14 page=0 chnl=15
+char id=59 x=244 y=0 width=10 height=29 xoffset=1 yoffset=4 xadvance=14 page=0 chnl=15
+char id=60 x=86 y=150 width=19 height=29 xoffset=1 yoffset=2 xadvance=23 page=0 chnl=15
+char id=61 x=182 y=176 width=25 height=25 xoffset=1 yoffset=4 xadvance=29 page=0 chnl=15
+char id=62 x=237 y=120 width=18 height=29 xoffset=1 yoffset=2 xadvance=23 page=0 chnl=15
+char id=63 x=180 y=120 width=28 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=64 x=34 y=150 width=3 height=1 xoffset=-1 yoffset=34 xadvance=29 page=0 chnl=15
+char id=65 x=120 y=120 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=66 x=150 y=120 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=67 x=124 y=0 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=68 x=154 y=0 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=69 x=214 y=0 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=70 x=30 y=30 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=71 x=60 y=30 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=72 x=90 y=30 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=73 x=240 y=90 width=14 height=29 xoffset=1 yoffset=2 xadvance=19 page=0 chnl=15
+char id=74 x=120 y=30 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=75 x=150 y=30 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=76 x=209 y=120 width=27 height=29 xoffset=1 yoffset=2 xadvance=31 page=0 chnl=15
+char id=77 x=30 y=0 width=31 height=29 xoffset=1 yoffset=2 xadvance=35 page=0 chnl=15
+char id=78 x=210 y=30 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=79 x=0 y=64 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=80 x=30 y=60 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=81 x=0 y=0 width=29 height=33 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=82 x=120 y=60 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=83 x=30 y=90 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=84 x=150 y=90 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=85 x=180 y=90 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=86 x=210 y=90 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=87 x=62 y=0 width=31 height=29 xoffset=1 yoffset=2 xadvance=35 page=0 chnl=15
+char id=88 x=0 y=124 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=89 x=30 y=120 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=90 x=60 y=120 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=91 x=240 y=60 width=14 height=29 xoffset=1 yoffset=2 xadvance=19 page=0 chnl=15
+char id=92 x=90 y=120 width=29 height=29 xoffset=1 yoffset=2 xadvance=33 page=0 chnl=15
+char id=93 x=140 y=150 width=14 height=29 xoffset=1 yoffset=2 xadvance=19 page=0 chnl=15
+char id=94 x=180 y=232 width=16 height=11 xoffset=1 yoffset=4 xadvance=21 page=0 chnl=15
+char id=95 x=0 y=262 width=29 height=10 xoffset=1 yoffset=21 xadvance=33 page=0 chnl=15
+char id=96 x=197 y=228 width=16 height=11 xoffset=1 yoffset=4 xadvance=21 page=0 chnl=15
+char id=97 x=208 y=176 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=98 x=0 y=210 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=99 x=26 y=210 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=100 x=52 y=206 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=101 x=78 y=206 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=102 x=104 y=206 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=103 x=130 y=206 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=104 x=156 y=206 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=105 x=234 y=176 width=12 height=25 xoffset=1 yoffset=6 xadvance=16 page=0 chnl=15
+char id=106 x=182 y=202 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=107 x=208 y=202 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=108 x=78 y=232 width=23 height=25 xoffset=1 yoffset=6 xadvance=27 page=0 chnl=15
+char id=109 x=197 y=150 width=26 height=25 xoffset=1 yoffset=6 xadvance=31 page=0 chnl=15
+char id=110 x=0 y=236 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=111 x=26 y=236 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=112 x=78 y=180 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=113 x=0 y=154 width=25 height=29 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=114 x=52 y=232 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=115 x=224 y=150 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=116 x=0 y=184 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=117 x=26 y=184 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=118 x=52 y=180 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=119 x=170 y=150 width=26 height=25 xoffset=1 yoffset=6 xadvance=31 page=0 chnl=15
+char id=120 x=104 y=180 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=121 x=130 y=180 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=122 x=156 y=180 width=25 height=25 xoffset=1 yoffset=6 xadvance=29 page=0 chnl=15
+char id=123 x=26 y=154 width=19 height=29 xoffset=1 yoffset=2 xadvance=23 page=0 chnl=15
+char id=124 x=155 y=150 width=14 height=29 xoffset=1 yoffset=2 xadvance=19 page=0 chnl=15
+char id=125 x=66 y=150 width=19 height=29 xoffset=1 yoffset=2 xadvance=23 page=0 chnl=15
+char id=126 x=214 y=228 width=16 height=11 xoffset=1 yoffset=4 xadvance=21 page=0 chnl=15
diff --git a/articles/tutorials/building_2d_games/21_customizing_gum_ui/images/atlas.png b/articles/tutorials/building_2d_games/21_customizing_gum_ui/images/atlas.png
new file mode 100644
index 00000000..f7def20f
Binary files /dev/null and b/articles/tutorials/building_2d_games/21_customizing_gum_ui/images/atlas.png differ
diff --git a/articles/tutorials/building_2d_games/21_customizing_gum_ui/images/mgcb-editor.png b/articles/tutorials/building_2d_games/21_customizing_gum_ui/images/mgcb-editor.png
new file mode 100644
index 00000000..0fcd0479
Binary files /dev/null and b/articles/tutorials/building_2d_games/21_customizing_gum_ui/images/mgcb-editor.png differ
diff --git a/articles/tutorials/building_2d_games/21_customizing_gum_ui/index.md b/articles/tutorials/building_2d_games/21_customizing_gum_ui/index.md
new file mode 100644
index 00000000..346405f6
--- /dev/null
+++ b/articles/tutorials/building_2d_games/21_customizing_gum_ui/index.md
@@ -0,0 +1,436 @@
+---
+title: "Chapter 21: Customizing Gum UI"
+description: "Learn how to create custom UI components with animations and visual styling in Gum."
+---
+
+In the [previous chapter](../20_implementing_ui_with_gum/index.md), we implemented a functional UI system for our game using the Gum framework. While the UI is now fully operational, it uses Gum's default styling. This default styling is good for quickly iterating when building the UI, but it does not match the game's visuals. A well designed UI should not only be functional but also complement the game's overall visual style to create a cohesive experience.
+
+In this chapter you will:
+
+- Learn about Gum's visual customization system and component hierarchy.
+- Understand how animation chains and visual states work in Gum.
+- Create custom styled button and slider components.
+- Update the game's texture atlas to include UI graphics.
+- Implement responsive visual feedback for player interactions.
+- Apply your custom components to the game's UI screens.
+
+## Understanding Gum's Customization System
+
+Gum provides a powerful customization system that separates a UI element's functionality from its appearance. This allows you to maintain the built-in behavior of standard controls while completely changing their visual representation.
+
+### Container Hierarchy
+
+Every customized UI component in Gum starts with a top-level container that holds all other visual elements. This container is typically of type `ContainerRuntime`, which is similar to the `Panel` type we used earlier, but specifically designed for building custom visuals.
+
+The container hierarchy follows a parent-child relationship:
+
+- The top-level container manages the overall size and positioning of the component.
+- Visual elements like backgrounds, text, and icons are added as children.
+- Child elements can be positioned relative to their parent container.
+- Child elements can also be nested within other children, creating deeper hierarchies.
+
+This hierarchical structure allows you to build complex UI components from simpler parts, with each part playing a specific role in the overall design.
+
+### Size Relationships with Width and WidthUnits
+
+One powerful feature of Gum is how it handles size relationships between parent and child elements. By using different `WidthUnits` values, you can create dependencies that flow in different directions:
+
+- **RelativeToChildren**: A parent container can size itself based on its children.
+- **PercentageOfParent**: A child element can size itself as a percentage of its parent.
+- **Absolute**: An element can have a fixed pixel size.
+- **RelativeToParent**: An element can size itself relative to a specific container.
+
+For example:
+
+- A button might use a text element with `WidthUnits` set to `RelativeToChildren`, which means the text will be exactly the size needed to display its content.
+- The button's container might use `RelativeToChildren` with some additional padding, allowing the button to automatically resize based on its text content.
+
+Although we have not explicitly assigned WidthUnits and HeightUnits in our code, we have indirectly set these values by calling the Visual's `Dock` method. Specifically, by passing `Dock.Fill` as the parameter, `WidthUnits` and `HeightUnits` are both set to `RelativeToParent`.
+
+> [!NOTE]
+> These size relationships can create circular dependencies when a child depends on its parent and the parent depends on the child. In such cases, Gum resolves the conflict by making the child depend on the parent, and the parent ignores that particular child when calculating its size.
+
+### Visual Elements
+
+Gum provides several visual element types that we can use to build our custom components:
+
+- **ContainerRuntime**: An invisible container for organizing other elements.
+- **NineSliceRuntime**: A special graphic that can stretch while preserving its corners and edges.
+- **TextRuntime**: An element for displaying text with custom fonts.
+- **ColoredRectangleRuntime**: A simple colored rectangle for backgrounds or fills.
+
+The `NineSliceRuntime` is particularly useful for UI elements that need to resize dynamically. It divides a graphic into nine sections (four corners, four edges, and a center), allowing the element to stretch without distorting its borders.
+
+> [!NOTE]
+> A MonoGame and Gum community member Kaltinril also has a video series discussing Gum. With permission, the following video segment is included to demonstrate the advantages of using a *Nineslice* when creating UI elements.
+>