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 2e3c54fd..a7448055 100644
--- a/articles/toc.yml
+++ b/articles/toc.yml
@@ -1,5 +1,5 @@
- name: Introduction
- href:
+ href:
- name: Roadmap
href: /roadmap/
- name: What's New
@@ -7,107 +7,145 @@
- name: Getting Started
href: getting_started/index.md
items:
- - name: Introduction
- href: getting_started/
- - name: Supported platforms
- href: getting_started/platforms.md
- - name: 1. Setting up your OS for development
- items:
- - name: Windows
- href: getting_started/1_setting_up_your_os_for_development_windows.md
- - name: macOS
- href: getting_started/1_setting_up_your_os_for_development_macos.md
- - name: Ubuntu 20.04
- href: getting_started/1_setting_up_your_os_for_development_ubuntu.md
- - name: 2. Choosing your IDE
- items:
- - name: Visual Studio for Windows
- href: getting_started/2_choosing_your_ide_visual_studio.md
- - name: Visual Studio Code
- href: getting_started/2_choosing_your_ide_vscode.md
- - name: Rider
- href: getting_started/2_choosing_your_ide_rider.md
- - name: 3. Understanding the Code
- href: getting_started/3_understanding_the_code.md
- - name: 4. Adding Content
- href: getting_started/4_adding_content.md
- - name: 5. Adding Basic Code
- href: getting_started/5_adding_basic_code.md
- - name: Packaging
- href: getting_started/packaging_games.md
- - name: Preparing for consoles
- href: getting_started/preparing_for_consoles.md
- - name: Using Development Nuget Packages
- href: getting_started/using_development_nuget_packages.md
- - name: Tools
- items:
- name: Introduction
- href: getting_started/tools/
- - name: MGCB
- href: getting_started/tools/mgcb.md
- - name: MGCB Editor
- href: getting_started/tools/mgcb_editor.md
- - name: MGFXC
- href: getting_started/tools/mgfxc.md
- - name: Content Pipeline
- items:
- - name: Introduction
- href: getting_started/content_pipeline/index.md
- - name: Why use the Content Pipeline
- href: getting_started/content_pipeline/why_content_pipeline.md
- - name: Using MGCB Editor
- href: getting_started/content_pipeline/using_mgcb_editor.md
- - name: Custom Effects
- href: getting_started/content_pipeline/custom_effects.md
- - name: TrueType fonts
- href: getting_started/content_pipeline/adding_ttf_fonts.md
- - name: Localization
- href: getting_started/content_pipeline/localization.md
+ href: getting_started/
+ - name: Supported platforms
+ href: getting_started/platforms.md
+ - name: 1. Setting up your OS for development
+ items:
+ - name: Windows
+ href: getting_started/1_setting_up_your_os_for_development_windows.md
+ - name: macOS
+ href: getting_started/1_setting_up_your_os_for_development_macos.md
+ - name: Ubuntu 20.04
+ href: getting_started/1_setting_up_your_os_for_development_ubuntu.md
+ - name: 2. Choosing your IDE
+ items:
+ - name: Visual Studio for Windows
+ href: getting_started/2_choosing_your_ide_visual_studio.md
+ - name: Visual Studio Code
+ href: getting_started/2_choosing_your_ide_vscode.md
+ - name: Rider
+ href: getting_started/2_choosing_your_ide_rider.md
+ - name: 3. Understanding the Code
+ href: getting_started/3_understanding_the_code.md
+ - name: 4. Adding Content
+ href: getting_started/4_adding_content.md
+ - name: 5. Adding Basic Code
+ href: getting_started/5_adding_basic_code.md
+ - name: Packaging
+ href: getting_started/packaging_games.md
+ - name: Preparing for consoles
+ href: getting_started/preparing_for_consoles.md
+ - name: Using Development Nuget Packages
+ href: getting_started/using_development_nuget_packages.md
+ - name: Tools
+ items:
+ - name: Introduction
+ href: getting_started/tools/
+ - name: MGCB
+ href: getting_started/tools/mgcb.md
+ - name: MGCB Editor
+ href: getting_started/tools/mgcb_editor.md
+ - name: MGFXC
+ href: getting_started/tools/mgfxc.md
+ - name: Content Pipeline
+ items:
+ - name: Introduction
+ href: getting_started/content_pipeline/index.md
+ - name: Why use the Content Pipeline
+ href: getting_started/content_pipeline/why_content_pipeline.md
+ - name: Using MGCB Editor
+ href: getting_started/content_pipeline/using_mgcb_editor.md
+ - name: Custom Effects
+ href: getting_started/content_pipeline/custom_effects.md
+ - name: TrueType fonts
+ href: getting_started/content_pipeline/adding_ttf_fonts.md
+ - name: Localization
+ href: getting_started/content_pipeline/localization.md
- 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: Console Access
href: console_access.md
- name: Help and Support
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..5d6ca1df
--- /dev/null
+++ b/articles/tutorials/building_2d_games/01_what_is_monogame/index.md
@@ -0,0 +1,83 @@
+---
+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* 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, 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-3: Streets of Rage 4** | |
+
+## 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/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..df286829
--- /dev/null
+++ b/articles/tutorials/building_2d_games/02_getting_started/index.md
@@ -0,0 +1,218 @@
+---
+title: "Chapter 02: Getting Started"
+description: Setup your development environment for dotnet development and MonoGame using Visual Studio Code as your IDE.
+---
+
+Unlike game engines, MonoGame is a *framework*. This means it does not come as a standalone program that you download an install with 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 you 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). At the time of this writing, MonoGame targets the .NET 8.0 SDK. To install it, follow the instructions based on your operating system below
+
+### [Windows](#tab/windows)
+
+1. Open a web browser and navigate to https://dotnet.microsoft.com/en-us/download.
+2. Click the *Download .NET SDK x64* button to start the download of the .NET SDK Installer.
+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.
+2. Click the *Download .NET SDK x64 (Intel)* button start the download of the .NET SDK Installer
+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) is 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
+```
+
+---
+
+## 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. As of this writing, the current version of MonoGame targeted is 3.8.2.1105. 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's just a matter of installing the correct extension to support that 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 operating system, we'll 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/.
+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/.
+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* directory to make it available in the macOS *LaunchPad*.
+
+### [Linux](#tab/linux)
+
+1. Open a web browser and navigate to 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's recommended to use the official *C# Dev Kit* 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.
+
+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.
+
+> [!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'll 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'll 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` as in the *Search Box*
+3. Click install for 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 enter 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 directory called *.winemonogame* will be created in your home directory. If you ever wish to undo the setup this script performed, you can just simply delete this directory.
+
+### [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 directory called *.winemonogame* will be created in your home directory. If you ever wish to undo the setup this script performed, you can just simply delete this directory.
+
+---
+
+## Creating Your First MonoGame Application
+
+Now that you have your development environment setup, it's 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`.
+3. Type `.NET New Project` in the *Command Palette* and choose the *.NET New Project* command
+4. Next you'll be shown 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.
+5. After choosing the template, a dialog window will appear asking you to choose a location to save the project.
+6. Next you'll be prompted to enter a name for the project. Enter the name `DungeonSlime`.
+7. Finally, select the *Create Project* prompt.
+
+After selecting *Create Project*, a new C# project will be created based on the MonoGame template we choose and opened automatically in VSCode.
+
+|  |
+| :---: |
+| **Figure 2-1: 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-2: 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 isn't 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'll discuss in more detail in the next chapter. The reason the application enters this loop is because game applications work differently than a traditional desktop application like your web browser.
+>
+> Desktop application are event based, meaning once loaded, the do not do much at all while waiting for input from the user, and then it response to that input event and redraws the window if needed based on the interaction.
+>
+> In games, things are always happening such as objects moving around like the player or particles. The 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 has been told to exit.
+
+## Conclusion
+
+Let's review what you accomplished in this chapter:
+
+* You setup your operating system to develop .NET applications by installing the .NET SDK
+* You install 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'll cover the contents of the *Game1.cs* file that was included in the MonoGame project you just created.
+
+## Test Your Knowledge
+
+1. What version of the .NET SDK is currently targeted by MonoGame applications?
+
+ :::question-answer
+ .NET 8.0
+ :::
+
+2. What is the current version of MonoGame?
+
+ :::question-answer
+ 3.8.2.1105
+ :::
+
+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/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..7d32bb27
--- /dev/null
+++ b/articles/tutorials/building_2d_games/03_the_game1_file/index.md
@@ -0,0 +1,119 @@
+---
+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're 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. You can test this yourself by modifying the code to use a different color, such as [**Color.MonoGameOrange**](xref:Microsoft.Xna.Framework.Color.MonoGameOrange), then running the game. (yes, there is a 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 in 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
+
+Here is a review of what was accomplished in this chapter:
+
+- 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.
+ :::
+
+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/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..a8ae0b68
--- /dev/null
+++ b/articles/tutorials/building_2d_games/04_creating_a_class_library/images/with-class-library-diagram.svg
@@ -0,0 +1,425 @@
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/04_creating_a_class_library/images/with-class-library.png b/articles/tutorials/building_2d_games/04_creating_a_class_library/images/with-class-library.png
new file mode 100644
index 00000000..f7366b52
Binary files /dev/null and b/articles/tutorials/building_2d_games/04_creating_a_class_library/images/with-class-library.png differ
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..dabfb615
--- /dev/null
+++ b/articles/tutorials/building_2d_games/04_creating_a_class_library/images/without-class-library-diagram.svg
@@ -0,0 +1,417 @@
+
+
+
+
diff --git a/articles/tutorials/building_2d_games/04_creating_a_class_library/images/without-class-library.png b/articles/tutorials/building_2d_games/04_creating_a_class_library/images/without-class-library.png
new file mode 100644
index 00000000..603b649a
Binary files /dev/null and b/articles/tutorials/building_2d_games/04_creating_a_class_library/images/without-class-library.png differ
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..efe1fdd6
--- /dev/null
+++ b/articles/tutorials/building_2d_games/04_creating_a_class_library/index.md
@@ -0,0 +1,221 @@
+---
+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'll 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'll 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're easy to find, use, and improve over time.
+
+The following diagrams show how this works:
+
+|  |
+|:-------------------------------------------------------------------------------------------------------------------------------:|
+| **Figure 4-1: Without using a class library, common modules are duplicated across projects** |
+
+|  |
+|:----------------------------------------------------------------------------------------------------------------:|
+| **Figure 4-2: Using a class library, common modules are shared across projects** |
+
+> [!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'll accumulate a personal collection of well-tested modules that make starting new projects much faster. The modules we'll 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:
+
+### [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* 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 directory 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:
+
+### [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 directory 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 directory and delete them:
+
+1. The *.config/* directory.
+2. The *Content/* directory
+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.
+
+## Creating Our First Library Module
+
+Let's 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 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'll 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'll 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, let's 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.
+
+|  |
+|:-------------------------------------------------------------------------------------------------:|
+| **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 'FramesPerSecondCounter' 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
+
+Let's review what you accomplished in this chapter:
+
+- 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'll 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 don't 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 won't 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..dea68ba3
--- /dev/null
+++ b/articles/tutorials/building_2d_games/04_creating_a_class_library/snippets/game1.cs
@@ -0,0 +1,45 @@
+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
+ }
+
+ 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..b505bff1
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..e6afb6a5
--- /dev/null
+++ b/articles/tutorials/building_2d_games/05_content_pipeline/images/content-pipeline-workflow-full.svg
@@ -0,0 +1,1258 @@
+
+
+
+
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..cc504de9
--- /dev/null
+++ b/articles/tutorials/building_2d_games/05_content_pipeline/index.md
@@ -0,0 +1,288 @@
+---
+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'll 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 directory.
+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.
+>
+> Alternatively, as we'll explore below, using the **Content Pipeline** handles this for you automatically.
+
+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 directory.
+3. Load the compiled asset at runtime using the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager).
+
+The following image illustrates this workflow:
+
+|  |
+|:----------------------------------------------------------------------------------------------:|
+| **Figure 5-1: MonoGame Content Pipeline Workflow** |
+
+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
+
+The MGCB Editor is a GUI tool that can be used to edit your content project. This tool is automatically added to your game project as a local [dotnet tool](https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools) when you create a new MonoGame game project using one of the MonoGame project templates. Using the editor, you can add existing assets to the content project for your game, or create a new asset using one of the built-in types in the MGCB Editor:
+
+- **Effect (.fx)**: A shader file that creates custom visual effects by controlling how graphics are rendered on the GPU.
+- **LocalizedSpriteFont Description (.spritefont)**: A configuration file for creating fonts with support for multiple languages.
+- **Sprite Effect (.fx)**: A shader specifically designed for use with 2D sprites to create special visual effects.
+- **SpriteFont Description (.spritefont)**: A configuration file that defines how text will be displayed in your game, including character set and font properties.
+- **Xml Content (.xml)**: A structured data file for storing game information like levels, dialogues, or configuration settings.
+
+> [!NOTE]
+> The content project is the *Content.mgcb* file in your game project directory. This file can be edited manually by hand, however it's much easier to use the MGCB Editor instead.
+
+### Opening the MGCB Editor
+
+There are different methods of opening the MGCB Editor tool depending on your IDE and environment:
+
+### [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-2: 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 directory as your game project's *.csproj* file.
+2. Enter the command `dotnet mgcb-editor ./Content/Content.mgcb`
+
+---
+
+|  |
+|:---------------------------------------------------------------------------------------------:|
+| **Figure 5-3: MonoGame Content Builder Editor (MGCB Editor) Window** |
+
+In Figure 5-3 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 hierarchial 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.
+
+### Adding Existing Assets
+
+To add an existing asset to the content project:
+
+1. In the Project panel, select the folder where you want to add the item. If you want to add it to the root, select the main *Content* node.
+2. Right-click on the selected folder and choose *Add > Existing Item...* from the context menu.
+3. In the file browser that appears, navigate to the location of the file you want to add.
+4. Select the file(s) you want to add and click *Open*.
+
+When adding existing assets to the content project, a pop-up dialog will appear with the following options:
+
+- **Copy the file to the directory**: Creates a duplicate of the file inside your project's Content directory. This creates an independent copy, meaning any later changes to the original file won't affect your project.
+- **Add a link**: Creates a reference to the original file without making a copy. This maintains a connection to the source file, so any updates to the original will be included when you build. Note that the link uses a path relative to the Content.mgcb file, so if either the source file or your project moves, you'll need to reestablish the link.
+- **Skip**: Cancels adding the current file while continuing with any other selected files.
+
+|  |
+|:--------------------------------------------------------------------:|
+| **Figure 5-4: Add existing file pop-up** |
+
+### Adding Built-In Asset Types
+
+To create a new asset using one of the built-in types in the MGCB Editor:
+
+1. In the Project panel, select the folder where you want to add the new asset. If you want to add it to the root, select the main *Content* node.
+2. Right-click on the selected folder and choose *Add > New Item...* from the context menu.
+3. In the dialog that appears, select the type of asset you want to create from the list of available built-in types:
+ - **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.
+4. Enter a name for your new asset in the *Name* field.
+5. Click *Create* to add the new asset to your project.
+
+|  |
+|:-----------------------------------------------------------:|
+| **Figure 5-5: New file pop-up** |
+
+> [!NOTE]
+> Each built-in asset type comes with a template that includes the minimum required structure and settings.
+
+### Adding Folders to Organize Content
+
+Organizing your game assets into folders helps keep your content project manageable as it grows. To add a new folder:
+
+1. In the Project panel, select the location where you want to create a new folder. This can be the root *Content* node or another existing folder.
+2. Right-click on the selected location and choose *Add > New Folder...* from the context menu.
+3. Type a name for the new folder and click *Ok*.
+
+|  |
+|:---------------------------------------------------------------:|
+| **Figure 5-6: New folder pop-up |
+
+The new folder will appear in your content tree, and you can now add items to it by:
+
+- Adding existing assets directly to the folder
+- Creating new assets within the folder
+
+The folder structure you create in the MGCB Editor affects how you'll access your content in code. It's good practice to establish a folder structure early in your project development to avoid having to reorganize and update content paths later.
+
+## The ContentManager Class
+
+To load assets in code that have been processed through the content pipeline, MonoGame provides the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) class.
+
+> [!NOTE]
+> The [**Game**](xref:Microsoft.Xna.Framework.Game) class provides the [**Content**](xref:Microsoft.Xna.Framework.Game.Content) property which is ready to use instance of the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager).
+
+### ContentManager Properties
+
+The [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) offers the following properties:
+
+| Property | Type | Description |
+|--------------------------------------------------------------------------------------------|--------------------|-------------------------------------------------------------|
+| [**RootDirectory**](xref:Microsoft.Xna.Framework.Content.ContentManager.RootDirectory) | `string` | The root directory the content manager searches for assets. |
+| [**ServiceProvider**](xref:Microsoft.Xna.Framework.Content.ContentManager.ServiceProvider) | `IServiceProvider` | The service provider used by the content manager. |
+
+### ContentManager Methods
+
+The [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) offers the following methods:
+
+| 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. |
+| [**LoadLocalized<T>(string)**](xref:Microsoft.Xna.Framework.Content.ContentManager.LoadLocalized``1(System.String)) | `T` | Loads the asset of type `T` that has been processed by the content pipeline using prepending the [**CultureInfo.CurrentCulture**](https://learn.microsoft.com/en-us/dotnet/api/system.globalization.cultureinfo.currentculture?view=net-9.0) value to the end of the asset name. (e.g. "assetname.en-US") |
+| [**Unload**](xref:Microsoft.Xna.Framework.Content.ContentManager.Unload) | `void` | Unloads all assets that have been loaded by that content manager instance. |
+| [**UnloadAsset(string)**](xref:Microsoft.Xna.Framework.Content.ContentManager.UnloadAsset(System.String)) | `void` | Unloads the asset with the specified name that has been loaded by that content manager instance. |
+| [**UnloadAssets(IList<string>)**](xref:Microsoft.Xna.Framework.Content.ContentManager.UnloadAssets(System.Collections.Generic.IList{System.String})) | `void` | Unloads the assets that have been loaded by that content manager with the names specified in the list provided. |
+| [**Dispose**](xref:Microsoft.Xna.Framework.Content.ContentManager.Dispose(System.Boolean)) | `void` | Unloads all assets from the content manager and disposes of the content manager instance. |
+
+> [!TIP]
+> When an asset is loaded for the first time, the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) will internally cache the loaded asset. Loading that same asset later will return the cached asset instead of having to perform another disk read to load the asset again.
+
+> [!TIP]
+> When an asset is unloaded, if the asset type implements the `IDisposable` interface, the `Dispose` method will automatically be called on the asset during hte unload process.
+
+When loading an asset, the load methods require two parts:
+
+1. `T` Type Reference: The content type we are loading.
+2. `assetName` Parameter: A string path that matches the content path of the asset to load.
+
+## Understanding Content Paths
+
+The folder structure you create in the MGCB Editor directly affects how you load content in your game. When you perform a build of your game project, the *MonoGame.Content.Builder.Tasks* NuGet package reference will:
+
+1. Compile the assets into an optimized format in the **content project's** output directory (typically *ProjectRoot/Content/bin/Platform/Content*) as an *.xnb* file.
+2. Copy the compiled assets to your **game's** output directory (typically *ProjectRoot/bin/Debug/net8.0/Content* or *ProjectRoot/bin/Release/net8.0/Content*).
+
+For example, if your content project contains:
+
+[!code-sh[](./snippets/content_dir_tree.sh)]
+
+then when the tasks first compiles the assets, they will be output to:
+
+[!code-sh[](./snippets/content_build_dir_tree.sh)]
+
+Then after compiling them and copying them to the game projects output directory, it will look like the following:
+
+[!code-sh[](./snippets/project_build_dir_tree.sh)]
+
+When the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) is used to load these assets, it looks for them relative to its [**RootDirectory**](xref:Microsoft.Xna.Framework.Content.ContentManager.RootDirectory) property. By default, this is set to `"Content"` in the `Game1` constructor to match where the compiled assets are copied. The path used to load an asset must match its location relative to the [**RootDirectory**](xref:Microsoft.Xna.Framework.Content.ContentManager.RootDirectory), minus any extension. For example, to load the above assets, the paths would be `"images/logo"` and `"sounds/music"`.
+
+## Loading Our First Asset
+
+Let's walk through the process of editing our content project using the MGCB Editor to add a new image asset and then load it in our game. To get started, we'll first need an image to load. Right-click the following image of the MonoGame logo and save it named *logo.png* somewhere on your computer:
+
+|  |
+|:----------------------------------------------------------:|
+| **Figure 5-7: MonoGame Horizontal Logo** |
+
+Now that we have an image file to add, perform the following:
+
+1. Open the content project in the MGCB Editor.
+2. Select the *Content* node in the Project Panel.
+3. Right-click on the selected *Content* node and choose *Add > New Folder...* from teh context menu.
+4. Name the folder *images* and click the *Ok* button.
+5. Select the new *images* node in the Project Panel.
+6. Right-click on the selected *images* node and choose *Add > Existing Item..* from teh context menu.
+7. In the file browser that appears, navigate to the location of the *logo.png* file you just downloaded.
+8. Select the *logo.png* file click *Open*.
+9. When prompted choose the *Copy the file to the directory* option from the add exiting file pop-up.
+10. Save the changes made to the content project by selecting *File > Save* from the top menu.
+
+> [!IMPORTANT]
+> After changes have been made in the MGBC Editor, ensure that you save the changes. They are not automatically saved, though you will be warned if you close the editor and haven't 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
+
+|  |
+|:-----------------------------------------------------------------------------------------------------------:|
+| **Figure 5-8: The logo image added to the content project in the MGCB Editor** |
+
+With the MonoGame logo image now added to the content project, we can load it in our game and draw it. Open the *Game1.cs* file and make the following changes:
+
+[!code-csharp[](./snippets/game1.cs?highlight=10-11,27,45-52)]
+
+The key changes made here are:
+
+1. The private field `_logo` was added to store the logo [**Texture2D**](xref:Microsoft.Xna.Framework.Graphics.Texture2D) once it is loaded.
+2. In [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent), the logo texture is loaded using the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager).
+3. In [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) the logo is drawn using the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch).
+
+ > [!NOTE]
+ > We'll go more into detail about the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) in the next chapter.
+
+Running the game now will show the MonoGame logo displayed in the upper-left corner of the game window.
+
+|  |
+|:----------------------------------------------------------------------------------:|
+| **Figure 5-9: The MonoGame logo drawn to the game window** |
+
+## Conclusion
+
+Let's review what you accomplished in this chapter:
+
+- You learned about the advantages of loading assets using the **Content Pipeline**.
+- You added an image file asset to the *Content.mgcb* content project using the MGCB Editor.
+- You learned about the **Content Pipeline** workflow and how MonoGame automates the process for you.
+- You loaded the image file asset using the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager)
+
+In the next chapter, we'll go more into detail on working with textures and the various options available when rendering them.
+
+## 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 directory. 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/content_build_dir_tree.sh b/articles/tutorials/building_2d_games/05_content_pipeline/snippets/content_build_dir_tree.sh
new file mode 100644
index 00000000..7f5e8a09
--- /dev/null
+++ b/articles/tutorials/building_2d_games/05_content_pipeline/snippets/content_build_dir_tree.sh
@@ -0,0 +1,9 @@
+ProjectRoot/
+ └── bin/
+ └── Debug/
+ └── net8.0/
+ └── Content/
+ ├── images/
+ │ └── logo.xnb
+ └── sounds/
+ └── music.xnb
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/snippets/content_dir_tree.sh b/articles/tutorials/building_2d_games/05_content_pipeline/snippets/content_dir_tree.sh
new file mode 100644
index 00000000..765ea0be
--- /dev/null
+++ b/articles/tutorials/building_2d_games/05_content_pipeline/snippets/content_dir_tree.sh
@@ -0,0 +1,5 @@
+Content/
+ ├── images/
+ │ └── logo.png
+ └── sounds/
+ └── music.mp3
\ No newline at end of file
diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/snippets/contentpaths.sh b/articles/tutorials/building_2d_games/05_content_pipeline/snippets/contentpaths.sh
new file mode 100644
index 00000000..16f5d0ce
--- /dev/null
+++ b/articles/tutorials/building_2d_games/05_content_pipeline/snippets/contentpaths.sh
@@ -0,0 +1,31 @@
+#region content
+Content/
+ ├── images/
+ │ └── logo.png
+ └── sounds/
+ └── music.mp3
+#endregion
+
+#region contentbuild
+ProjectRoot/
+ └── bin/
+ └── Debug/
+ └── net8.0/
+ └── Content/
+ ├── images/
+ │ └── logo.xnb
+ └── sounds/
+ └── music.xnb
+#endregion
+
+#region projectbuild
+ProjectRoot/
+ └── Content/
+ └── bin/
+ └── DesktopGL/
+ └── Content/
+ ├── images/
+ │ └── logo.xnb
+ └── sounds/
+ └── music.xnb
+#endregion
\ No newline at end of file
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..77aca013
--- /dev/null
+++ b/articles/tutorials/building_2d_games/05_content_pipeline/snippets/game1.cs
@@ -0,0 +1,56 @@
+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");
+ 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);
+
+ // 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/05_content_pipeline/snippets/project_build_dir_tree.sh b/articles/tutorials/building_2d_games/05_content_pipeline/snippets/project_build_dir_tree.sh
new file mode 100644
index 00000000..17ec1545
--- /dev/null
+++ b/articles/tutorials/building_2d_games/05_content_pipeline/snippets/project_build_dir_tree.sh
@@ -0,0 +1,9 @@
+ProjectRoot/
+ └── Content/
+ └── bin/
+ └── DesktopGL/
+ └── Content/
+ ├── images/
+ │ └── logo.xnb
+ └── sounds/
+ └── music.xnb
\ No newline at end of file
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..0800f3dd
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/index.md
@@ -0,0 +1,340 @@
+---
+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)]
+
+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. |
+
+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, let's 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. Let's 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-15)]
+
+> [!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. Let's 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 its center.
+
+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. 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'll cover this in a moment.** |
+
+### Rotation
+
+First let's explore the `rotation` parameter. This value is the amount of rotation to apply to the sprite when rendering it. Let's 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's 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 only need to change the `origin` parameter so that instead of defaulting to the upper-left corner of the sprite, it is set to the center of the sprite. When doing this, we need to set the values based on the sprites width and height, so the center origin will be half the width and height of the sprite. Update the code to:
+
+[!code-csharp[](./snippets/origin.cs?highlight=18-20)]
+
+By moving the sprite's origin point to its center, this not only corrects the point of rotation, but also eliminates the need to offset the position by half the sprite's dimensions. Running the game now shows the log properly centered and rotated 90°.
+
+|  |
+|:----------------------------------------------------------------------------------------------------------------------:|
+| **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. Let's see an example of this by setting the scale of the logo sprite to `1.5f`:
+
+[!code-csharp[](./snippets/scale.cs?highlight=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, let's 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. |
+
+Let's see this by applying the [**SpriteEffects.FlipHorizontally**](xref:Microsoft.Xna.Framework.Graphics.SpriteEffects.FlipHorizontally) value to the sprite:
+
+[!code-csharp[](./snippets/spriteeffects.cs?highlight=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's 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're 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` doesn't change the value, [**Color.White**](xref:Microsoft.Xna.Framework.Color.White) essentially preserves the original colors of the sprite.
+
+Let's change the `color` parameter to use [**Color.Green**](xref:Microsoft.Xna.Framework.Color.Green):
+
+[!code-csharp[](./snippets/color.cs?highlight=16)]
+
+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's not quite fully black, but it is very close.
+>
+> This is why it's 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've 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've 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`.
+
+Let's 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've 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'll 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'll explore further in the next chapter.
+
+### Layer Depth
+
+The final parameter to discuss is the `layerDepth` parameter. Notice that in Figure 5-14 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'll 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. |
+
+Let's see this in action. We've 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're 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 didn't change the order of the `_spriteBatch.Draw` calls, if you run the game now, you will see the following:
+
+|  |
+|:----------------------------------------------------------------------------------------------------:|
+| **Figure 5-17: 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're 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'll 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
+
+Let's review what you accomplished in this chapter:
+
+- 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'll take what we've 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..2fd4be49
--- /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 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..842407f4
--- /dev/null
+++ b/articles/tutorials/building_2d_games/06_working_with_textures/snippets/draw_center_wrong.cs
@@ -0,0 +1,21 @@
+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,
+ 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..de588de8
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..79457bb6
--- /dev/null
+++ b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/index.md
@@ -0,0 +1,259 @@
+---
+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'll 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, let's explore the following simplified draw calls 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're 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're 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. Let's first build on this and create a class called `TextureRegion`.
+
+We're 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 directory in the *MonoGameLibrary* project named `Graphics`
+2. Create a new file named *TextureRegion.cs* inside the *Graphics* directory you just created.
+
+Add the following code for the foundation of the `TextureRegion` class to the *TextureRegion.cs* file:
+
+[!code-csharp[](./snippets/textureregion.cs#declaration)]
+
+> [!NOTE]
+> The *TextureRegion.cs* class file is placed in the *MonoGame/Graphics* directory 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.
+
+### 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 will provide 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, while the parameterized constructor allows you to define the region's source texture and boundary in a single step. This 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.
+
+## 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'll create the `TextureAtlas` class to represent the collection of the regions that make up all of our sprites.
+
+Just like the `TextureRegion` class, we're going to add this to the class library. In the *Graphics* directory within the *MonoGameLibrary* project, add a new file named *TextureAtlas.cs*. Add the following code for the foundation fo the `TextureAtlas` class to the *TextureAtlas.cs* file:
+
+[!code-csharp[](./snippets/textureatlas.cs#declaration)]
+
+### 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 will provide 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, while the parameterized constructor allows you to specify the source texture immediately. Both constructors initialize the `_regions` dictionary so that it's 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'll look more into using the XML configuration in a moment.
+
+## Using the TextureAtlas Class
+
+Let's put our new `TextureAtlas` class to use by exploring two approaches; creating an atlas manually and using XML configuration. So far, we've been practicing using textures with the MonoGame logo. Now we will use a new texture atlas that contains various sprites we'll 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 isn't 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* directory 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'll 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,67-77)]
+
+The key changes in this implementation are:
+
+1. The `_logo` field was removed.
+2. The `FramesPerSecondCounter` component that was added in the constructor was removed.
+3. Added `TextureRegion` members for the slime and bat sprites.
+4. 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.
+5. Updated [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) to render both sprites, using the slime's `Width` property to position the bat.
+
+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. Let's now explore the `TextureAtlas.FromFile` method to load our atlas configuration from XML instead. Perform the following:
+
+1. Create a new file named *atlas-definition.xml* in the *Content/images* directory.
+2. Add the following content to that file:
+
+ [!code-xml[](./snippets/atlas_definition.xml)]
+
+3. Open the *Content.mgcb* file in the MGCB Editor
+4. In the editor, right-click the *images* directory and choose *Add . Existing item...*.
+5. Navigate to and choose the *atlas-definition.xml* file you just created to add it.
+6. In the properties panel at the bottom for the *atlas-definition.xml* file, change the *Build Action* property from *Build* to *Copy*.
+
+ |  |
+ |:---------------------------------------------------------------------------------------------------------------------------------------------------:|
+ | **Figure 7-4: The atlas-definition.xml file added to the content project with the Build Action property set to Copy** |
+
+7. Save the changes and close the MGCB Editor
+
+ > [!TIP]
+ > Using the content pipeline to copy files ensures they're placed in the correct location alongside other game content. While there are other methods (like editing the .csproj), this approach keeps asset management centralized
+
+8. Replace 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.
+
+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
+
+Let's review what you accomplished in this chapter:
+
+- 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'll 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..e543e984
--- /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..4590ffc7
--- /dev/null
+++ b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/game1/textureatlas_usage.cs
@@ -0,0 +1,81 @@
+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, 80, 80);
+
+ // add the bat region to the atlas.
+ atlas.AddRegion("bat", 0, 80, 80, 80);
+
+ // retrieve the slime region from the atlas.
+ _slime = atlas.GetRegion("slime");
+
+ // retrieve the bat region from the atlas.
+ _bat = atlas.GetRegion("bat");
+
+ 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)
+ {
+ // 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.
+ _slime.Draw(SpriteBatch, Vector2.One, Color.White);
+
+ // Draw the bat texture region 10px to the right of the slime.
+ _bat.Draw(SpriteBatch, new Vector2(_slime.Width + 10, 0), Color.White);
+
+ // 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..3aa29ff4
--- /dev/null
+++ b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/game1/textureatlas_xml_usage.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
+{
+ // 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");
+
+ 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)
+ {
+ // 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.
+ _slime.Draw(SpriteBatch, Vector2.One, Color.White);
+
+ // Draw the bat texture region 10px to the right of the slime.
+ _bat.Draw(SpriteBatch, new Vector2(_slime.Width + 10, 0), Color.White);
+
+ // Always end the sprite batch when finished.
+ SpriteBatch.End();
+
+ base.Draw(gameTime);
+ }
+}
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..084ad844
--- /dev/null
+++ b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/textureatlas.cs
@@ -0,0 +1,146 @@
+#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'll retrieve that value then use the content manager to load the texture.
+ string texturePath = root.Element("Texture").Value;
+ atlas.Texture = content.Load(texturePath);
+
+ // The element contains individual elements, each one describing
+ // a different texture region within the atlas.
+ //
+ // Example:
+ //
+ //
+ //
+ //
+ //
+ // So we retrieve all of the elements then loop through each one
+ // and generate a new TextureRegion instance from it and add it to this atlas.
+ var regions = root.Element("Regions")?.Elements("Region");
+
+ if (regions != null)
+ {
+ foreach (var region in regions)
+ {
+ string name = region.Attribute("name")?.Value;
+ int x = int.Parse(region.Attribute("x")?.Value ?? "0");
+ int y = int.Parse(region.Attribute("y")?.Value ?? "0");
+ int width = int.Parse(region.Attribute("width")?.Value ?? "0");
+ int height = int.Parse(region.Attribute("height")?.Value ?? "0");
+
+ if (!string.IsNullOrEmpty(name))
+ {
+ atlas.AddRegion(name, x, y, width, height);
+ }
+ }
+ }
+
+ 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..02ee5e61
--- /dev/null
+++ b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/snippets/textureregion.cs
@@ -0,0 +1,119 @@
+#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..297ba5d2
--- /dev/null
+++ b/articles/tutorials/building_2d_games/08_the_sprite_class/index.md
@@ -0,0 +1,121 @@
+---
+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'll 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* directory within the *MonoGameLibrary* project, add a new file named *Sprite.cs*. Add the following code for the foundation of the `Sprite` class to the *Sprite.cs* file:
+
+[!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 will provide 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, while the parameterized constructor allows you to specify the source texture region for the sprite.
+
+### Methods
+
+Finally, the `Sprite` class provides the following two methods:
+
+[!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 *TextureAtlas.cs* and add the following method:
+
+[!code-csharp[](./snippets/createsprite.cs)]
+
+## Using the Sprite Class
+
+Let's adjust our game now to use the `Sprite` class instead of just the texture regions. Replace the contents of *Game1.cs* with the following:
+
+[!code-csharp[](./snippets/game1.cs?highlight=11-15,34-38,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.
+- 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** |
+
+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'll 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..b9432993
--- /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");
+
+ // Create the bat sprite from the atlas.
+ _bat = atlas.CreateSprite("bat");
+
+ 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)
+ {
+ // 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..96d16a45
--- /dev/null
+++ b/articles/tutorials/building_2d_games/08_the_sprite_class/snippets/sprite.cs
@@ -0,0 +1,116 @@
+#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..77ea9d82
--- /dev/null
+++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/index.md
@@ -0,0 +1,222 @@
+---
+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'll 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. Let's create an `Animation` class to encapsulate this information. In the *Graphics* directory within the *MonoGameLibrary* project, add a new file named *Animation.cs* with this initial structure:
+
+[!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. Let's 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'll be displayed in the same sequence they're added, creating the animation's movement. For example, in our bat animation, the frames would be ordered to show the wings moving up, then fully extended, then down.
+
+The `Delay` property defines how long each frame should be displayed before moving to the next one. This timing control allows us to adjust the speed of our animations; a shorter delay creates faster animations, while a longer delay creates slower ones.
+
+> [!NOTE]
+> Using `TimeSpan` for the delay allows us to specify precise timing intervals, making it easier to synchronize animations with game time. In other scenarios, you could opt to use `float` values instead.
+
+### Animation Constructors
+
+The `Animation` class will provide two ways to create an animation. Add the following constructors:
+
+[!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. Let's first update our *atlas-definition.xml* file to include all regions in the atlas, as well as add new `` elements to define the animations. Open the *atlas-definition.xml* file and replace the contents with the following:
+
+[!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 storage for animations
+
+ [!code-csharp[](./snippets/textureatlas/add_animation_storage.cs)]
+
+2. Update the constructors so that the animations dictionary is initialized:
+
+ [!code-csharp[](./snippets//textureatlas/update_ctors.cs?highlight=7,18)]
+
+3. Add methods to manage animations, similar to those that we use to manage regions:
+
+ [!code-csharp[](./snippets/textureatlas/add_animation_management.cs)]
+
+4. 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.
+
+> [!NOTE]
+> By inheriting from `Sprite`, our `AnimatedSprite` class automatically gets all the rendering properties (position, rotation, scale, etc.) while adding animation-specific functionality.
+
+The key to this design is the `Sprite.Region` property. Our `Sprite` class already knows how to render whatever region is currently set, so our `AnimatedSprite` class just needs to update this region property to the correct animation frame at the right time.
+
+Let's create the initial structure for our `AnimatedSprite` class. In the *Graphics* directory within the *MonoGameLibrary* project, add a new file named *AnimatedSprite.cs*:
+
+[!code-csharp[](./snippets/animatedsprite.cs#declaration)]
+
+### AnimatedSprite Members
+
+An animated sprite needs to track both its current animation state and timing information. Let's add the following members to the `AnimatedSprite` class:
+
+[!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 will provide 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 a single `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've reached the end
+ - Updates the sprite's region to display the current frame
+
+> [!NOTE]
+> Unlike the `Sprite` class which only needs a `Draw` method, the `AnimatedSprite` requires this additional `Update` method to handle frame changes over time. This follows MonoGame's update/draw pattern we first saw in [Chapter 03](../03_the_game1_file/index.md)
+
+The `Draw` method inherited from the base `Sprite` class remains unchanged, as it will automatically use whatever frame is currently set as the sprite's region.
+
+## Creating AnimatedSprites With The TextureAtlas Class
+
+Similar to the update we did to the `TextureAtlas` class in [Chapter 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 spirte 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
+
+Let's adjust our game now to use the `AnimatedSprite` class to see our sprites come to life. Replaces the contents of *Game1.cs* with the following:
+
+[!code-csharp[](./snippets/game1.cs?highlight=11-15,34-38,48-52)]
+
+Let's examine the key changes in this implementation:
+
+- The `_slime` and `_bat` members were changed from `Sprite` to `AnimatedSprite`.
+- In [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent) the `_slime` and `_bat` sprites are now created using the new `TextureAtlas.CreateAnimatedSprite` method.
+- In [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)), the animations are updated based on the game time using hte `AnimatedSprite.Update` method.
+
+Running the game now shows both sprites animating automatically:
+
+- The slime bounces between two frames
+- The bat's wings flap in a continuous cycle
+
+|  |
+| :----------------------------------------------------------------------------------: |
+| **Figure 9-2: The slime and bat sprite animating** |
+
+## Conclusion
+
+Let's review what you accomplished in this chapter:
+
+- Created an `Animation` class to manage frame sequences and timing.
+- Extended the `TextureAtlas` class to support animation definitions.
+- Built an `AnimatedSprite` class that inherits from `Sprite`.
+- Applied inheritance to add animation capabilities while maintaining existing sprite functionality.
+- Used XML configuration to define animations separately from code.
+
+Now that we can efficiently manage and render sprites and animations, in the next chapter we'll start taking a look at user input.
+
+## Test Your Knowledge
+
+1. Why did we create a separate `Animation` class instead of putting animation properties directly in `AnimatedSprite`?
+
+ :::question-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 doesn't?
+
+ :::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..6f6bc593
--- /dev/null
+++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/animatedsprite.cs
@@ -0,0 +1,68 @@
+#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..634919f0
--- /dev/null
+++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/snippets/animation.cs
@@ -0,0 +1,44 @@
+#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..7361ba13
--- /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..085c9abe
--- /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");
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+
+ base.LoadContent();
+ }
+
+ 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..32bc87f4
--- /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'll retrieve that value then use the content manager to load the texture.
+ string texturePath = root.Element("Texture").Value;
+ atlas.Texture = content.Load(texturePath);
+
+ // The element contains individual elements, each one describing
+ // a different texture region within the atlas.
+ //
+ // Example:
+ //
+ //
+ //
+ //
+ //
+ // So we retrieve all of the elements then loop through each one
+ // and generate a new TextureRegion instance from it and add it to this atlas.
+ var regions = root.Element("Regions")?.Elements("Region");
+
+ if (regions != null)
+ {
+ foreach (var region in regions)
+ {
+ string name = region.Attribute("name")?.Value;
+ int x = int.Parse(region.Attribute("x")?.Value ?? "0");
+ int y = int.Parse(region.Attribute("y")?.Value ?? "0");
+ int width = int.Parse(region.Attribute("width")?.Value ?? "0");
+ int height = int.Parse(region.Attribute("height")?.Value ?? "0");
+
+ if (!string.IsNullOrEmpty(name))
+ {
+ atlas.AddRegion(name, x, y, width, height);
+ }
+ }
+ }
+
+ // The element contains individual elements, each one describing
+ // a different animation within the atlas.
+ //
+ // Example:
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ // So we retrieve all of the elements then loop through each one
+ // and generate a new Animation instance from it and add it to this atlas.
+ var animationElements = root.Element("Animations").Elements("Animation");
+
+ if (animationElements != null)
+ {
+ foreach (var animationElement in animationElements)
+ {
+ string name = animationElement.Attribute("name")?.Value;
+ float delayInMilliseconds = float.Parse(animationElement.Attribute("delay")?.Value ?? "0");
+ TimeSpan delay = TimeSpan.FromMilliseconds(delayInMilliseconds);
+
+ List frames = new List();
+
+ var frameElements = animationElement.Elements("Frame");
+
+ if (frameElements != null)
+ {
+ foreach (var frameElement in frameElements)
+ {
+ string regionName = frameElement.Attribute("region").Value;
+ TextureRegion region = atlas.GetRegion(regionName);
+ frames.Add(region);
+ }
+ }
+
+ Animation animation = new Animation(frames, delay);
+ atlas.AddAnimation(name, animation);
+ }
+ }
+
+ return atlas;
+ }
+ }
+ }
+}
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..3a586ead
--- /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'll retrieve that value then use the content manager to load the texture.
+ string texturePath = root.Element("Texture").Value;
+ atlas.Texture = content.Load(texturePath);
+
+ // The element contains individual elements, each one describing
+ // a different texture region within the atlas.
+ //
+ // Example:
+ //
+ //
+ //
+ //
+ //
+ // So we retrieve all of the elements then loop through each one
+ // and generate a new TextureRegion instance from it and add it to this atlas.
+ var regions = root.Element("Regions")?.Elements("Region");
+
+ if (regions != null)
+ {
+ foreach (var region in regions)
+ {
+ string name = region.Attribute("name")?.Value;
+ int x = int.Parse(region.Attribute("x")?.Value ?? "0");
+ int y = int.Parse(region.Attribute("y")?.Value ?? "0");
+ int width = int.Parse(region.Attribute("width")?.Value ?? "0");
+ int height = int.Parse(region.Attribute("height")?.Value ?? "0");
+
+ if (!string.IsNullOrEmpty(name))
+ {
+ atlas.AddRegion(name, x, y, width, height);
+ }
+ }
+ }
+
+ // The element contains individual elements, each one describing
+ // a different animation within the atlas.
+ //
+ // Example:
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ // So we retrieve all of the elements then loop through each one
+ // and generate a new Animation instance from it and add it to this atlas.
+ var animationElements = root.Element("Animations").Elements("Animation");
+
+ if (animationElements != null)
+ {
+ foreach (var animationElement in animationElements)
+ {
+ string name = animationElement.Attribute("name")?.Value;
+ float delayInMilliseconds = float.Parse(animationElement.Attribute("delay")?.Value ?? "0");
+ TimeSpan delay = TimeSpan.FromMilliseconds(delayInMilliseconds);
+
+ List frames = new List();
+
+ var frameElements = animationElement.Elements("Frame");
+
+ if (frameElements != null)
+ {
+ foreach (var frameElement in frameElements)
+ {
+ string regionName = frameElement.Attribute("region").Value;
+ TextureRegion region = atlas.GetRegion(regionName);
+ frames.Add(region);
+ }
+ }
+
+ Animation animation = new Animation(frames, delay);
+ atlas.AddAnimation(name, animation);
+ }
+ }
+
+ return atlas;
+ }
+ }
+}
\ No newline at end of file
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..712b383d
--- /dev/null
+++ b/articles/tutorials/building_2d_games/10_handling_input/index.md
@@ -0,0 +1,421 @@
+---
+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's 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'll 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.
+>
+> | 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)]
+
+#### 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're 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* 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** |
+
+## Conclusion
+
+In this chapter, you learned how to:
+
+- 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'll 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's 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's 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're 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's 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 aren't 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..75fafa14
--- /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");
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+
+ base.LoadContent();
+ }
+
+ 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/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..c85aa856
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/index.md
@@ -0,0 +1,419 @@
+---
+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
+
+Let's 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).
+
+Let's 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[](./snippets/key_down_every_frame.cs)]
+
+However, many game actions shouldn't repeat while a key is held. For instance, if the Space key makes your character jump, you probably don't 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)]
+
+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'll need to compare the current input state with the previous frame's state.
+
+So far, we've 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're 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, let's 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 directory called *Input* in the *MonoGameLibrary* project. We'll put all of our input related classes here.
+
+## The KeyboardInfo Class
+
+Let's 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* directory 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 needs a constructor 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. Let's 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'll 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 a key is being held down.
+- `IsKeyUp`: Returns true as long as a key is not being pressed.
+
+And for detecting state changes:
+
+- `WasKeyJustPressed`: Returns true only on the frame when a key changes from up-to-down.
+- `WasKeyJustReleased`: Returns true only on the frame when a 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, let's 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'll 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* directory 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
+
+Let's create this class in the *Input* directory of the *MonoGameLibrary* project. Add 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. Let's add these properties.
+
+First, we need properties for tracking mouse states:
+
+[!code-csharp[](./snippets/mouseinfo.cs#properties_states)]
+
+Next, we'll add properties for handling cursor position:
+
+[!code-csharp[](./snippets/mouseinfo.cs#properties_position)]
+
+> [!NOTE]
+> The position properties use a `SetPosition` method that we'll 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'll 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'll 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 needs a constructor 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. Let's start with our update method:
+
+[!code-csharp[](./snippets/mouseinfo.cs#methods_update)]
+
+Next, we'll add methods to check various button states:
+
+[!code-csharp[](./snippets/mouseinfo.cs#methods_buttonstate)]
+
+These methods serve two distinct purposes. For checking continuous states:
+
+- `IsKeyDown`: Returns true as long as a key is being held down.
+- `IsKeyUp`: Returns true as long as a key is not being pressed.
+
+And for detecting state changes:
+
+- `WasKeyJustPressed`: Returns true only on the frame when a key changes from up-to-down.
+- `WasKeyJustReleased`: Returns true only on the frame when a key 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'll 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.
+
+Let's create this class in the *Input* directory of the *MonoGameLibrary* project. Add a new file named *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)]
+
+Recall from the [previous chapter](../10_handling_input/index.md#gamepad-input) that 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'll 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 needs a constructor 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. Let's start with our update method:
+
+[!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'll 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'll 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 don't need to manage stopping it manually.
+
+That's it for the `GamePadInfo` class. Next, let's 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* directory 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 tha we have our input management system complete, let's update our game to use it. We'll 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=6,39-47,103-104,112-115)]
+
+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 let's 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=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're using our new `InputManager` class instead.
+
+|  |
+|:-----------------------------------------------------------------------------------------------:|
+| **Figure 11-1: The slime moving around based on device input** |
+
+## Conclusion
+
+In this chapter, you learned how to:
+
+- 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's 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..13e4ed7d
--- /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 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..9f20fdb1
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/snippets/game1.cs
@@ -0,0 +1,176 @@
+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");
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+
+ base.LoadContent();
+ }
+
+ 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;
+ 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 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..03c09520
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/snippets/gamepadinfo.cs
@@ -0,0 +1,160 @@
+#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..0d211341
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/snippets/inputmanager.cs
@@ -0,0 +1,63 @@
+#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..8c2e09ed
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/snippets/keyboardinfo.cs
@@ -0,0 +1,95 @@
+#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; }
+
+ ///
+ /// 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..acb23021
--- /dev/null
+++ b/articles/tutorials/building_2d_games/11_input_management/snippets/mouseinfo.cs
@@ -0,0 +1,226 @@
+#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..a577d186
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/index.md
@@ -0,0 +1,405 @@
+---
+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.
+
+Let's start by understanding the basics of collision detection and the different approaches we can use.
+
+## Understanding Collision Detection
+
+Before we start implementing collision detection, let's 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 triange** |
+
+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 don't 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's 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's 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 sine 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 ploygon shape overlaps 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 aren't 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 isn't 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.
+
+## 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 *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 will implement [`IEquatable`](https://learn.microsoft.com/en-us/dotnet/api/system.iequatable-1). When creating value types like this, it is recommended to implement `IEquatable` because it has better performance 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)
+
+### 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, while 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, 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)]
+
+> [!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, let's 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'll 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 doesn't move, providing no challenge in the game.
+
+Let's update our game to implement these changes using collision detection and response. Open *Game1.cs* and make the following changes:
+
+[!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 `using MonoGameLibrary` using directive was added so we can use the new `Circle` struct.
+2. The field `_batPosition` was added to track the position of the bat.
+3. The field `_batVelocity` was added to track the velocity of the bat.
+4. The `AssignRandomBatVelocity()` method was added which calculates a random x and y velocity for the bat to move at when called.
+5. 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.
+6. 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.
+7. 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
+
+Let's review what you accomplished in this chapter:
+
+- 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'll start exploring audio to add sound effects when a collision occurs and background music to 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's more accurate for round objects
+ - It handles rotating objects better
+ - It's simpler for continuous collision detection
+ - It's 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..288df71a
--- /dev/null
+++ b/articles/tutorials/building_2d_games/12_collision_detection/snippets/cirlce.cs
@@ -0,0 +1,158 @@
+#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..b7a63f8d
--- /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");
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+
+ base.LoadContent();
+ }
+
+ 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's 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's 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..1e098d9c
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..d8a5de9f
--- /dev/null
+++ b/articles/tutorials/building_2d_games/13_working_with_tilemaps/index.md
@@ -0,0 +1,248 @@
+---
+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've 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's 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're 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've 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'll 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.
+
+Let's implement this concept 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* directory 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 should require 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* directory 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 should require 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 should provide 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 should also provide 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, let's update our game to use them. We'll 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've been using only contains the sprites for the slime and bat animations. Let's update it to a new version that contains the tileset as well. Download the new texture atlas below by right-clicking the following image and saving it as *atlas.png* in the *Content/images* directory of the game project, overwriting the existing one.
+
+|  |
+|:-----------------------------------------------------------------------------------------------------------------:|
+| **Figure 13-3: The texture atlas for our game updated to include the tileset for the tilemap** |
+
+Since the texture atlas image was updated and the location of the slime and bat sprites were repositioned, we need to update the atlas configuration file as well. Open the *atlas-definition.xml* configuration file and update it to the following:
+
+[!code-xml[](./snippets/atlas-definition.xml)]
+
+This change just adjusts the x and y coordinate positions to match the new locations of the slime and bat sprites in the atlas.
+
+## Creating a Tilemap XML Configuration
+
+Now that we have the texture atlas updated to include the tileset, let's 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.
+
+Create a new file named *tilemap-definition.xml* in the *Content/images* directory of the game project and add the following:
+
+[!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.
+
+Next, we need to add this configuration file to our content project with 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. Select the *tilemap-definition.xml* file you just created.
+4. In the Properties panel, change the *Build Action* property from *Build* to *Copy*.
+5. 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** |
+
+### Update the Game1 Class
+
+With all of the assets now in place and configured, let's update the `Game1` class to load the tilemap and draw it. We'll 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,78-79,111,113,115,117,120,122,124,126,144,147,149,152,155,158,160,163,183-186,308-309)]
+
+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), the tilemap is loaded from the XML configuration file.
+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**](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.
+
+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've learned in this chapter form the foundation for working with any tilemap system, regardless of which tool you might use.
+
+## Conclusion
+
+Let's review what you accomplished in this chapter:
+
+- Learned about tilemaps and how they're 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'll 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..482b80ee
--- /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..d4c2f923
--- /dev/null
+++ b/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/game1.cs
@@ -0,0 +1,322 @@
+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(
+ _tilemap.TileSize,
+ _tilemap.TileSize,
+ screenBounds.Width - _tilemap.TileSize * 2,
+ screenBounds.Height - _tilemap.TileSize * 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, centerRow) * _tilemap.TileSize;
+
+ // 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");
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+
+ // Create the tilemap from the XML configuration file.
+ _tilemap = Tilemap.FromFile(Content, "images/tilemap-definition.xml");
+
+ base.LoadContent();
+ }
+
+ 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's 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's 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))
+ {
+ // 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
+ // available within the bounds of the tilemap walls.
+ 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..2c0d42fa
--- /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
+
+
\ No newline at end of file
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..47f6249f
--- /dev/null
+++ b/articles/tutorials/building_2d_games/13_working_with_tilemaps/snippets/tilemap.cs
@@ -0,0 +1,241 @@
+#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 the size of each tile in this tilemap.
+ ///
+ public int TileSize { get; }
+ #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.
+ /// The size of each tile in this tilemap.
+ public Tilemap(Tileset tileset, int columns, int rows, int tileSize)
+ {
+ _tileset = tileset;
+ Rows = rows;
+ Columns = columns;
+ Count = Columns * Rows;
+ TileSize = tileSize;
+ _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 * TileSize, y * TileSize);
+ tile.Draw(spriteBatch, position, Color.White);
+ }
+ }
+ #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 an attribute that specifies the
+ // size of each tile in the tilemap
+ int tileSize = int.Parse(root.Attribute("tileSize")?.Value ?? "0");
+
+ // 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, tileSize);
+
+ // 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..b943b17c
--- /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, x, 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..c12a707b
--- /dev/null
+++ b/articles/tutorials/building_2d_games/14_soundeffects_and_music/index.md
@@ -0,0 +1,253 @@
+---
+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.
+
+Let's 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). |
+
+## 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) - For when the bat bounces off screen edges
+- [collect.wav](./files/collect.wav) - For when the slime eats the bat
+- [theme.ogg](./files/theme.ogg) - 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 directory called `audio` (right-click *Content* > *Add* > *New Folder*).
+3. Right-click the new *audio* directory 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`.
+
+Next, open the *Game1.cs* file and update it to the following:
+
+[!code-csharp[](./snippets/game1.cs?highlight=3,6,39-43,89-108,203,222)]
+
+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
+
+Let's review what you accomplished in this chapter:
+
+- 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'll 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're 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's 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..9d988c73
--- /dev/null
+++ b/articles/tutorials/building_2d_games/14_soundeffects_and_music/snippets/game1.cs
@@ -0,0 +1,351 @@
+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(
+ _tilemap.TileSize,
+ _tilemap.TileSize,
+ screenBounds.Width - _tilemap.TileSize * 2,
+ screenBounds.Height - _tilemap.TileSize * 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, centerRow) * _tilemap.TileSize;
+
+ // 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");
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+
+ // Load the tilemap from the XML configuration file.
+ _tilemap = Tilemap.FromFile(Content, "images/tilemap-definition.xml");
+
+ // 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 isn't 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;
+
+ base.LoadContent();
+ }
+
+ 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's 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's 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..a3635d7c
--- /dev/null
+++ b/articles/tutorials/building_2d_games/15_audio_controller/index.md
@@ -0,0 +1,163 @@
+---
+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'll 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 directory named *Audio*.
+2. Add a new class file named *AudioController.cs* to the *Audio* directory 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's 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` will need 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.
+
+#### 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 doesn't 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 Volume Control
+
+The `AudioController` also provides methods to increase and decrease the global volume of songs and sound effects. Add the following methods:
+
+[!code-csharp[](./snippets/audiocontroller.cs#volume)]
+
+#### AudioController IDisposable Implementation
+
+Finally, the `AudioController` implements the `IDisposable` interface. Add the following methods:
+
+[!code-csharp[](./snippets/audiocontroller.cs#idisposable)]
+
+Games often use limited system resources like audio channels. When we're 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're 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, let's update the game to use it. We'll 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'll 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,101-102,196-197,220-221,274-290)]
+
+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 global volume is increased by `0.1f`.
+ 3. If the - key is pressed, the global volume is 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.** |
+
+## Conclusion
+
+Let's review what you accomplished in this chapter:
+
+- 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'll 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..bc43a242
--- /dev/null
+++ b/articles/tutorials/building_2d_games/15_audio_controller/snippets/audiocontroller.cs
@@ -0,0 +1,267 @@
+#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 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 don't 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
+
+ #region volume
+ ///
+ /// Increases volume of all audio by the specified amount.
+ ///
+ /// The amount to increase the audio by.
+ public void IncreaseVolume(float amount)
+ {
+ if (!IsMuted)
+ {
+ MediaPlayer.Volume = Math.Min(MediaPlayer.Volume + amount, 1.0f);
+ SoundEffect.MasterVolume = Math.Min(SoundEffect.MasterVolume + amount, 1.0f);
+ }
+ }
+
+ ///
+ /// Decreases the volume of all audio by the specified amount.
+ ///
+ /// The amount to decrease the audio by.
+ public void DecreaseVolume(float amount)
+ {
+ if (!IsMuted)
+ {
+ MediaPlayer.Volume = Math.Max(MediaPlayer.Volume - amount, 0.0f);
+ SoundEffect.MasterVolume = Math.Max(SoundEffect.MasterVolume - amount, 0.0f);
+ }
+ }
+ #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..54b595f8
--- /dev/null
+++ b/articles/tutorials/building_2d_games/15_audio_controller/snippets/game1.cs
@@ -0,0 +1,368 @@
+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(
+ _tilemap.TileSize,
+ _tilemap.TileSize,
+ screenBounds.Width - _tilemap.TileSize * 2,
+ screenBounds.Height - _tilemap.TileSize * 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, centerRow) * _tilemap.TileSize;
+
+ // 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");
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+
+ // Load the tilemap from the XML configuration file.
+ _tilemap = Tilemap.FromFile(Content, "images/tilemap-definition.xml");
+
+ // 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");
+
+ base.LoadContent();
+ }
+
+ 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's 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's 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))
+ {
+ // 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();
+
+ // 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.IncreaseVolume(0.1f);
+ }
+
+ // If the - button was pressed, decrease the volume.
+ if (Input.Keyboard.WasKeyJustPressed(Keys.OemMinus))
+ {
+ Audio.DecreaseVolume(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);
+ }
+}
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_11.TTF b/articles/tutorials/building_2d_games/16_working_with_spritefonts/files/04B_11.TTF
new file mode 100644
index 00000000..86337e5f
Binary files /dev/null and b/articles/tutorials/building_2d_games/16_working_with_spritefonts/files/04B_11.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..ab6dae22
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..a1d732ef
--- /dev/null
+++ b/articles/tutorials/building_2d_games/16_working_with_spritefonts/index.md
@@ -0,0 +1,263 @@
+---
+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've already 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.
+
+Let's 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. 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. The MGCB Editor makes this process straightforward. 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'll 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 recommends using [Roboto](https://fonts.google.com/specimen/Roboto).
+
+Alternatively, for better portability across development environments, it's 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 directory as the *.spritefont* file.
+
+ > [!IMPORTANT]
+ > You place the font file in the same directory 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'll 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'll 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/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/images/atlas.png b/articles/tutorials/building_2d_games/17_scenes/images/atlas.png
new file mode 100644
index 00000000..57e68dd5
Binary files /dev/null and b/articles/tutorials/building_2d_games/17_scenes/images/atlas.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..7f685309
--- /dev/null
+++ b/articles/tutorials/building_2d_games/17_scenes/index.md
@@ -0,0 +1,366 @@
+---
+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 active scene is first 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'll create concrete implementations of this, like a title scene.
+
+To get started, in the *MonoGameLibrary* project:
+
+1. Create a new directory named *Scenes*.
+2. Add a new class file named *Scene.cs* to the *Scenes* directory 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 14](../14_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 directory 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 overidden 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)]
+
+## 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'll create two scenes; a title scene and a gameplay scene.
+
+### The Title Scene
+
+The title scene serves as the game's initial starting point, making the first impression on the player when they first launch the game. For our game, this scene will display stylized text for the title of the game and a prompt for an action for the user to perform to start the game. The stylized text is a graphic that was created and added to the texture atlas which features the title of the game with a drop shadow effect on the text. So first, let's update the texture atlas to the new version with the title graphic. Download the new texture atlas below by right-clicking the following image and saving it as *atlas.png* in the *Content/images* directory of the game project, overwriting the existing one:
+
+|  |
+|:------------------------------------------------------------------------------------------------------:|
+| **Figure 17-1: The texture atlas for our game updated to include the title sprite** |
+
+Next, open the *atlas-definition.xml* file and add the region for the title sprite:
+
+[!code-xml[](./snippets/atlas-definition.xml?highlight=10)]
+
+With the atlas now updated, create the `TitleScene` class file. In the main game project:
+
+1. Create a new directory named *Scenes*. We'll put all of our game specific scenes here.
+2. Add a new class file named *TitleScene.cs* to the *Scenes* directory 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)]
+
+- The `PRESS_ENTER` constant is the text we'll draw for the press enter prompt for the user.
+- The `_font` field stores a reference to the sprite font we'll load to render the press enter prompt with.
+- The `_titleSprite` field stores a reference the sprite we'll render for the stylized title from the texture atlas
+- The `_titlePos` and `_pressEnterPos` fields store the precalculated positions for the title sprite and the press enter prompt text when they are drawn. Since they are stationary, we can just calculate the positions once and store it instead of calculating it each frame.
+- The `_pressEnterOrigin` field stores the precalculated origin for hte press enter prompt text when it is drawn. Like with the position, we only need to calculate this once and store it instead of calculating it each frame.
+
+#### 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 bounds of the screen is captures by using the `Core.GraphicsDevice.PresentationParameters.Bounds` value.
+- The position to draw the title sprite is precalculated so that it will be centered horizontally and 80px down from the top of the screen. The origin is set to center.
+- The position to draw the press enter prompt is precalculated so that it will be centered horizontally and 100 px above the bottom of the screen. The string is measured and used to center the origin for the text.
+
+##### Title Scene LoadContent
+
+Add the following override for the `LoadContent` method to the `TitleScene` class:
+
+[!code-csharp[](./snippets/titlescene.cs#loadcontent)]
+
+- The font used to draw the press enter prompt is loaded.
+- The texture atlas is loaded using the XML configuration file.
+- The `_titleSprite` is generated from the `"title-card"` region in the atlas.
+
+> [!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 haven't created the `GameScene` class yet. We'll create it in a moment after finishing the title scene.
+
+##### Title Scene Draw
+
+Add the following override for the `Draw` method to the `TitleScene` class:
+
+[!code-csharp[](./snippets/titlescene.cs#draw)]
+
+- The back buffer is cleared.
+- The title sprite is drawn at its precalculated position.
+- The press enter prompt is drawn at its precalculated position.
+
+### 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'll move it into a dedicated scene class. In the *Scenes* directory:
+
+1. Add a new class file named *GameScene.cs*.
+2. Add the following code as the initial structure for the class:
+
+ [!code-csharp[](./snippets/gamescene.cs#declaration)]
+
+#### 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 `_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.
+
+#### 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 height of the font is measured to calculate proper vertical positioning for the game elements.
+- The initial positions of the slime and bat are set, placing the slime below where the score will be displayed and the bat a small distance to the right of the slime.
+- 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)]
+
+- Similar to the title scene, we capture a reference to the global content manager from the `Core` class.
+- The texture atlas is loaded using the global content manager, and the slime and bat animated sprites are created from it.
+- The sound effects are loaded using the scene's content manager since they're 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're 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 screen bounds.
+ - Make the bat bounce off screen edges.
+ - Detect when the slime eats the bat.
+- When the slime eats the bat, the bat respawns in a random location, given a new velocity, the collect sound is played, and the score is increased.
+
+##### Game Scene Helper Methods
+
+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
+
+Add the following override for the `Draw` method to the `GameScene` class:
+
+[!code-csharp[](./snippets/gamescene.cs#draw)]
+
+- A reference to the graphics device from the `Core` instance is captured so we don't have to type `Core.Instance.GraphicsDevice` each time to use it.
+- The back buffer is cleared.
+- The slime and bat animated sprites are drawn at the current position.
+- The player's score is drawn in the top-left corner of the screen.
+
+### 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 update it to the following:
+
+[!code-csharp[](./snippets/game1.cs)]
+
+The `Game1` class is now much simpler as most of the game logic has been moved to the appropriate scene classes. It:
+
+1. Inherits from our `Core` class instead of the MonoGame Game class.
+2. Sets up the game window with the constructor parameters.
+3. Overrides the `Initialize` method to set the title scene as the starting scene.
+4. 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've 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'll 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/atlas-definition.xml b/articles/tutorials/building_2d_games/17_scenes/snippets/atlas-definition.xml
new file mode 100644
index 00000000..5cf4646f
--- /dev/null
+++ b/articles/tutorials/building_2d_games/17_scenes/snippets/atlas-definition.xml
@@ -0,0 +1,24 @@
+
+
+ images/atlas
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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..8acd161c
--- /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 doesn't 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..7553f265
--- /dev/null
+++ b/articles/tutorials/building_2d_games/17_scenes/snippets/game1.cs
@@ -0,0 +1,35 @@
+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()
+ {
+ base.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..5f57b5a0
--- /dev/null
+++ b/articles/tutorials/building_2d_games/17_scenes/snippets/gamescene.cs
@@ -0,0 +1,372 @@
+#region declaration
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Content;
+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;
+
+ // 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;
+ #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;
+
+ // Determine the height of the font
+ float textHeight = _font.MeasureString("A").Y;
+
+ // Place the slime at a position below where the score will be displayed
+ _slimePosition = new Vector2(0, textHeight + 10);
+
+ // Set the initial position of the bat to be 10px to the right of the slime.
+ _batPosition = _slimePosition + new Vector2(_slime.Width + 10.0f, 0.0f);
+
+ // 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");
+
+ // Create the bat animated sprite from the atlas.
+ _bat = atlas.CreateAnimatedSprite("bat-animation");
+
+ // 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/gameFont");
+ }
+ #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();
+
+ // Create a bounding rectangle for the screen
+ Rectangle screenBounds = Core.GraphicsDevice.PresentationParameters.Bounds;
+
+ // 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's 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's 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);
+
+ // Play the bounce sound effect
+ Core.Audio.PlaySoundEffect(_bounceSoundEffect);
+ }
+
+ _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 = screenBounds.Width / (int)_bat.Width;
+ int totalRows = screenBounds.Height / (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();
+
+ // 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()
+ {
+ // 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 (Core.Input.Keyboard.IsKeyDown(Keys.Space))
+ {
+ speed *= 1.5f;
+ }
+
+ // If the W or Up keys are down, move the slime up on the screen.
+ if (Core.Input.Keyboard.IsKeyDown(Keys.W) || Core.Input.Keyboard.IsKeyDown(Keys.Up))
+ {
+ _slimePosition.Y -= speed;
+ }
+
+ // if the S or Down keys are down, move the slime down on the screen.
+ if (Core.Input.Keyboard.IsKeyDown(Keys.S) || Core.Input.Keyboard.IsKeyDown(Keys.Down))
+ {
+ _slimePosition.Y += speed;
+ }
+
+ // If the A or Left keys are down, move the slime left on the screen.
+ if (Core.Input.Keyboard.IsKeyDown(Keys.A) || Core.Input.Keyboard.IsKeyDown(Keys.Left))
+ {
+ _slimePosition.X -= speed;
+ }
+
+ // If the D or Right keys are down, move the slime right on the screen.
+ if (Core.Input.Keyboard.IsKeyDown(Keys.D) || Core.Input.Keyboard.IsKeyDown(Keys.Right))
+ {
+ _slimePosition.X += speed;
+ }
+
+ // If the M key is pressed, toggle mute state for audio.
+ if (Core.Input.Keyboard.WasKeyJustPressed(Keys.M))
+ {
+ Core.Audio.ToggleMute();
+ }
+
+ // If the + button is pressed, increase the volume.
+ if (Core.Input.Keyboard.WasKeyJustPressed(Keys.OemPlus))
+ {
+ Core.Audio.IncreaseVolume(0.1f);
+ }
+
+ // If the - button was pressed, decrease the volume.
+ if (Core.Input.Keyboard.WasKeyJustPressed(Keys.OemMinus))
+ {
+ Core.Audio.DecreaseVolume(0.1f);
+ }
+ }
+
+ private void CheckGamePadInput()
+ {
+ 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 slime sprite.
+ _slime.Draw(Core.SpriteBatch, _slimePosition);
+
+ // Draw the bat sprite.
+ _bat.Draw(Core.SpriteBatch, _batPosition);
+
+ // Draw the score
+ Core.SpriteBatch.DrawString(_font, $"Score: {_score}", Vector2.Zero, Color.White);
+
+ // 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/titleFont.spritefont b/articles/tutorials/building_2d_games/17_scenes/snippets/titleFont.spritefont
new file mode 100644
index 00000000..3c1bbfe8
--- /dev/null
+++ b/articles/tutorials/building_2d_games/17_scenes/snippets/titleFont.spritefont
@@ -0,0 +1,16 @@
+
+
+
+ 04B_30.ttf
+ 85
+ 0
+ true
+
+
+
+
+ ~
+
+
+
+
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..bd2a94c3
--- /dev/null
+++ b/articles/tutorials/building_2d_games/17_scenes/snippets/titlescene.cs
@@ -0,0 +1,116 @@
+#region declaration
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using Microsoft.Xna.Framework.Media;
+using MonoGameLibrary;
+using MonoGameLibrary.Graphics;
+using MonoGameLibrary.Scenes;
+
+namespace DungeonSlime.Scenes;
+
+public class TitleScene : Scene
+{
+
+}
+#endregion
+{
+ #region fields
+ private const string PRESS_ENTER = "Press Enter To Start";
+
+ // The font to use to render normal text.
+ private SpriteFont _font;
+
+ // The sprite to draw for the stylized title
+ private Sprite _titleSprite;
+
+ // The position to draw the title sprite at.
+ private Vector2 _titlePos;
+
+ // 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;
+
+ // Get the bounds of the screen for position calculations
+ Rectangle screenBounds = Core.GraphicsDevice.PresentationParameters.Bounds;
+
+ // Precalculate the positions and origins for texts and the slime sprite
+ // so we're not calculating it every draw frame.
+ _titlePos = new Vector2(
+ screenBounds.Width * 0.5f,
+ 80 + _titleSprite.Height * 0.5f);
+
+ // Center the origin of the title sprite.
+ _titleSprite.CenterOrigin();
+
+ // Precalculate the position of for the press enter text so that it is
+ // centered horizontally and place 100 pixels above the bottom of the
+ // screen.
+ _pressEnterPos = new Vector2(
+ screenBounds.Width * 0.5f,
+ screenBounds.Height - 100
+ );
+
+ // Precalculate the center origin of the press enter text.
+ Vector2 pressEnterSize = _font.MeasureString(PRESS_ENTER);
+ _pressEnterOrigin = pressEnterSize * 0.5f;
+ }
+ #endregion
+
+ #region loadcontent
+ public override void LoadContent()
+ {
+ // Load the font for the standard txt.
+ _font = Core.Content.Load("fonts/gameFont");
+
+ // Create a texture atlas from the XML configuration file.
+ TextureAtlas atlas = TextureAtlas.FromFile(Core.Content, "images/atlas-definition.xml");
+
+ _titleSprite = atlas.CreateSprite("title-card");
+ }
+ #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);
+
+ // Draw that title sprite
+ _titleSprite.Draw(Core.SpriteBatch, _titlePos);
+
+ // Draw the press enter text
+ Core.SpriteBatch.DrawString(_font, PRESS_ENTER, _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/index.md b/articles/tutorials/building_2d_games/index.md
new file mode 100644
index 00000000..84835a7f
--- /dev/null
+++ b/articles/tutorials/building_2d_games/index.md
@@ -0,0 +1,92 @@
+---
+title: Building 2D Games with MonoGame
+description: A beginner tutorial for building 2D games with MonoGame.
+---
+
+This tutorial covers game development concepts using the MonoGame framework as our tool. Throughout each chapter, we will explore game development with MonoGame, introducing new concepts to build upon previous ones as we go. We will create a Snake game, which will serve as the vehicle to apply the concepts learned throughout the tutorial. The goal of this tutorial is to give you a solid foundation in 2D game development with MonoGame and provide you with reusable modules that you can leverage to jump start future projects.
+
+## Who This Is For
+
+This documentation is meant to be an introduction to game development and MonoGame. Readers should have a foundational understanding of C# and be comfortable with concepts such as classes and objects.
+
+> [!NOTE]
+> If you are just getting started with C# for the first time, I would recommend following the official [Learn C#](https://dotnet.microsoft.com/en-us/learn/csharp) tutorials provided by Microsoft. These free tutorials will teach you programming concepts as well as the C# language. Once you feel you have a good foundation, come back and continue here.
+
+## How This Documentation Is Organized
+
+This documentation will introduce game development concepts using the MonoGame framework while walking the reader through the development of a Snake clone. The documentation is organized such that each chapter should be read sequentially, with each introducing new concepts and building off of the previous chapters.
+
+> [!CAUTION]
+> This is currently a work in progress and is not finished.
+
+| Chapter | Summary | Source Files |
+| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
+| [01: What Is MonoGame](01_what_is_monogame/index.md) | Learn about the history of MonoGame and explore the features it provides to developers when creating games. | |
+| [02: Getting Started](02_getting_started/index.md) | Setup your development environment for .NET development and MonoGame using Visual Studio Code as your IDE. | |
+| [03: The Game1 File](03_the_game1_file/index.md) | Explore the contents of the Game1 file generated when creating a new MonoGame project. | |
+| [04: Creating a Class Library](04_creating_a_class_library/index.md) | Learn how to create and structure a reusable MonoGame class library to organize game components and share code between projects. | |
+| [05: Content Pipeline](05_content_pipeline/index.md) | Learn the advantages of using the **Content Pipeline** to load assets and go through the processes of loading your first asset | |
+| [06: Working with Textures](06_working_with_textures/index.md) | Learn how to load and render textures using the MonoGame content pipeline and [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch). | |
+| [07: Optimizing Texture Rendering](07_optimizing_texture_rendering/index.md) | Explore optimization techniques when rendering textures using a texture atlas. | |
+| [08: The Sprite Class](08_the_sprite_class/index.md) | Explore creating a reusable Sprite class to efficiently manage sprites and their rendering properties, including position, rotation, scale, and more. | |
+| [09: The AnimatedSprite Class](09_the_animatedsprite_class/index.md) | Create an AnimatedSprite class that builds upon our Sprite class to support frame-based animations. | |
+| [10: Handling Input](10_handling_input/index.md) | Learn how to handle keyboard, mouse, and gamepad input in MonoGame. | |
+| [11: Input Management](11_input_management/index.md) | 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. | |
+| [12: Collision Detection](12_collision_detection/index.md) | Learn how to implement collision detection between game objects and handle collision responses like blocking, triggering events, and bouncing. | |
+| [13: Working With Tilemaps](13_working_with_tilemaps/index.md) | 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. | |
+| [14: Sound Effects and Music](14_soundeffects_and_music/index.md) | Learn how to load and play sound effects and background music in MonoGame, including managing audio volume, looping, and handling multiple simultaneous sound effects. | |
+| [15: Audio Controller](15_audio_controller/index.md) | Learn how to create a reusable audio controller class to manage sound effects and music, including volume control, muting/unmuting, and proper resource cleanup. | |
+| [16: Working with SpriteFonts](16_working_with_spritefonts/index.md) | Learn how to create and use SpriteFonts to render text in your MonoGame project, including loading custom fonts and controlling text appearance. | |
+| [17: Scenes](17_scenes/index.md) | Learn how to implement scene management to handle different game screens like menus, gameplay, and transitions between scenes. | |
+
+In addition to the chapter documentation, supplemental documentation is also provided to give a more in-depth look at different topics with MonoGame. These are provided through the Appendix documentation below:
+
+| Appendix | Summary |
+| -------- | ------- |
+| | |
+
+## Conventions Used in This Documentation
+
+The following conventions are used in this documentation.
+
+### Italics
+
+*Italics* are used for emphasis, technical terms, and paths such as file paths, including filenames and extensions.
+
+### Inline Code Blocks
+
+`Inline code` blocks are used for methods, functions, and variable names when they are discussed within the body of a paragraph.
+
+### Code Blocks
+
+```cs
+// Example Code Block
+public void Foo() { }
+```
+
+Code blocks are used to show code examples with syntax highlighting.
+
+## MonoGame
+
+If you ever have questions about MonoGame or would like to talk with other developers to share ideas or just hang out with us, you can find us in the various MonoGame communities below
+
+- [Discord](https://discord.gg/monogame)
+- [GitHub Discussions Forum](https://github.com/MonoGame/MonoGame/discussions)
+- [Community Forums (deprecated)](https://community.monogame.net/)
+- [Reddit](https://www.reddit.com/r/monogame/)
+- [Facebook](https://www.facebook.com/monogamecommunity)
+
+## Note From Author
+
+> I have been using MonoGame for the past several years (since 2017). It was a time in my game development journey where I was looking for something that I had more control over. I didn't want to spend the time to write a full game engine, but I also wanted to have more control than what the current engines at the time (i.e. Unity) offered. At that time, there was a vast amount of resources available for getting started, but none of them felt like they fit a good beginner series. Even now, the resources available still seem this way. They either require the reader to have a great understanding of game development and programming, or they assume the reader has none and instead focuses on teaching programming more than teaching MonoGame. Even still, some relied too heavily on third party libraries, while others were simply very bare bones, asking the reader to just copy and paste code without explaining the *what* of it all.
+>
+> Since then, I have written various small self contained tutorials on different topics for MonoGame to try and give back to the community for those getting started. I also participate regularly in the community discussion channels, answering questions and offering technical advice, so I'm very familiar with the topics and pain points that users get hung up on when first starting out.
+>
+> With this documentation, I hope to take the lessons I've learned and provide a tutorial series that I wish was available when I first started, and to present using MonoGame in a straight forward way, introducing concepts and building off of them as we go along in a way that makes sense and is easy to follow.
+>
+> \- Christopher Whitley (Aristurtle)
+
+## Acknowledgements
+
+> [!NOTE]
+> Acknowledgments will be added at a later time to recognize everyone that assisted with editing and reviewing this documentation.
diff --git a/articles/tutorials.md b/articles/tutorials/index.md
similarity index 100%
rename from articles/tutorials.md
rename to articles/tutorials/index.md
diff --git a/docfx.json b/docfx.json
index 7168e601..1f4a0a83 100644
--- a/docfx.json
+++ b/docfx.json
@@ -50,7 +50,9 @@
{
"files": [
"**/images/**",
+ "**/videos/**",
"**/files/**",
+ "**/snippets/**",
"CNAME"
]
}
@@ -72,6 +74,14 @@
"modern",
"templates/monogame"
],
+ "markdownEngineProperties": {
+ "markdigExtensions": [
+ "Abbreviations",
+ "Figures",
+ "CustomContainers"
+
+ ]
+ },
"postProcessors": [],
"keepFileLink": false,
"disableGitFeatures": false
diff --git a/templates/monogame/layout/_master.tmpl b/templates/monogame/layout/_master.tmpl
index f9686160..8d747c68 100644
--- a/templates/monogame/layout/_master.tmpl
+++ b/templates/monogame/layout/_master.tmpl
@@ -78,7 +78,8 @@
{{/_enableSearch}}
{{>partials/footer}}
-
+