diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/images/content-pipeline-workflow-full.png b/articles/tutorials/building_2d_games/05_content_pipeline/images/content-pipeline-workflow-full.png new file mode 100644 index 00000000..4be30284 Binary files /dev/null and b/articles/tutorials/building_2d_games/05_content_pipeline/images/content-pipeline-workflow-full.png differ diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/images/content-pipeline-workflow-full.svg b/articles/tutorials/building_2d_games/05_content_pipeline/images/content-pipeline-workflow-full.svg index da04f7ba..5ea48230 100644 --- 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 @@ -1 +1,773 @@ -MonoGame Content Pipeline WorkflowRuntime PhaseBuild PhaseCreate the source files you will use in your game including images, audio, fonts, effects, and models.Use the MonoGame Content Builder (MGCB) Editor to edit your content project by adding and/or removing the source files to be used in your game.Development PhaseMGCB EditorMGCB ToolMonoGame.Content.Builder.TasksContent.mgcbAudioImagesAaFontsEffectsModelsSource FilesXnbXnbAaXnbXnbXnbCompiled AssetsWhen performing a build of your project, the MonoGame.Content.Builder.Tasks reference will automatically compile the source files defined in the Content.mgcb content project file into XNB files using the MonoGame Content Builder (MGCB) Tool.The compiled assets are then automatically copied from your content project build folder to your game's build folderAt runtime in the game, load the compiled assets using the ContentManager objectWhen saving the content project in the MGCB Editor, it will generate the Content.mgcb content project fileContentManagerHigh-Level View + +MonoGame Content Pipeline WorkflowMGCB EditorContent.mgcbAudioImagesFontsEffectsModelsSource FilesMonoGame.Content.Builder.TasksXnbXnbXnbXnbXnbCompiled AssetsContentManagerHigh-Level View AaAa diff --git a/articles/tutorials/building_2d_games/05_content_pipeline/index.md b/articles/tutorials/building_2d_games/05_content_pipeline/index.md index ced05025..2d30eabe 100644 --- a/articles/tutorials/building_2d_games/05_content_pipeline/index.md +++ b/articles/tutorials/building_2d_games/05_content_pipeline/index.md @@ -1,6 +1,6 @@ --- 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 +description: Learn the advantages of using the Content Pipeline to load assets and go through the processes of loading your first asset --- Every game has assets; images to represent the visual graphics to players, audio to provide sound effects and background music, fonts to render text with, and much more. These assets start out as raw files (e.g. *.png* image files or *.mp3* audio files), which you will need to load into the game to use. @@ -16,9 +16,7 @@ For instance, to load an image file directly at runtime, you would need to: 3. Load the image file as a texture at runtime using the [**Texture2D.FromFile**](xref:Microsoft.Xna.Framework.Graphics.Texture2D.html#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 will explore below, using the **Content Pipeline** handles this for you automatically. +> A big disadvantage to loading an image file as a texture directly, is when that when it loads it, it does so in its compressed format such as *.png* or *.jpg*. These compression formats are not understood by a Graphics Processing Unit (GPU); they will need to be decompressed into raw bytes as a format the GPU does understand before it can store the data. Doing this can potentially leave a larger memory footprint for your assets. You will also need to handle how different compression formats work on the platform you are targeting such as desktops, mobile, and consoles. On the other side of this coin, MonoGame offers the **Content Pipeline**; a workflow for managing assets. The workflow is made up of a set of tools and utilities that are automatically added by default when you create a new MonoGame project using the MonoGame project templates. To use this workflow, you need to: @@ -26,12 +24,6 @@ On the other side of this coin, MonoGame offers the **Content Pipeline**; a work 2. Perform a project build. Doing this, the *MonoGame.Content.Builder.Tasks* NuGet reference will compile the assets defined in the content project, optimized for the target platform, and automatically copy them to the game project build folder. 3. Load the compiled asset at runtime using the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager). -The following image illustrates this workflow: - -| ![Figure 5-1: MonoGame Content Pipeline Workflow](./images/content-pipeline-workflow-full.svg) | -|:----------------------------------------------------------------------------------------------:| -| **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] @@ -41,28 +33,17 @@ For this tutorial series, we are going to focus on using the content pipeline wo ## 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 folder. This file can be edited manually by hand, however it is much easier to use the MGCB Editor instead. - -### Opening the MGCB Editor +As mentioned previously, the content pipeline workflow in MonoGame is made up of a set of tools that come with every new MonoGame project. At the center of this workflow is the MGCB Editor; a graphical tool for managing your game's content. -There are different methods of opening the MGCB Editor tool depending on your IDE and environment: +Opening the MGCB Editor can be done in different ways depending on which IDE and development environment you have. Choose the one you are using below to open the MGCB Editor so we can explore its interface: ### [Visual Studio Code](#tab/vscode) To open the *Content.mgcb* content project file in the MGCB Editor with Visual Studio Code, you can use the *MonoGame for VSCode* extension. You should have installed this extension in [Chapter 02](../02_getting_started/index.md#installing-the-monogame-for-vscode-extension). With this extension install, anytime you have a code file open, you will see the MonoGame logo in the top-right of the code window like below: -| ![Figure 5-2: MonoGame for VSCode extension icon](./images/mgcb-editor-icon.png) | -|:--------------------------------------------------------------------------------:| -| **Figure 5-2: MonoGame for VSCode extension icon** | +| ![Figure 5-1: MonoGame for VSCode extension icon](./images/mgcb-editor-icon.png) | +| :------------------------------------------------------------------------------: | +| **Figure 5-1: MonoGame for VSCode extension icon** | Clicking the MonoGame logo here will open the *Content.mgcb* content project file from the current project in the MGCB Editor. @@ -74,12 +55,16 @@ To open the *Content.mgcb* content project file in the MGCB Editor with Visual S To open the *Content.mgcb* content project file in the MGCB Editor using the dotnet CLI commands, perform the following: -1. Open a new Command Prompt or Terminal window in the same folder as your game project's *.csproj* file. -2. Enter the command `dotnet mgcb-editor ./Content/Content.mgcb` +1. Open a new Command Prompt or Terminal window in the same folder as the *DungeonSlime.csproj* project file (your main game project). +2. Enter the following dotnet CLI command + + ```sh + dotnet mgcb-editor ./Content/Content.mgcb` + ``` --- -> [!TIP] + [!TIP] > If for any reason the MGCB editor fails to load or you are hit with MGCB errors when you build your project, it is likely the MGCB references from the `dotnet-tools.json` configuration located in your projects `.config` folder have not been loaded/initialized. > > To correct this, simply run the following from a terminal/command prompt in your projects directory (there the `.config` folder is located) @@ -90,198 +75,204 @@ To open the *Content.mgcb* content project file in the MGCB Editor using the dot > > This should restore the tool with the version that is configured. If at any time you update your `dotnet-tools.json` configuration, e.g. when upgrading to a newer version of MonoGame, **you will need to run this command again** -| ![Figure 5-3: MonoGame Content Builder Editor (MGCB Editor) Window](./images/mgcb-editor.png) | -|:---------------------------------------------------------------------------------------------:| -| **Figure 5-3: MonoGame Content Builder Editor (MGCB Editor) Window** | +| ![Figure 5-2: MonoGame Content Builder Editor (MGCB Editor) Window](./images/mgcb-editor.png) | +| :-------------------------------------------------------------------------------------------: | +| **Figure 5-2: MonoGame Content Builder Editor (MGCB Editor) Window** | -In Figure 5-3 above, you can see the user interface for the MGCB Editor: +In Figure 5-2 above, you can see the user interface for the MGCB Editor: - **Toolbar**: Contains icon buttons for common actions such as creating new items, opening files, saving changes, and building content. - **Project Panel**: Located on the left of the MGCB Editor, displays a hierarchical tree view of all content items added to the content project. The root node *Content* represents the root of the content project. - **Properties Panel**: Located on the bottom left of the MGCB Editor, shows the properties of the currently selected item in the project panel. The properties available are based on the item type selected. - **Build Output Panel**: The large area to the right side outputs build messages, warnings, and errors when content is processed. -### Adding Assets +### Creating Folders to Organize Content -To add assets (such as textures, audio, etc) to the content project: +Organizing your game assets into folders helps keep your content project manageable as it grows. For now, we will add a new folder that will hold the image assets we will add to the game throughout this tutorial series. In the MGCB Editor: -1. In the Project panel, select the 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*. +1. In the Project Panel, select the root *Content* node. +2. Right-click it and choose *Add* > *New Folder...* from the context menu. +3. Type "images" for the folder name and click the "Ok" button. -When adding assets to the content project, a pop-up dialog will appear with the following options: +| ![Figure 5-3: New folder pop-up](./images/new-folder-popup.png) | +| :-------------------------------------------------------------: | +| **Figure 5-3: New folder pop-up** | -- **Copy the file to the folder**: Creates a duplicate of the file inside your project's Content folder. This creates an independent copy, meaning any later changes to the original file wo not 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 will need to re-establish the link. -- **Skip**: Cancels adding the current file while continuing with any other selected files. +You have now created a folder that will help organize the game's image assets. As we continue through this tutorial series, we will be adding additional folders for organization of content such as audio, fonts, and effects. -| ![Figure 5-4: Add existing file pop-up](./images/add-file-popup.png) | -|:--------------------------------------------------------------------:| -| **Figure 5-4: Add existing file pop-up** | +> [!NOTE] +> If you try to add a new folder that already exists in the file system but is not showing in the MGCB editor, you will get an error. Either remove the folder or use "Add Existing Folder" instead. -### Adding Built-In Asset Types +### Adding Your First Asset -To create a new asset using one of the built-in asset types in the MGCB Editor: +Now that we have a folder structure, we can add our first image asset to the project. For this example, we will use the MonoGame logo. Perform the following -1. 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. +1. First, download the MonoGame logo by right-clicking the following image and saving it as `logo.png` somewhere on your computer: -| ![Figure 5-5: New file pop-up](./images/new-file-popup.png) | -|:-----------------------------------------------------------:| -| **Figure 5-5: New file pop-up** | + | ![Figure 5-4: MonoGame Horizontal Logo](./images/logo.png) | + | :--------------------------------------------------------: | + | **Figure 5-4: MonoGame Horizontal Logo** | -> [!NOTE] -> Each built-in asset type comes with a template that includes the minimum required structure and settings. +2. In the MGCB Editor, select the *images* folder you created earlier. +3. Right-click it and choose *Add* > *Existing Item...* from the context menu. +4. Navigate to the location of the `logo.png` file you just downloaded and select it. +5. Click the "Open" button +6. When prompted in the add existing file popup, choose *Copy the file to the directory.* -### Adding Folders to Organize Content + | ![Figure 5-5: Add existing file pop-up](./images/add-file-popup.png) | + | :------------------------------------------------------------------: | + | **Figure 5-5: Add existing file pop-up** | -Organizing your game assets into folders helps keep your content project manageable as it grows. To add a new folder: +7. Save the changes to the content project by selecting *File* > *Save* from the top menu or pressing `CTRL+S`. -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: The logo image added to the content project in the MGCB Editor](./images/mgcb-logo-added.png) | +| :---------------------------------------------------------------------------------------------------------: | +| **Figure 5-6: The logo image added to the content project in the MGCB Editor** | -| ![Figure 5-6: New folder pop-up](./images/new-folder-popup.png) | -|:---------------------------------------------------------------:| -| **Figure 5-6: New folder pop-up | +> [!IMPORTANT] +> After changes have been made in the MGCB Editor, ensure that you save the changes. They are not automatically saved, though you will be warned if you close the editor and have not saved changes. You can tell that changes have not been saved by looking at the title bar of the MGCB editor window. If it has an '*' at the end of the window title, this means changes have not been saved. -The new folder will appear in your content tree, and you can now add items to it by: +## Understanding the Content Pipeline Workflow -- Adding existing assets directly to the folder -- Creating new assets within the folder +Now that we have added our first asset, we can take a moment to understand what happens to this asset in the Content Pipeline workflow: -The folder structure you create in the MGCB Editor affects how you will access your content in code. It is good practice to establish a folder structure early in your project development to avoid having to reorganize and update content paths later. +1. You create source files for your game assets such as images, audio, fonts, effects, and 3D models. +2. Using the MGCB Editor, add these assets your content project (the `Content.mgcb` file). +3. When you perform a build of your project, the `MonoGame.Content.Builder.Task` NuGet reference will: + 1. Compile the assets defined in the content project using the **MonoGame Content Builder (MGCB)** tool into `.xnb` files. + 2. Copy the compiled `.xnb` files from the content project's build folder to your game project's build folder. +4. At runtime, you load the compiled assets using the [ContentManager](xref:Microsoft.Xna.Framework.Content.ContentManager). -> [!NOTE] -> If you try to add a new folder that already exists in the file system but is not showing in the MGCB editor, you will get an error. Either remove the folder or use "Add Existing Folder" instead. +The following diagram demonstrates this workflow: -## The ContentManager Class +| ![Figure 5-7: MonoGame Content Pipeline Workflow diagram showing the process flow from source files (Images, Audio, Fonts, Effects, Models) through the MGCB Editor to generate the Content.mgcb file, which is then processed by MonoGame.Content.Builder.Tasks to create compiled .xnb assets (Xnb formats for each type), which are finally loaded by the ContentManager at runtime](./images/content-pipeline-workflow-full.png) | +| :--------------------------------------------------------------------------------------------: | +| **Figure 5-7: MonoGame Content Pipeline Workflow diagram showing the process flow from source files (Images, Audio, Fonts, Effects, Models) through the MGCB Editor to generate the Content.mgcb file, which is then processed by MonoGame.Content.Builder.Tasks to create compiled .xnb assets (Xnb formats for each type), which are finally loaded by the ContentManager at runtime** | -To load assets in code that have been processed through the content pipeline, MonoGame provides the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) class. +The Content Pipeline offers significant advantages: -> [!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). +- Assets are pre-processed and optimized for your target platform. +- Image files can be compiled using formats like DXT compression, which GPU's understand natively. +- Asset loading is simplified and consistent across platforms. -### ContentManager Properties - -The [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) offers the following properties: +## The ContentManager Class -| Property | Type | Description | -|--------------------------------------------------------------------------------------------|--------------------|-------------------------------------------------------------| -| [**RootDirectory**](xref:Microsoft.Xna.Framework.Content.ContentManager.RootDirectory) | `string` | The root folder the content manager searches for assets. | -| [**ServiceProvider**](xref:Microsoft.Xna.Framework.Content.ContentManager.ServiceProvider) | `IServiceProvider` | The service provider used by the content manager. | +To load assets in your game code, MonoGame provides the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) class. The [**Game**](xref:Microsoft.Xna.Framework.Game) already has a [**Content**](xref:Microsoft.Xna.Framework.Game.Content) property which is a ready-to-ue instance of the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) ### ContentManager Methods -The [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) offers the following methods: +They key methods for asset loading are: | Method | Returns | Description | -|------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [**Load<T>(string)**](xref:Microsoft.Xna.Framework.Content.ContentManager.Load``1(System.String)) | `T` | Loads the assets of type `T` that has been processed by the content pipeline. | -| [**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. +> When an asset is loaded for the first time, the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager) internally caches it. Loading the same asset again will return the cached version, avoiding extra disk reads. -> [!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 the unload process. +## Understanding Content Paths -When loading an asset, the load methods require two parts: +When loading content, you need to specify the path to the asset, minus the extension. This path is relative to the ContentManager's [**RootDirectory**](xref:Microsoft.Xna.Framework.Content.ContentManager.RootDirectory) property, which is set to "Content" by default in the `Game1` constructor. -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. +For example, with our newly added logo in the "images" folder,the path would be "images/logo" (without the file extension). The reason for this relates to the build process. When you build your project, the *MonoGame.Content.Builder.Tasks* NuGet reference [compiles your assets and copies them to the game's output folder](#understanding-the-content-pipeline-workflow). -## Understanding Content Paths +This creates a folder structure in your output directory similar to: -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: +```sh +DungeonSlime/ + └── bin/ + └── Debug/ + └── net8.0/ + ├── DungeonSlime.exe + └── Content/ + └── images/ + └── logo.xnb +``` -1. Compile the assets into an optimized format in the **content project's** output folder (typically *ProjectRoot/Content/bin/Platform/Content*) as an *`.xnb`* file. -2. Copy the compiled assets to your **game's** output folder (typically *ProjectRoot/bin/Debug/net8.0/Content* or *ProjectRoot/bin/Release/net8.0/Content*). +> [!NOTE] +> Notice that the compile asset has an .xnb extension, but when loading the asset in code, you refer to it without any extension. -For example, if your content project contains: +## Loading and Displaying Your First Asset -[!code-sh[](./snippets/content_dir_tree.sh)] +Now that we have the MonoGame logo added as an asset in the content project, we can modify the game to display the logo. In the *DungeonSlime* project open the `Game1.cs` file and perform the following: -then when the tasks first compiles the assets, they will be output to: +1. Add a field to store the logo texture by inserting this line after the class declaration: -[!code-sh[](./snippets/content_build_dir_tree.sh)] + ```cs + // The MonoGame logo texture + private Texture2D _logo; + ``` -Then after compiling them and copying them to the game projects output folder, it will look like the following: +2. In the [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent) method, add this line to load the logo texture: -[!code-sh[](./snippets/project_build_dir_tree.sh)] + ```cs + _logo = Content.Load("images/logo"); + ``` -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"`. +3. Finally, in the [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)) method, add these lines before the `base.Draw(gameTime);` call: -## Loading Our First Asset + ```cs + // Begin the sprite batch to prepare for rendering. + SpriteBatch.Begin(); -Now, we will 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 will 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: + // Draw the logo texture + SpriteBatch.Draw(_logo, Vector2.Zero, Color.White); -| ![Figure 5-7: MonoGame Horizontal Logo](./images/logo.png) | -|:----------------------------------------------------------:| -| **Figure 5-7: MonoGame Horizontal Logo** | + // Always end the sprite batch when finished. + SpriteBatch.End(); + ``` -Now that we have an image file to add, perform the following: + > [!NOTE] + > We will go more into detail about the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) in the next chapter. -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 the 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 the 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. +The complete updated `Game1.cs` file should now look like this -> [!IMPORTANT] -> After changes have been made in the MGCB Editor, ensure that you save the changes. They are not automatically saved, though you will be warned if you close the editor and have not saved changes. You can tell that changes have not been saved by looking at the title bar of the MGCB editor window. If it has an '*' at the end of the window title, this means changes have not been saved +[!code-csharp[](./snippets/game1.cs?highlight=10-11,27,44-51)] -| ![Figure 5-8: The logo image added to the content project in the MGCB Editor](./images/mgcb-logo-added.png) | -|:-----------------------------------------------------------------------------------------------------------:| -| **Figure 5-8: The logo image added to the content project in the MGCB Editor** | +Running the game now will show the MonoGame logo displayed in the upper-left corner of the game window. -With the MonoGame logo image now added to the content project, we can load it in our game and draw it. Return to your code editor and open the `Game1.cs` file and make the following changes: +| ![Figure 5-8: The MonoGame logo drawn to the game window](./images/logo-drawn.png) | +| :--------------------------------------------------------------------------------: | +| **Figure 5-8: The MonoGame logo drawn to the game window** | -[!code-csharp[](./snippets/game1.cs?highlight=10-11,27,45-52)] +## Adding Build-In Asset Types -The key changes made here are: +The MGCB Editor can also create certain built-in asset types. In this section we will explore these types and this functionality. If not already open, [open the MGCB Editor](#opening-the-mgcb-editor) and perform the following: -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). +1. Select the *Content* node. +2. Right-click it and choose *Add* > *New Item...* from the context menu. +3. In the dialog that appears, you will see the available built-in types. - > [!NOTE] - > We will go more into detail about the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) in the next chapter. + | ![Figure 5-9: New file pop-up](./images/new-file-popup.png) | + | :---------------------------------------------------------: | + | **Figure 5-9: New file pop-up** | -Running the game now will show the MonoGame logo displayed in the upper-left corner of the game window. +The available built-in types include: + +- **Effect (.fx)**: A shader file that creates custom visual effects by controlling how graphics are rendered on the GPU. +- **SpriteFont Description (.spritefont)**: A configuration file that defines how text will be displayed in your game, including character set and font properties. +- **Sprite Effect (.fx)**: A shader specifically designed for use with 2D sprites to create special visual effects. +- **Xml Content (.xml)**: A structured data file for storing game information like levels, dialogues, or configuration settings. +- **LocalizedSpriteFont Description (.spritefont)**: A configuration file for creating fonts with support for multiple languages. + +> [!NOTE] +> Each built-in asset type comes with a template that includes the minimum required structure and settings. -| ![Figure 5-9: The MonoGame logo drawn to the game window](./images/logo-drawn.png) | -|:----------------------------------------------------------------------------------:| -| **Figure 5-9: The MonoGame logo drawn to the game window** | +For now, click the "Cancel" button on the new file dialog. We will explore these built-in types further in later chapters when we need them. ## Conclusion In this chapter, you accomplished the following: - You learned about the advantages of loading assets using the **Content Pipeline**. -- You 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) +- You opened the MGCB Editor and explored its interface. +- You created a folder structure to organize your game assets. +- You added an image file asset to the content project. +- You understood the Content Pipeline workflow and how MonoGame automates the process. +- You loaded and displayed your first asset using the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager). -In the next chapter, we will go more into detail on working with textures and the various options available when rendering them. +In the next chapter, we will explore working with textures in more detail and learning about different rendering options. ## Test Your Knowledge 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 deleted file mode 100644 index 7f5e8a09..00000000 --- a/articles/tutorials/building_2d_games/05_content_pipeline/snippets/content_build_dir_tree.sh +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 765ea0be..00000000 --- a/articles/tutorials/building_2d_games/05_content_pipeline/snippets/content_dir_tree.sh +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index 16f5d0ce..00000000 --- a/articles/tutorials/building_2d_games/05_content_pipeline/snippets/contentpaths.sh +++ /dev/null @@ -1,31 +0,0 @@ -#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 index 77aca013..46ed5c3f 100644 --- a/articles/tutorials/building_2d_games/05_content_pipeline/snippets/game1.cs +++ b/articles/tutorials/building_2d_games/05_content_pipeline/snippets/game1.cs @@ -25,7 +25,6 @@ protected override void Initialize() protected override void LoadContent() { _logo = Content.Load("images/logo"); - base.LoadContent(); } protected override void Update(GameTime 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 deleted file mode 100644 index 17ec1545..00000000 --- a/articles/tutorials/building_2d_games/05_content_pipeline/snippets/project_build_dir_tree.sh +++ /dev/null @@ -1,9 +0,0 @@ -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/07_optimizing_texture_rendering/index.md b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/index.md index e2ada40a..0080d480 100644 --- a/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/index.md +++ b/articles/tutorials/building_2d_games/07_optimizing_texture_rendering/index.md @@ -191,7 +191,7 @@ Add this texture atlas to your content project using the MGCB Editor: First, we will explore creating the texture atlas and defining the texture regions directly in code. Replace the contents of `Game1.cs` with the following: -[!code-csharp[](./snippets/game1/textureatlas_usage.cs?highlight=5,11-15,31-47,67-77)] +[!code-csharp[](./snippets/game1/textureatlas_usage.cs?highlight=5,11-15,31-47,65-75)] The key changes in this implementation are: 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 index 0d7064f5..b461b107 100644 --- 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 @@ -45,8 +45,6 @@ protected override void LoadContent() // retrieve the bat region from the atlas. _bat = atlas.GetRegion("bat"); - - base.LoadContent(); } protected override void Update(GameTime gameTime) 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 index 2047912f..6b3850ce 100644 --- 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 @@ -36,8 +36,6 @@ protected override void LoadContent() // retrieve the bat region from the atlas. _bat = atlas.GetRegion("bat"); - - base.LoadContent(); } protected override void Update(GameTime gameTime) 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 index 20d2c58f..272ed59e 100644 --- a/articles/tutorials/building_2d_games/08_the_sprite_class/index.md +++ b/articles/tutorials/building_2d_games/08_the_sprite_class/index.md @@ -75,7 +75,7 @@ We can simplify this process by adding a sprite creation method to the `TextureA Now we can adjust our game now to use the `Sprite` class instead of just the texture regions. Update the contents of `Game1.cs` with the following: -[!code-csharp[](./snippets/game1.cs?highlight=11-15,34-40,63-67)] +[!code-csharp[](./snippets/game1.cs?highlight=11-15,34-40,61-65)] The key changes in this implementation are: 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 index 1b281987..b2dff791 100644 --- 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 @@ -38,8 +38,6 @@ protected override void LoadContent() // Create the bat sprite from the atlas. _bat = atlas.CreateSprite("bat"); _bat.Scale = new Vector2(4.0f, 4.0f); - - base.LoadContent(); } protected override void Update(GameTime gameTime) 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 index c8be06ce..3851c1ef 100644 --- a/articles/tutorials/building_2d_games/09_the_animatedsprite_class/index.md +++ b/articles/tutorials/building_2d_games/09_the_animatedsprite_class/index.md @@ -178,7 +178,7 @@ We can simplify this process by adding an animated sprite creation method to the We can now adjust our game now to use the `AnimatedSprite` class to see our sprites come to life. Update the contents of `Game1.cs` with the following: -[!code-csharp[](./snippets/game1.cs?highlight=11-15,34-40,50-54)] +[!code-csharp[](./snippets/game1.cs?highlight=11-15,34-40,48-52)] Here are the key changes in this implementation: 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 index 9843d28d..a790893a 100644 --- 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 @@ -38,8 +38,6 @@ protected override void LoadContent() // Create the bat animated sprite from the atlas. _bat = atlas.CreateAnimatedSprite("bat-animation"); _bat.Scale = new Vector2(4.0f, 4.0f); - - base.LoadContent(); } protected override void Update(GameTime gameTime) diff --git a/articles/tutorials/building_2d_games/10_handling_input/index.md b/articles/tutorials/building_2d_games/10_handling_input/index.md index 41eacb35..7b19c1b9 100644 --- a/articles/tutorials/building_2d_games/10_handling_input/index.md +++ b/articles/tutorials/building_2d_games/10_handling_input/index.md @@ -1,427 +1,429 @@ ---- -title: "Chapter 10: Handling Input" -description: "Learn how to handle keyboard, mouse, and gamepad input in MonoGame." ---- - -When you play a game, you need ways to control what is happening; using a keyboard or gamepad to control a character or clicking the mouse to navigate a menu, MonoGame helps us handle all these different types of controls through dedicated input classes: - -- [**Keyboard**](xref:Microsoft.Xna.Framework.Input.Keyboard): Detects which keys are being pressed. -- [**Mouse**](xref:Microsoft.Xna.Framework.Input.Mouse): Tracks mouse movement, button clicks, and scroll wheel use. -- [**GamePad**](xref:Microsoft.Xna.Framework.Input.GamePad): Manages controller input like button presses and thumbstick movement. -- [**TouchPanel**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel): Manages touch input on devices with a touch panel such as mobile phones and tablets. - -Each of these input types has a `GetState` method that, when called, checks what is happening with that device at that moment. Think of it like taking a snapshot; when you call `GetState`, MonoGame looks at that exact moment to see which buttons are pressed, where the mouse is, or how the controller is being used. - -In this chapter you will, we will learn how to use each of these dedicated input classes to handle player input. - -## Keyboard Input - -The keyboard is often the primary input device for PC games, used for everything from character movement to menu navigation. MonoGame provides the [**Keyboard**](xref:Microsoft.Xna.Framework.Input.Keyboard) class to handle keyboard input, making it easy to detect which keys are being pressed at any time. Calling [**Keyboard.GetState**](xref:Microsoft.Xna.Framework.Input.Keyboard.GetState) will retrieve the current state of the keyboard as a [**KeyboardState**](xref:Microsoft.Xna.Framework.Input.KeyboardState) struct. - -### KeyboardState Struct - -The [**KeyboardState**](xref:Microsoft.Xna.Framework.Input.KeyboardState) struct contains methods that can be used to determine if a keyboard key is currently down or up: - -| Method | Description | -|-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| -| [**IsKeyDown(Keys)**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys)) | Returns `true` if the specified key is down; otherwise, returns `false`. | -| [**IsKeyUp(Keys)**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyUp(Microsoft.Xna.Framework.Input.Keys)) | Returns `true` if the specified key is up; otherwise, returns `false`. | - -For example, if we wanted to see if the Space key is down, you could use the following: - -[!code-csharp[](./snippets/keyboardstate.cs)] - -> [!TIP] -> Notice we store the keyboard state in a variable instead of calling [**Keyboard.GetState**](xref:Microsoft.Xna.Framework.Input.Keyboard.GetState) multiple times. This is more efficient and ensures consistent input checking within a single frame. - -## Mouse Input - -The mouse is often the secondary input device for PC games, used for various actions from camera movement to interacting with menus and objects. MonoGame provides the [**Mouse**](xref:Microsoft.Xna.Framework.Input.Mouse) class to handle mouse input, making it easy to detect which buttons are pressed, the position of the mouse cursor, and the value of the scroll wheel. Calling [**Mouse.GetState**](xref:Microsoft.Xna.Framework.Input.Mouse.GetState) will retrieve the current state of the mouse as a [**MouseState**](xref:Microsoft.Xna.Framework.Input.MouseState) struct. - -### MouseState Struct - -The [**MouseState**](xref:Microsoft.Xna.Framework.Input.MouseState) struct contains properties that can be used to determine the state of the mouse buttons, the mouse position, and the scroll wheel value: - -| Property | Type | Description | -|----------------------------------------------------------------------------------------|-------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------| -| [**LeftButton**](xref:Microsoft.Xna.Framework.Input.MouseState.LeftButton) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the left mouse button. | -| [**MiddleButton**](xref:Microsoft.Xna.Framework.Input.MouseState.MiddleButton) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the middle mouse button. This is often the button when pressing the scroll wheel down as a button | -| [**Position**](xref:Microsoft.Xna.Framework.Input.MouseState.Position) | [**Point**](xref:Microsoft.Xna.Framework.Point) | Returns the position of the mouse cursor relative to the bounds of the game window. | -| [**RightButton**](xref:Microsoft.Xna.Framework.Input.MouseState.RightButton) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the right mouse button. | -| [**ScrollWheelValue**](xref:Microsoft.Xna.Framework.Input.MouseState.ScrollWheelValue) | `int` | Returns the **cumulative** scroll wheel value since the start of the game | -| [**XButton1**](xref:Microsoft.Xna.Framework.Input.MouseState.XButton1) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the first extended button on the mouse. | -| [**XButton2**](xref:Microsoft.Xna.Framework.Input.MouseState.XButton2) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the second extended button on the mouse. | - -> [!NOTE] -> [**ScrollWheelValue**](xref:Microsoft.Xna.Framework.Input.MouseState.ScrollWheelValue) returns the cumulative value of the scroll wheel since the start of the game, not how much it moved since the last update. To determine how much it moved between one update and the next, you would need to compare it with the previous frame's value. We will discuss comparing previous and current frame values for inputs in the next chapter. - -Unlike keyboard input which uses [**IsKeyDown(Keys)**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys))/[**IsKeyUp(Keys)**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyUp(Microsoft.Xna.Framework.Input.Keys)) methods mouse buttons return a [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState): - -- [**ButtonState.Pressed**](xref:Microsoft.Xna.Framework.Input.ButtonState): The button is being held down. -- [**ButtonState.Released**](xref:Microsoft.Xna.Framework.Input.ButtonState): The button is not being pressed. - -For example, if we wanted to see if the left mouse button is down, you could use the following - -[!code-csharp[](./snippets/mousestate.cs)] - -## Gamepad Input - -Gamepads are often used as a primary input for a game or an alternative for keyboard and mouse controls. MonoGame provides the [**GamePad**](xref:Microsoft.Xna.Framework.Input.GamePad) class to handle gamepad input, making it easy to detect which buttons are pressed and the value of the thumbsticks. Calling [**GamePad.GetState**](xref:Microsoft.Xna.Framework.Input.GamePad.GetState(Microsoft.Xna.Framework.PlayerIndex)) will retrieve the state of the gamepad as a [**GamePadState**](xref:Microsoft.Xna.Framework.Input.GamePadState) struct. Since multiple gamepads can be connected, you will need to supply a [**PlayerIndex**](xref:Microsoft.Xna.Framework.PlayerIndex) value to specify which gamepad state to retrieve. - -### GamePadState Struct - -The [**GamePadState**](xref:Microsoft.Xna.Framework.Input.GamePadState) struct and properties that can be used to get the state of the buttons, dpad, triggers, and thumbsticks: - -| Property | Type | Description | -|--------------------------------------------------------------------------------|---------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [**Buttons**](xref:Microsoft.Xna.Framework.Input.GamePadState.Buttons) | [**GamePadButtons**](xref:Microsoft.Xna.Framework.Input.GamePadButtons) | Returns a struct that identifies which buttons on the controller are pressed. | -| [**DPad**](xref:Microsoft.Xna.Framework.Input.GamePadState.DPad) | [**GamePadDPad**](xref:Microsoft.Xna.Framework.Input.GamePadDPad) | Returns a struct that identifies which directions on the DPad are pressed. | -| [**IsConnected**](xref:Microsoft.Xna.Framework.Input.GamePadState.IsConnected) | `bool` | Returns a value that indicates whether the controller is connected. | -| [**ThumbSticks**](xref:Microsoft.Xna.Framework.Input.GamePadState.ThumbSticks) | [**GamePadThumbSticks**](xref:Microsoft.Xna.Framework.Input.GamePadThumbSticks) | Returns a struct that contains the direction of each thumbstick. Each thumbstick (left and right) are represented as a [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) value between `-1.0f` and `1.0` for the x- and y-axes. | -| [**Triggers**](xref:Microsoft.Xna.Framework.Input.GamePadState.Triggers) | [**GamePadTriggers**](xref:Microsoft.Xna.Framework.Input.GamePadTriggers) | Returns a struct that contains the value of each trigger. Each trigger (left and right) are represented as a `float` value between `0.0f`, meaning not pressed, and `1.0f`, meaning fully pressed. | - -#### Buttons - -The [**GamePadState.Buttons**](xref:Microsoft.Xna.Framework.Input.GamePadState.Buttons) property returns a [**GamePadButtons**](xref:Microsoft.Xna.Framework.Input.GamePadButtons) struct that can be used to identify which buttons on the controller are pressed. This struct contains the following properties: - -| Property | Type | Description | -|--------------------------------------------------------------------------------------|-------------------------------------------------------------------|-----------------------------------------------| -| [**A**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.A) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the A button | -| [**B**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.B) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the B button | -| [**Back**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.Back) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the Back button | -| [**BigButton**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.BigButton) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the BigButton button | -| [**LeftShoulder**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.LeftShoulder) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the LeftShoulder button | -| [**LeftStick**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.LeftStick) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the LeftStick button | -| [**RightShoulder**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.RightShoulder) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the RightShoulder button | -| [**RightStick**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.RightStick) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the RightStick button | -| [**Start**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.Start) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the Start button | -| [**X**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.X) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the X button | -| [**Y**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.Y) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the Y button | - -> [!NOTE] -> Recall from [Chapter 01](../01_what_is_monogame/index.md) that MonoGame is a implementation the XNA API. Since XNA was originally created for making games on Windows PC and Xbox 360, the names of the gamepad buttons match those of an Xbox 360 controller. -> -> The [**BigButton**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.BigButton) refers to the large, centrally located button on special Xbox 360 controllers created for games like "Scene It?" - this button is not present on standard controllers and is not mapped to any button on modern controllers. It remains in the API for backward compatibility with XNA. -> -> | Front | Back | -> | :--------------------------------------------------------- | :------------------------------------------------------- | -> | Xbox | | -> | ![Front Of Controller](./images/xbox-controller-front.svg) | ![Back Of Controller](./images/xbox-controller-back.svg) | -> | Playstation | | -> | ![Front Of Controller](./images/ps-controller-front.svg) | ![Back Of Controller](./images/ps-controller-back.svg) | - -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)] - -You may notice however, that the GamePadState also requires a controller index, as more than one can be connected at the same time. The latest Xbox console for instance can support up to 8 controllers at a time, for this reason you need to specify which controller you are listening for. Additionally, if you want ANY controller to start your game, you will need to loop through all possible controllers each frame until the first one "picks up". - -#### DPad - -The [**DPad**](xref:Microsoft.Xna.Framework.Input.GamePadState.DPad) property returns a [**GamePadDPad**](xref:Microsoft.Xna.Framework.Input.GamePadDPad) struct that can be used to identify which DPad buttons on the controller are pressed. This struct contains the following properties: - -| Property | Type | Description | -|------------------------------------------------------------------|-------------------------------------------------------------------|---------------------------------------------| -| [**Down**](xref:Microsoft.Xna.Framework.Input.GamePadDPad.Down) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the DPad Down button. | -| [**Left**](xref:Microsoft.Xna.Framework.Input.GamePadDPad.Down) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the DPad Left button. | -| [**Right**](xref:Microsoft.Xna.Framework.Input.GamePadDPad.Down) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the DPad Right button. | -| [**Up**](xref:Microsoft.Xna.Framework.Input.GamePadDPad.Down) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the DPad Up Button. | - -Like with the [Buttons](#buttons), these also return a [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) enum value to represent the state of the DPad button. For instance, if you wanted to check if the DPad up button is being pressed, you could do the following: - -[!code-csharp[](./snippets/buttonstate.cs)] - -#### Thumbsticks - -The [**ThumbSticks**](xref:Microsoft.Xna.Framework.Input.GamePadState.ThumbSticks) property returns a [**GamePadThumbSticks**](xref:Microsoft.Xna.Framework.Input.GamePadThumbSticks) struct that can be used to retrieve the values of the left and right thumbsticks. This struct contains the following properties: - -| Property | Type | Description | -|--------------------------------------------------------------------------|-----------------------------------------------------|------------------------------------------------| -| [**Left**](xref:Microsoft.Xna.Framework.Input.GamePadThumbSticks.Left) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | The direction the left thumbstick is pressed. | -| [**Right**](xref:Microsoft.Xna.Framework.Input.GamePadThumbSticks.Right) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | The direction the right thumbstick is pressed. | - -The thumbstick values are represented as a [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) value: - -- X-axis: A value between `-1.0f` (pushed fully to the left) and `1.0f` (pushed fully to the right). -- Y-axis: A value between `-1.0f` (pushed fully downward) and `1.0f` (pushed fully upward). - -For example, if you wanted to move a sprite using the left thumbstick, you could do the following - -[!code-csharp[](./snippets/thumbstick.cs)] - -> [!IMPORTANT] -> Notice that we inverted the y-axis value of the thumbstick by multiplying it by `-1.0f`. This is necessary because the thumbstick y-axis values range from `-1.0f` (down) to `1.0f` (up). The y-axis of the screen coordinates in MonoGame **increases** downward, as we saw in [Chapter 06](../06_working_with_textures/index.md#drawing-a-texture). -> -> This inversion aligns the thumbstick's y-axis value with the screen movement. - -#### Triggers - -The [**Triggers**](xref:Microsoft.Xna.Framework.Input.GamePadState.Triggers) property returns a [**GamePadTriggers**](xref:Microsoft.Xna.Framework.Input.GamePadTriggers) struct that can be used to retrieve the values of the left and right triggers. This struct contains the following properties: - -| Property | Type | Description | -|-----------------------------------------------------------------------|---------|--------------------------------| -| [**Left**](xref:Microsoft.Xna.Framework.Input.GamePadTriggers.Left) | `float` | The value of the left trigger. | -| [**Right**](xref:Microsoft.Xna.Framework.Input.GamePadTriggers.Right) | `float` | The value of the left trigger. | - -The trigger values are represented as a float value between `0.0f` (not pressed) to `1.0f` (fully pressed). The triggers on a gamepad, however, can be either *analog* or *digital* depending the gamepad manufacturer. For gamepads with *digital* triggers, the value will always be either `0.0f` or `1.0f`, as a digital trigger does not register values in between based on the amount of pressure applied to the trigger. - -For example, if we were creating a racing game, the right trigger could be used for acceleration like the following: - -[!code-csharp[](./snippets/triggers.cs)] - -### GamePadState Methods - -The [**GamePadState**](xref:Microsoft.Xna.Framework.Input.GamePadState) struct also contains two methods that can be used to get information about the device's inputs as either being up or down: - -| Method | Description | -|----------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [**IsButtonDown(Buttons)**](xref:Microsoft.Xna.Framework.Input.GamePadState.IsButtonDown(Microsoft.Xna.Framework.Input.Buttons)) | Returns a value that indicates whether the specified button is down. Multiple [**Buttons**](xref:Microsoft.Xna.Framework.Input.Buttons) values can be given using the bitwise OR `|` operator. When multiple buttons are given, the return value indicates if all buttons specified are down, not just one of them. | -| [**IsButtonUp(Buttons)**](xref:Microsoft.Xna.Framework.Input.GamePadState.IsButtonUp(Microsoft.Xna.Framework.Input.Buttons)) | Returns a value that indicates whether the specified button is up. Multiple [**Buttons**](xref:Microsoft.Xna.Framework.Input.Buttons) values can be given using the bitwise OR `|` operator. When multiple buttons are given, the return value indicates if all buttons specified are up, not just one of them. | - -You can use the [**IsButtonDown(Buttons)**](xref:Microsoft.Xna.Framework.Input.GamePadState.IsButtonDown(Microsoft.Xna.Framework.Input.Buttons)) and [**IsButtonUp(Buttons)**](xref:Microsoft.Xna.Framework.Input.GamePadState.IsButtonUp(Microsoft.Xna.Framework.Input.Buttons)) methods to get the state of all buttons, including the DPad. The following is a complete list of all of the [**Buttons**](xref:Microsoft.Xna.Framework.Input.Buttons) enum values: - -- [**Buttons.A**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.B**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.Back**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.BigButton**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.DPadDown**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.DPadLeft**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.DPadRight**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.DPadUp**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.LeftShoulder**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.LeftStick**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.LeftThumbstickDown**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.LeftThumbstickLeft**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.LeftThumbstickRight**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.LeftThumbstickUp**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.LeftTrigger**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.None**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.RightShoulder**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.RightStick**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.RightStickDown**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.RightStickLeft**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.RightStickRight**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.RightStickUp**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.RightTrigger**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.Start**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.X**](xref:Microsoft.Xna.Framework.Input.Buttons) -- [**Buttons.Y**](xref:Microsoft.Xna.Framework.Input.Buttons) - -> [!CAUTION] -> While you can use these methods to get the state of any of these button inputs, the state will only tell you if it is being pressed or released. For the actual thumbstick values and trigger values, you would need to use the properties instead. - -For example, if we wanted to check if the A button on the the first gamepad is pressed, you could use the following: - -[!code-csharp[](./snippets/isbuttondown.cs)] - -### GamePad Vibration - -Another capability of gamepads is haptic feedback through vibration motors. MonoGame allows you to control this feature using the [**GamePad.SetVibration**](xref:Microsoft.Xna.Framework.Input.GamePad.SetVibration(Microsoft.Xna.Framework.PlayerIndex,System.Single,System.Single)) method. This method takes three parameters: - -1. The [**PlayerIndex**](xref:Microsoft.Xna.Framework.PlayerIndex) of the gamepad to vibrate. -2. The intensity of the left motor (from `0.0f` for no vibration to `1.0f` for maximum vibration). -3. The intensity of the right motor (using the same scale). - -Most modern gamepads have two vibration motors, a larger one (usually the left motor) for low-frequency rumble and a smaller one (usually the right motor) for high-frequency feedback. By controlling these independently, you can create various haptic effects: - -[!code-csharp[](./snippets/vibration.cs)] - -## TouchPanel Input - -For mobile devices such as Android/iOS phones and tablets, the primary input device is the touch panel screen. Touching a location on the screen is similar to clicking a location on your computer with a mouse. MonoGame provides the [**TouchPanel**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel) class to handle touch input. - -The [**TouchPanel**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel) class offers two ways of retrieving information about touch input: - -- [**TouchPanel.GetState**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.GetState) retrieves a [**TouchCollection**](xref:Microsoft.Xna.Framework.Input.Touch.TouchCollection) struct that contains [**TouchLocation**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation) values for each point of touch on the touch panel. -- [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture) retrieves a [**GestureSample**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample) struct that contains information about recent gestures that have been performed like a vertical or horizontal drag across the screen. - -### TouchCollection - -When calling [**TouchPanel.GetState**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.GetState) a [**TouchCollection**](xref:Microsoft.Xna.Framework.Input.Touch.TouchCollection) struct is returned. This collection contains a [**TouchLocation**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation) value for each point of touch. - -#### TouchLocation - -Each [**TouchLocation**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation) value in a touch collection contains the following properties: - -| Property | Type | Description | -|---------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| -| [**Id**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.Id) | `int` | The id of the touch location. | -| [**Position**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.Position) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | The position of the touch location. | -| [**Pressure**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.Pressure) | `float` | The amount of pressure applied at the touch location. **(Only available for Android devices.)** | -| [**State**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) | [**TouchLocationState**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState) | The current state of the touch location. | - -The important properties of the location are the [**Position**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.Position) and the [**State**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) The position property will tell us the location of the touch event, and the state can be one of the following values: - -| State | Description | -|------------------------------------------------------------------------------|---------------------------------------------------------------------------| -| [**Invalid**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) | This touch location position is invalid. | -| [**Moved**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) | This touch location position was updated or pressed at the same position. | -| [**Pressed**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) | This touch location was pressed. | -| [**Released**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) | This touch location was released. | - -When the state is moved or pressed, then we know that location on the touch panel is being touched. So we can capture it and use it like the following: - -[!code-csharp[](./snippets/touchstate.cs)] - -> [!NOTE] -> Unlike mouse input which only tracks a single point, [**TouchPanel**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel) supports multiple simultaneous touch points. The [**TouchCollection**](xref:Microsoft.Xna.Framework.Input.Touch.TouchCollection) contains all active touch points, which is why we loop through them in the sample above. - -The state of a touch location progresses through the states typically in order of: - -- [**Pressed**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State): Initial contact with the screen. -- [**Moved**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) : Touch point moved while maintaining contact. -- [**Released**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State): Contact with screen ended. -- [**Invalid**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) : Touch data is invalid (using when tracking data is lost). - -### GestureSample - -When calling [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture) a [**GestureSample**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample) struct containing the information about recent gestures that have been performed is returned. The [**GestureSample**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample) struct contains the following properties: - -| Property | Type | Description | -|---------------------------------------------------------------------------------------|-------------------------------------------------------------------------|--------------------------------------------------------------------------------| -| [**Delta**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.Delta) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | Gets the delta information about the first touch-point in the gesture sample. | -| [**Delta2**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.Delta2) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | Gets the delta information about the second touch-point in the gesture sample. | -| [**GestureType**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.GestureType) | [**GestureType**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | Gets the type of the gesture. | -| [**Position**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.Position) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | Gets the position of the first touch-point in the gesture sample. | -| [**Position2**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.Position2) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | Gets the position of the second touch-point in the gesture sample. | - -> [!NOTE] -> Gestures have two delta properties and two position properties. This is because some gestures require multiple touch inputs to perform, such as performing a pinch to zoom in or out. You would need the location of both touch points to determine the correct zoom to apply during the gesture. - -To determine what type of gesture is performed, we can get that from the [**GestureType**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.GestureType) property which will be one of the following values: - -| Gesture Type | Description | -|----------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| -| [**DoubleTap**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user double tapped the device twice which is always preceded by a Tap gesture. | -| [**DragComplete**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | States completion of a drag gesture (VerticalDrag, HorizontalDrag, or FreeDrag). | -| [**Flick**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | States that a touch was combined with a quick swipe. | -| [**FreeDrag**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user touched a point and the performed a free-form drag. | -| [**Hold**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user touched a single point for approximately one second. | -| [**HorizontalDrag**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user touched the screen and performed either a left-to-right or right-to-left drag gesture. | -| [**None**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | No gesture. | -| [**Pinch**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user converged or diverged two touch-points on the screen which is like a two-finger drag. | -| [**PinchComplete**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | An in-progress pinch gesture was completed. | -| [**Tap**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user touched a single point. | -| [**VerticalDrag**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user touched the screen and performed either a top-to-bottom or bottom-to-top drag gesture. | - -> [!IMPORTANT] -> Before gestures can be detected, they have to be enabled using [**TouchPanel.EnabledGestures**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.EnabledGestures). This can be done in [**Game.Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize) like the following: -> -> [!code-csharp[](./snippets/enablegestures.cs)] - -The following is an example of using a gesture to detect horizontal and vertical drags: - -[!code-csharp[](./snippets/gestures.cs)] - -> [!IMPORTANT] -> Notice above that we use a `while` loop with [**TouchPanel.IsGestureAvailable**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.IsGestureAvailable) as the condition for the loop. The reason we do this is because when a user performs a gesture, such as a horizontal drag across the screen, very quickly, what can often occurs is a series of multiple small drag gestures are registered and queued. -> -> Each time [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture) is called, it will dequeue the next gesture. So to ensure that we handle the complete gesture, we loop the gesture queue until there are none left. - -## Implementing Input in Our Game - -For our game, we are going to implement keyboard and gamepad controls based on the following criteria: - -| Keyboard Input | Gamepad Input | Description | -|---------------------------|---------------------------------------------|--------------------------------------| -| [Keys.W] and [Keys.Up] | [Thumbstick.Left.Y] and [Buttons.DPadUp] | Moves the slime up the screen. | -| [Keys.S] and [Keys.Down] | [Thumbstick.Left.Y] and [Buttons.DPadDown] | Moves the slime down the screen | -| [Keys.A] and [Keys.Left] | [Thumbstick.Left.X] and [Buttons.DPadLeft] | Moves the slime left on the screen. | -| [Keys.D] and [Keys.Right] | [Thumbstick.Left.X] and [Buttons.DPadRight] | Moves the slime right on the screen. | -| [Keys.Space] | [Buttons.A] | Increased the speed of the slime. | - -Open `Game1.cs` and update it with the following: - -[!code-csharp[](./snippets/game1.cs?highlight=17-21,62-66,71-159,170)] - -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](./videos/input-moving-slime.webm) | -|:-----------------------------------------------------------------------------------------------:| -| **Figure 10-1: The slime moving around based on device input** | - -You may notice that the slime is capable of moving completely off the screen, this is completely normal as we have not yet implemented any logic to prevent it from doing so, it only doing what we currently tell it to do. - -## Conclusion - -In this chapter, you accomplished the following: - -- Handle keyboard input to detect key presses. -- Handle mouse input including button clicks and cursor position. -- Work with gamepad controls including buttons, thumbsticks, and vibration. -- Understand touch input for mobile devices including touch points and gestures. -- Implement movement controls using different input methods. -- Consider controller-specific details like coordinate systems and analog vs digital input. - -In the next chapter, we will learn how to track previous input states to handle single-press events and implement an input management system to simplify some of the complexity of handling input. - -## Test Your Knowledge - -1. Why do we store the result of `GetState` in a variable instead of calling it multiple times? - - :::question-answer - Storing the state in a variable is more efficient and ensures consistent input checking within a frame. Each `GetState` call polls the device, which can impact performance if called repeatedly. - ::: - -2. What is the main difference between how keyboard and mouse/gamepad button states are checked? - - :::question-answer - Keyboard input uses [**IsKeyUp**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyUp(Microsoft.Xna.Framework.Input.Keys))/[**IsKeyDown**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys)) methods, while mouse and gamepad buttons return a [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) enum value (Pressed or Released). - ::: - -3. When using thumbstick values for movement, why do we multiply the Y value by -1? - - :::question-answer - The thumbstick Y-axis values (-1.0f down to 1.0f up) are inverted compared to MonoGame's screen coordinate system (Y increases downward). Multiplying by -1 aligns the thumbstick direction with screen movement. - ::: - -4. What is the difference between analog and digital trigger input on a gamepad? - - :::question-answer - Analog triggers provide values between 0.0f and 1.0f based on how far they are pressed, while digital triggers only report 0.0f (not pressed) or 1.0f (pressed). This affects how you handle trigger input in your game. - ::: - -5. What is the key difference between [**TouchPanel.GetState**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.GetState) and [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture)? - - :::question-answer - [**TouchPanel.GetState**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.GetState) returns information about current touch points on the screen, while [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture) provides information about specific gesture patterns like taps, drags, and pinches that have been performed. - ::: - -6. Why do we use a while loop with [**TouchPanel.IsGestureAvailable**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.IsGestureAvailable) when reading gestures? - - :::question-answer - Quick gestures can generate multiple gesture events that are queued. Using a while loop with [**TouchPanel.IsGestureAvailable**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.IsGestureAvailable) ensures we process all queued gestures, as [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture) only returns one gesture at a time. - ::: - -7. How does touch input differ from mouse input in terms of handling multiple input points? - - :::question-answer - Touch input can handle multiple simultaneous touch points through the [**TouchCollection**](xref:Microsoft.Xna.Framework.Input.Touch.TouchCollection), while mouse input only tracks a single cursor position. This allows touch input to support features like multi-touch gestures that are not possible with a mouse. - ::: - -8. What are the different states a [**TouchLocation**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation) can have and what do they indicate? - - :::question-answer - A [**TouchLocation**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation) can have four states: - - - [**Pressed**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState): Initial contact with the screen - - [**Moved**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState): Touch point moved while maintaining contact - - [**Released**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState): Contact with the screen ended - - [**Invalid**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState): Touch data is not valid or tracking was lost - - ::: +--- +title: "Chapter 10: Handling Input" +description: "Learn how to handle keyboard, mouse, and gamepad input in MonoGame." +--- + +When you play a game, you need ways to control what is happening; using a keyboard or gamepad to control a character or clicking the mouse to navigate a menu, MonoGame helps us handle all these different types of controls through dedicated input classes: + +- [**Keyboard**](xref:Microsoft.Xna.Framework.Input.Keyboard): Detects which keys are being pressed. +- [**Mouse**](xref:Microsoft.Xna.Framework.Input.Mouse): Tracks mouse movement, button clicks, and scroll wheel use. +- [**GamePad**](xref:Microsoft.Xna.Framework.Input.GamePad): Manages controller input like button presses and thumbstick movement. +- [**TouchPanel**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel): Manages touch input on devices with a touch panel such as mobile phones and tablets. + +Each of these input types has a `GetState` method that, when called, checks what is happening with that device at that moment. Think of it like taking a snapshot; when you call `GetState`, MonoGame looks at that exact moment to see which buttons are pressed, where the mouse is, or how the controller is being used. + +In this chapter you will, we will learn how to use each of these dedicated input classes to handle player input. + +## Keyboard Input + +The keyboard is often the primary input device for PC games, used for everything from character movement to menu navigation. MonoGame provides the [**Keyboard**](xref:Microsoft.Xna.Framework.Input.Keyboard) class to handle keyboard input, making it easy to detect which keys are being pressed at any time. Calling [**Keyboard.GetState**](xref:Microsoft.Xna.Framework.Input.Keyboard.GetState) will retrieve the current state of the keyboard as a [**KeyboardState**](xref:Microsoft.Xna.Framework.Input.KeyboardState) struct. + +### KeyboardState Struct + +The [**KeyboardState**](xref:Microsoft.Xna.Framework.Input.KeyboardState) struct contains methods that can be used to determine if a keyboard key is currently down or up: + +| Method | Description | +|-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| +| [**IsKeyDown(Keys)**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys)) | Returns `true` if the specified key is down; otherwise, returns `false`. | +| [**IsKeyUp(Keys)**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyUp(Microsoft.Xna.Framework.Input.Keys)) | Returns `true` if the specified key is up; otherwise, returns `false`. | + +For example, if we wanted to see if the Space key is down, you could use the following: + +[!code-csharp[](./snippets/keyboardstate.cs)] + +> [!TIP] +> Notice we store the keyboard state in a variable instead of calling [**Keyboard.GetState**](xref:Microsoft.Xna.Framework.Input.Keyboard.GetState) multiple times. This is more efficient and ensures consistent input checking within a single frame. + +## Mouse Input + +The mouse is often the secondary input device for PC games, used for various actions from camera movement to interacting with menus and objects. MonoGame provides the [**Mouse**](xref:Microsoft.Xna.Framework.Input.Mouse) class to handle mouse input, making it easy to detect which buttons are pressed, the position of the mouse cursor, and the value of the scroll wheel. Calling [**Mouse.GetState**](xref:Microsoft.Xna.Framework.Input.Mouse.GetState) will retrieve the current state of the mouse as a [**MouseState**](xref:Microsoft.Xna.Framework.Input.MouseState) struct. + +### MouseState Struct + +The [**MouseState**](xref:Microsoft.Xna.Framework.Input.MouseState) struct contains properties that can be used to determine the state of the mouse buttons, the mouse position, and the scroll wheel value: + +| Property | Type | Description | +|----------------------------------------------------------------------------------------|-------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------| +| [**LeftButton**](xref:Microsoft.Xna.Framework.Input.MouseState.LeftButton) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the left mouse button. | +| [**MiddleButton**](xref:Microsoft.Xna.Framework.Input.MouseState.MiddleButton) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the middle mouse button. This is often the button when pressing the scroll wheel down as a button | +| [**Position**](xref:Microsoft.Xna.Framework.Input.MouseState.Position) | [**Point**](xref:Microsoft.Xna.Framework.Point) | Returns the position of the mouse cursor relative to the bounds of the game window. | +| [**RightButton**](xref:Microsoft.Xna.Framework.Input.MouseState.RightButton) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the right mouse button. | +| [**ScrollWheelValue**](xref:Microsoft.Xna.Framework.Input.MouseState.ScrollWheelValue) | `int` | Returns the **cumulative** scroll wheel value since the start of the game | +| [**XButton1**](xref:Microsoft.Xna.Framework.Input.MouseState.XButton1) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the first extended button on the mouse. | +| [**XButton2**](xref:Microsoft.Xna.Framework.Input.MouseState.XButton2) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the second extended button on the mouse. | + +> [!NOTE] +> [**ScrollWheelValue**](xref:Microsoft.Xna.Framework.Input.MouseState.ScrollWheelValue) returns the cumulative value of the scroll wheel since the start of the game, not how much it moved since the last update. To determine how much it moved between one update and the next, you would need to compare it with the previous frame's value. We will discuss comparing previous and current frame values for inputs in the next chapter. + +Unlike keyboard input which uses [**IsKeyDown(Keys)**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys))/[**IsKeyUp(Keys)**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyUp(Microsoft.Xna.Framework.Input.Keys)) methods mouse buttons return a [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState): + +- [**ButtonState.Pressed**](xref:Microsoft.Xna.Framework.Input.ButtonState): The button is being held down. +- [**ButtonState.Released**](xref:Microsoft.Xna.Framework.Input.ButtonState): The button is not being pressed. + +For example, if we wanted to see if the left mouse button is down, you could use the following + +[!code-csharp[](./snippets/mousestate.cs)] + +## Gamepad Input + +Gamepads are often used as a primary input for a game or an alternative for keyboard and mouse controls. MonoGame provides the [**GamePad**](xref:Microsoft.Xna.Framework.Input.GamePad) class to handle gamepad input, making it easy to detect which buttons are pressed and the value of the thumbsticks. Calling [**GamePad.GetState**](xref:Microsoft.Xna.Framework.Input.GamePad.GetState(Microsoft.Xna.Framework.PlayerIndex)) will retrieve the state of the gamepad as a [**GamePadState**](xref:Microsoft.Xna.Framework.Input.GamePadState) struct. Since multiple gamepads can be connected, you will need to supply a [**PlayerIndex**](xref:Microsoft.Xna.Framework.PlayerIndex) value to specify which gamepad state to retrieve. + +### GamePadState Struct + +The [**GamePadState**](xref:Microsoft.Xna.Framework.Input.GamePadState) struct and properties that can be used to get the state of the buttons, dpad, triggers, and thumbsticks: + +| Property | Type | Description | +|--------------------------------------------------------------------------------|---------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [**Buttons**](xref:Microsoft.Xna.Framework.Input.GamePadState.Buttons) | [**GamePadButtons**](xref:Microsoft.Xna.Framework.Input.GamePadButtons) | Returns a struct that identifies which buttons on the controller are pressed. | +| [**DPad**](xref:Microsoft.Xna.Framework.Input.GamePadState.DPad) | [**GamePadDPad**](xref:Microsoft.Xna.Framework.Input.GamePadDPad) | Returns a struct that identifies which directions on the DPad are pressed. | +| [**IsConnected**](xref:Microsoft.Xna.Framework.Input.GamePadState.IsConnected) | `bool` | Returns a value that indicates whether the controller is connected. | +| [**ThumbSticks**](xref:Microsoft.Xna.Framework.Input.GamePadState.ThumbSticks) | [**GamePadThumbSticks**](xref:Microsoft.Xna.Framework.Input.GamePadThumbSticks) | Returns a struct that contains the direction of each thumbstick. Each thumbstick (left and right) are represented as a [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) value between `-1.0f` and `1.0` for the x- and y-axes. | +| [**Triggers**](xref:Microsoft.Xna.Framework.Input.GamePadState.Triggers) | [**GamePadTriggers**](xref:Microsoft.Xna.Framework.Input.GamePadTriggers) | Returns a struct that contains the value of each trigger. Each trigger (left and right) are represented as a `float` value between `0.0f`, meaning not pressed, and `1.0f`, meaning fully pressed. | + +#### Buttons + +The [**GamePadState.Buttons**](xref:Microsoft.Xna.Framework.Input.GamePadState.Buttons) property returns a [**GamePadButtons**](xref:Microsoft.Xna.Framework.Input.GamePadButtons) struct that can be used to identify which buttons on the controller are pressed. This struct contains the following properties: + +| Property | Type | Description | +|--------------------------------------------------------------------------------------|-------------------------------------------------------------------|-----------------------------------------------| +| [**A**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.A) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the A button | +| [**B**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.B) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the B button | +| [**Back**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.Back) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the Back button | +| [**BigButton**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.BigButton) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the BigButton button | +| [**LeftShoulder**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.LeftShoulder) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the LeftShoulder button | +| [**LeftStick**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.LeftStick) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the LeftStick button | +| [**RightShoulder**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.RightShoulder) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the RightShoulder button | +| [**RightStick**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.RightStick) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the RightStick button | +| [**Start**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.Start) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the Start button | +| [**X**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.X) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the X button | +| [**Y**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.Y) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the Y button | + +> [!NOTE] +> Recall from [Chapter 01](../01_what_is_monogame/index.md) that MonoGame is a implementation the XNA API. Since XNA was originally created for making games on Windows PC and Xbox 360, the names of the gamepad buttons match those of an Xbox 360 controller. +> +> The [**BigButton**](xref:Microsoft.Xna.Framework.Input.GamePadButtons.BigButton) refers to the large, centrally located button on special Xbox 360 controllers created for games like "Scene It?" - this button is not present on standard controllers and is not mapped to any button on modern controllers. It remains in the API for backward compatibility with XNA. +> +> | Front | Back | +> | :--------------------------------------------------------- | :------------------------------------------------------- | +> | Xbox | | +> | ![Front Of Controller](./images/xbox-controller-front.svg) | ![Back Of Controller](./images/xbox-controller-back.svg) | +> | Playstation | | +> | ![Front Of Controller](./images/ps-controller-front.svg) | ![Back Of Controller](./images/ps-controller-back.svg) | + +Like with the [mouse input](#mousestate-struct), each of these buttons are represented by a [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) enum value. For instance, if you wanted to check if the A button is being pressed you could do the following: + +[!code-csharp[](./snippets/gamepadstate.cs)] + +> [!NOTE] +> You may notice however, that the GamePadState also requires a controller index, as more than one can be connected at the same time. The latest Xbox console for instance can support up to 8 controllers at a time, for this reason you need to specify which controller you are listening for. Additionally, if you want ANY controller to start your game, you will need to loop through all possible controllers each frame until the first one "picks up". + +#### DPad + +The [**DPad**](xref:Microsoft.Xna.Framework.Input.GamePadState.DPad) property returns a [**GamePadDPad**](xref:Microsoft.Xna.Framework.Input.GamePadDPad) struct that can be used to identify which DPad buttons on the controller are pressed. This struct contains the following properties: + +| Property | Type | Description | +|------------------------------------------------------------------|-------------------------------------------------------------------|---------------------------------------------| +| [**Down**](xref:Microsoft.Xna.Framework.Input.GamePadDPad.Down) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the DPad Down button. | +| [**Left**](xref:Microsoft.Xna.Framework.Input.GamePadDPad.Down) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the DPad Left button. | +| [**Right**](xref:Microsoft.Xna.Framework.Input.GamePadDPad.Down) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the DPad Right button. | +| [**Up**](xref:Microsoft.Xna.Framework.Input.GamePadDPad.Down) | [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) | Returns the state of the DPad Up Button. | + +Like with the [Buttons](#buttons), these also return a [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) enum value to represent the state of the DPad button. For instance, if you wanted to check if the DPad up button is being pressed, you could do the following: + +[!code-csharp[](./snippets/buttonstate.cs)] + +#### Thumbsticks + +The [**ThumbSticks**](xref:Microsoft.Xna.Framework.Input.GamePadState.ThumbSticks) property returns a [**GamePadThumbSticks**](xref:Microsoft.Xna.Framework.Input.GamePadThumbSticks) struct that can be used to retrieve the values of the left and right thumbsticks. This struct contains the following properties: + +| Property | Type | Description | +|--------------------------------------------------------------------------|-----------------------------------------------------|------------------------------------------------| +| [**Left**](xref:Microsoft.Xna.Framework.Input.GamePadThumbSticks.Left) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | The direction the left thumbstick is pressed. | +| [**Right**](xref:Microsoft.Xna.Framework.Input.GamePadThumbSticks.Right) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | The direction the right thumbstick is pressed. | + +The thumbstick values are represented as a [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) value: + +- X-axis: A value between `-1.0f` (pushed fully to the left) and `1.0f` (pushed fully to the right). +- Y-axis: A value between `-1.0f` (pushed fully downward) and `1.0f` (pushed fully upward). + +For example, if you wanted to move a sprite using the left thumbstick, you could do the following + +[!code-csharp[](./snippets/thumbstick.cs)] + +> [!IMPORTANT] +> Notice that we inverted the y-axis value of the thumbstick by multiplying it by `-1.0f`. This is necessary because the thumbstick y-axis values range from `-1.0f` (down) to `1.0f` (up). The y-axis of the screen coordinates in MonoGame **increases** downward, as we saw in [Chapter 06](../06_working_with_textures/index.md#drawing-a-texture). +> +> This inversion aligns the thumbstick's y-axis value with the screen movement. + +#### Triggers + +The [**Triggers**](xref:Microsoft.Xna.Framework.Input.GamePadState.Triggers) property returns a [**GamePadTriggers**](xref:Microsoft.Xna.Framework.Input.GamePadTriggers) struct that can be used to retrieve the values of the left and right triggers. This struct contains the following properties: + +| Property | Type | Description | +|-----------------------------------------------------------------------|---------|--------------------------------| +| [**Left**](xref:Microsoft.Xna.Framework.Input.GamePadTriggers.Left) | `float` | The value of the left trigger. | +| [**Right**](xref:Microsoft.Xna.Framework.Input.GamePadTriggers.Right) | `float` | The value of the left trigger. | + +The trigger values are represented as a float value between `0.0f` (not pressed) to `1.0f` (fully pressed). The triggers on a gamepad, however, can be either *analog* or *digital* depending the gamepad manufacturer. For gamepads with *digital* triggers, the value will always be either `0.0f` or `1.0f`, as a digital trigger does not register values in between based on the amount of pressure applied to the trigger. + +For example, if we were creating a racing game, the right trigger could be used for acceleration like the following: + +[!code-csharp[](./snippets/triggers.cs)] + +### GamePadState Methods + +The [**GamePadState**](xref:Microsoft.Xna.Framework.Input.GamePadState) struct also contains two methods that can be used to get information about the device's inputs as either being up or down: + +| Method | Description | +|----------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [**IsButtonDown(Buttons)**](xref:Microsoft.Xna.Framework.Input.GamePadState.IsButtonDown(Microsoft.Xna.Framework.Input.Buttons)) | Returns a value that indicates whether the specified button is down. Multiple [**Buttons**](xref:Microsoft.Xna.Framework.Input.Buttons) values can be given using the bitwise OR `|` operator. When multiple buttons are given, the return value indicates if all buttons specified are down, not just one of them. | +| [**IsButtonUp(Buttons)**](xref:Microsoft.Xna.Framework.Input.GamePadState.IsButtonUp(Microsoft.Xna.Framework.Input.Buttons)) | Returns a value that indicates whether the specified button is up. Multiple [**Buttons**](xref:Microsoft.Xna.Framework.Input.Buttons) values can be given using the bitwise OR `|` operator. When multiple buttons are given, the return value indicates if all buttons specified are up, not just one of them. | + +You can use the [**IsButtonDown(Buttons)**](xref:Microsoft.Xna.Framework.Input.GamePadState.IsButtonDown(Microsoft.Xna.Framework.Input.Buttons)) and [**IsButtonUp(Buttons)**](xref:Microsoft.Xna.Framework.Input.GamePadState.IsButtonUp(Microsoft.Xna.Framework.Input.Buttons)) methods to get the state of all buttons, including the DPad. The following is a complete list of all of the [**Buttons**](xref:Microsoft.Xna.Framework.Input.Buttons) enum values: + +- [**Buttons.A**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.B**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.Back**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.BigButton**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.DPadDown**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.DPadLeft**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.DPadRight**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.DPadUp**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.LeftShoulder**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.LeftStick**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.LeftThumbstickDown**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.LeftThumbstickLeft**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.LeftThumbstickRight**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.LeftThumbstickUp**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.LeftTrigger**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.None**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.RightShoulder**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.RightStick**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.RightStickDown**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.RightStickLeft**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.RightStickRight**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.RightStickUp**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.RightTrigger**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.Start**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.X**](xref:Microsoft.Xna.Framework.Input.Buttons) +- [**Buttons.Y**](xref:Microsoft.Xna.Framework.Input.Buttons) + +> [!CAUTION] +> While you can use these methods to get the state of any of these button inputs, the state will only tell you if it is being pressed or released. For the actual thumbstick values and trigger values, you would need to use the properties instead. + +For example, if we wanted to check if the A button on the the first gamepad is pressed, you could use the following: + +[!code-csharp[](./snippets/isbuttondown.cs)] + +### GamePad Vibration + +Another capability of gamepads is haptic feedback through vibration motors. MonoGame allows you to control this feature using the [**GamePad.SetVibration**](xref:Microsoft.Xna.Framework.Input.GamePad.SetVibration(Microsoft.Xna.Framework.PlayerIndex,System.Single,System.Single)) method. This method takes three parameters: + +1. The [**PlayerIndex**](xref:Microsoft.Xna.Framework.PlayerIndex) of the gamepad to vibrate. +2. The intensity of the left motor (from `0.0f` for no vibration to `1.0f` for maximum vibration). +3. The intensity of the right motor (using the same scale). + +Most modern gamepads have two vibration motors, a larger one (usually the left motor) for low-frequency rumble and a smaller one (usually the right motor) for high-frequency feedback. By controlling these independently, you can create various haptic effects: + +[!code-csharp[](./snippets/vibration.cs)] + +## TouchPanel Input + +For mobile devices such as Android/iOS phones and tablets, the primary input device is the touch panel screen. Touching a location on the screen is similar to clicking a location on your computer with a mouse. MonoGame provides the [**TouchPanel**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel) class to handle touch input. + +The [**TouchPanel**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel) class offers two ways of retrieving information about touch input: + +- [**TouchPanel.GetState**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.GetState) retrieves a [**TouchCollection**](xref:Microsoft.Xna.Framework.Input.Touch.TouchCollection) struct that contains [**TouchLocation**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation) values for each point of touch on the touch panel. +- [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture) retrieves a [**GestureSample**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample) struct that contains information about recent gestures that have been performed like a vertical or horizontal drag across the screen. + +### TouchCollection + +When calling [**TouchPanel.GetState**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.GetState) a [**TouchCollection**](xref:Microsoft.Xna.Framework.Input.Touch.TouchCollection) struct is returned. This collection contains a [**TouchLocation**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation) value for each point of touch. + +#### TouchLocation + +Each [**TouchLocation**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation) value in a touch collection contains the following properties: + +| Property | Type | Description | +|---------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| +| [**Id**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.Id) | `int` | The id of the touch location. | +| [**Position**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.Position) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | The position of the touch location. | +| [**Pressure**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.Pressure) | `float` | The amount of pressure applied at the touch location. **(Only available for Android devices.)** | +| [**State**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) | [**TouchLocationState**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState) | The current state of the touch location. | + +The important properties of the location are the [**Position**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.Position) and the [**State**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) The position property will tell us the location of the touch event, and the state can be one of the following values: + +| State | Description | +|------------------------------------------------------------------------------|---------------------------------------------------------------------------| +| [**Invalid**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) | This touch location position is invalid. | +| [**Moved**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) | This touch location position was updated or pressed at the same position. | +| [**Pressed**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) | This touch location was pressed. | +| [**Released**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) | This touch location was released. | + +When the state is moved or pressed, then we know that location on the touch panel is being touched. So we can capture it and use it like the following: + +[!code-csharp[](./snippets/touchstate.cs)] + +> [!NOTE] +> Unlike mouse input which only tracks a single point, [**TouchPanel**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel) supports multiple simultaneous touch points. The [**TouchCollection**](xref:Microsoft.Xna.Framework.Input.Touch.TouchCollection) contains all active touch points, which is why we loop through them in the sample above. + +The state of a touch location progresses through the states typically in order of: + +- [**Pressed**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State): Initial contact with the screen. +- [**Moved**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) : Touch point moved while maintaining contact. +- [**Released**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State): Contact with screen ended. +- [**Invalid**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation.State) : Touch data is invalid (using when tracking data is lost). + +### GestureSample + +When calling [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture) a [**GestureSample**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample) struct containing the information about recent gestures that have been performed is returned. The [**GestureSample**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample) struct contains the following properties: + +| Property | Type | Description | +|---------------------------------------------------------------------------------------|-------------------------------------------------------------------------|--------------------------------------------------------------------------------| +| [**Delta**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.Delta) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | Gets the delta information about the first touch-point in the gesture sample. | +| [**Delta2**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.Delta2) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | Gets the delta information about the second touch-point in the gesture sample. | +| [**GestureType**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.GestureType) | [**GestureType**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | Gets the type of the gesture. | +| [**Position**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.Position) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | Gets the position of the first touch-point in the gesture sample. | +| [**Position2**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.Position2) | [**Vector2**](xref:Microsoft.Xna.Framework.Vector2) | Gets the position of the second touch-point in the gesture sample. | + +> [!NOTE] +> Gestures have two delta properties and two position properties. This is because some gestures require multiple touch inputs to perform, such as performing a pinch to zoom in or out. You would need the location of both touch points to determine the correct zoom to apply during the gesture. + +To determine what type of gesture is performed, we can get that from the [**GestureType**](xref:Microsoft.Xna.Framework.Input.Touch.GestureSample.GestureType) property which will be one of the following values: + +| Gesture Type | Description | +|----------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| +| [**DoubleTap**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user double tapped the device twice which is always preceded by a Tap gesture. | +| [**DragComplete**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | States completion of a drag gesture (VerticalDrag, HorizontalDrag, or FreeDrag). | +| [**Flick**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | States that a touch was combined with a quick swipe. | +| [**FreeDrag**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user touched a point and the performed a free-form drag. | +| [**Hold**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user touched a single point for approximately one second. | +| [**HorizontalDrag**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user touched the screen and performed either a left-to-right or right-to-left drag gesture. | +| [**None**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | No gesture. | +| [**Pinch**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user converged or diverged two touch-points on the screen which is like a two-finger drag. | +| [**PinchComplete**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | An in-progress pinch gesture was completed. | +| [**Tap**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user touched a single point. | +| [**VerticalDrag**](xref:Microsoft.Xna.Framework.Input.Touch.GestureType) | The user touched the screen and performed either a top-to-bottom or bottom-to-top drag gesture. | + +> [!IMPORTANT] +> Before gestures can be detected, they have to be enabled using [**TouchPanel.EnabledGestures**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.EnabledGestures). This can be done in [**Game.Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize) like the following: +> +> [!code-csharp[](./snippets/enablegestures.cs)] + +The following is an example of using a gesture to detect horizontal and vertical drags: + +[!code-csharp[](./snippets/gestures.cs)] + +> [!IMPORTANT] +> Notice above that we use a `while` loop with [**TouchPanel.IsGestureAvailable**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.IsGestureAvailable) as the condition for the loop. The reason we do this is because when a user performs a gesture, such as a horizontal drag across the screen, very quickly, what can often occurs is a series of multiple small drag gestures are registered and queued. +> +> Each time [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture) is called, it will dequeue the next gesture. So to ensure that we handle the complete gesture, we loop the gesture queue until there are none left. + +## Implementing Input in Our Game + +For our game, we are going to implement keyboard and gamepad controls based on the following criteria: + +| Keyboard Input | Gamepad Input | Description | +|---------------------------|---------------------------------------------|--------------------------------------| +| [Keys.W] and [Keys.Up] | [Thumbstick.Left.Y] and [Buttons.DPadUp] | Moves the slime up the screen. | +| [Keys.S] and [Keys.Down] | [Thumbstick.Left.Y] and [Buttons.DPadDown] | Moves the slime down the screen | +| [Keys.A] and [Keys.Left] | [Thumbstick.Left.X] and [Buttons.DPadLeft] | Moves the slime left on the screen. | +| [Keys.D] and [Keys.Right] | [Thumbstick.Left.X] and [Buttons.DPadRight] | Moves the slime right on the screen. | +| [Keys.Space] | [Buttons.A] | Increased the speed of the slime. | + +Open `Game1.cs` 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](./videos/input-moving-slime.webm) | +|:-----------------------------------------------------------------------------------------------:| +| **Figure 10-1: The slime moving around based on device input** | + +> [!NOTE] +> You may notice that the slime is capable of moving completely off the screen, this is completely normal as we have not yet implemented any logic to prevent it from doing so, it only doing what we currently tell it to do. + +## Conclusion + +In this chapter, you accomplished the following: + +- Handle keyboard input to detect key presses. +- Handle mouse input including button clicks and cursor position. +- Work with gamepad controls including buttons, thumbsticks, and vibration. +- Understand touch input for mobile devices including touch points and gestures. +- Implement movement controls using different input methods. +- Consider controller-specific details like coordinate systems and analog vs digital input. + +In the next chapter, we will learn how to track previous input states to handle single-press events and implement an input management system to simplify some of the complexity of handling input. + +## Test Your Knowledge + +1. Why do we store the result of `GetState` in a variable instead of calling it multiple times? + + :::question-answer + Storing the state in a variable is more efficient and ensures consistent input checking within a frame. Each `GetState` call polls the device, which can impact performance if called repeatedly. + ::: + +2. What is the main difference between how keyboard and mouse/gamepad button states are checked? + + :::question-answer + Keyboard input uses [**IsKeyUp**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyUp(Microsoft.Xna.Framework.Input.Keys))/[**IsKeyDown**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys)) methods, while mouse and gamepad buttons return a [**ButtonState**](xref:Microsoft.Xna.Framework.Input.ButtonState) enum value (Pressed or Released). + ::: + +3. When using thumbstick values for movement, why do we multiply the Y value by -1? + + :::question-answer + The thumbstick Y-axis values (-1.0f down to 1.0f up) are inverted compared to MonoGame's screen coordinate system (Y increases downward). Multiplying by -1 aligns the thumbstick direction with screen movement. + ::: + +4. What is the difference between analog and digital trigger input on a gamepad? + + :::question-answer + Analog triggers provide values between 0.0f and 1.0f based on how far they are pressed, while digital triggers only report 0.0f (not pressed) or 1.0f (pressed). This affects how you handle trigger input in your game. + ::: + +5. What is the key difference between [**TouchPanel.GetState**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.GetState) and [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture)? + + :::question-answer + [**TouchPanel.GetState**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.GetState) returns information about current touch points on the screen, while [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture) provides information about specific gesture patterns like taps, drags, and pinches that have been performed. + ::: + +6. Why do we use a while loop with [**TouchPanel.IsGestureAvailable**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.IsGestureAvailable) when reading gestures? + + :::question-answer + Quick gestures can generate multiple gesture events that are queued. Using a while loop with [**TouchPanel.IsGestureAvailable**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.IsGestureAvailable) ensures we process all queued gestures, as [**TouchPanel.ReadGesture**](xref:Microsoft.Xna.Framework.Input.Touch.TouchPanel.ReadGesture) only returns one gesture at a time. + ::: + +7. How does touch input differ from mouse input in terms of handling multiple input points? + + :::question-answer + Touch input can handle multiple simultaneous touch points through the [**TouchCollection**](xref:Microsoft.Xna.Framework.Input.Touch.TouchCollection), while mouse input only tracks a single cursor position. This allows touch input to support features like multi-touch gestures that are not possible with a mouse. + ::: + +8. What are the different states a [**TouchLocation**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation) can have and what do they indicate? + + :::question-answer + A [**TouchLocation**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocation) can have four states: + + - [**Pressed**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState): Initial contact with the screen + - [**Moved**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState): Touch point moved while maintaining contact + - [**Released**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState): Contact with the screen ended + - [**Invalid**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState): Touch data is not valid or tracking was lost + + ::: diff --git a/articles/tutorials/building_2d_games/10_handling_input/snippets/game1.cs b/articles/tutorials/building_2d_games/10_handling_input/snippets/game1.cs index 284cd57e..a48e5d3d 100644 --- a/articles/tutorials/building_2d_games/10_handling_input/snippets/game1.cs +++ b/articles/tutorials/building_2d_games/10_handling_input/snippets/game1.cs @@ -44,8 +44,6 @@ protected override void LoadContent() // Create the bat animated sprite from the atlas. _bat = atlas.CreateAnimatedSprite("bat-animation"); _bat.Scale = new Vector2(4.0f, 4.0f); - - base.LoadContent(); } protected override void Update(GameTime gameTime) diff --git a/articles/tutorials/building_2d_games/11_input_management/index.md b/articles/tutorials/building_2d_games/11_input_management/index.md index 3aecd62d..84f3f590 100644 --- a/articles/tutorials/building_2d_games/11_input_management/index.md +++ b/articles/tutorials/building_2d_games/11_input_management/index.md @@ -382,7 +382,7 @@ The key changes to the `Core` class are: Now we can update our `Game1` class to use the new input management system through the `Core` class. Open `Game1.cs` in the game project and update it to the following: -[!code-csharp[](./snippets/game1.cs?highlight=1,7,77,83,89,95,101,109,114,117,121,127,129-130,135,141,147,153)] +[!code-csharp[](./snippets/game1.cs?highlight=6,74,80,86,92,98,106,111,114,118,124,126-127,132,138,144,150)] The key changes to the `Game1` class are: 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 index ada62d6e..ab380346 100644 --- a/articles/tutorials/building_2d_games/11_input_management/snippets/game1.cs +++ b/articles/tutorials/building_2d_games/11_input_management/snippets/game1.cs @@ -46,8 +46,6 @@ protected override void LoadContent() // Create the bat animated sprite from the atlas. _bat = atlas.CreateAnimatedSprite("bat-animation"); _bat.Scale = new Vector2(4.0f, 4.0f); - - base.LoadContent(); } protected override void Update(GameTime gameTime) diff --git a/articles/tutorials/building_2d_games/12_collision_detection/index.md b/articles/tutorials/building_2d_games/12_collision_detection/index.md index cae8c174..9554ab9d 100644 --- a/articles/tutorials/building_2d_games/12_collision_detection/index.md +++ b/articles/tutorials/building_2d_games/12_collision_detection/index.md @@ -311,7 +311,7 @@ If you run the game right now and move the slime around, you will notice a few i We can now implement these features using collision detection and response in our game. In the *DungeonSlime* project (your main game project), open the `Game1.cs` file and make the following changes to the `Game1` class: -[!code-csharp[](./snippets/game1.cs?highlight=25-29,40-45,81-181,186-198,298-299)] +[!code-csharp[](./snippets/game1.cs?highlight=1,5,25-29,40-45,79-179,184-196,296-297)] The key changes made here are: 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 index ea167c0b..7a85b431 100644 --- a/articles/tutorials/building_2d_games/12_collision_detection/snippets/game1.cs +++ b/articles/tutorials/building_2d_games/12_collision_detection/snippets/game1.cs @@ -57,8 +57,6 @@ protected override void LoadContent() // Create the bat animated sprite from the atlas. _bat = atlas.CreateAnimatedSprite("bat-animation"); _bat.Scale = new Vector2(4.0f, 4.0f); - - base.LoadContent(); } protected override void Update(GameTime gameTime) 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 index 8411f4d3..72cad12c 100644 --- a/articles/tutorials/building_2d_games/13_working_with_tilemaps/index.md +++ b/articles/tutorials/building_2d_games/13_working_with_tilemaps/index.md @@ -186,7 +186,7 @@ This tilemap configuration creates a simple dungeon layout with walls around the With all of the assets now in place and configured, we can update the `Game1` class to load the tilemap and draw it. We will also need to update the collision logic so that the boundary is no longer the edge of the screen, but instead the edges of the wall tiles of the tilemap. Open `Game1.cs` and make the following updates: -[!code-csharp[](./snippets/game1.cs?highlight=31-35,46-61,80-82,114,116,118,120,123,125,127,129,147,150,152,155,158,161,163,166,181-183,305-306)] +[!code-csharp[](./snippets/game1.cs?highlight=31-35,46-61,80-82,112,114,116,128,121,123,125,127,145,148,150,153,156,159,161,164,179-181,303-304)] The key changes to the `Game1` class include: 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 index d57de7c3..adbd0a34 100644 --- 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 @@ -80,8 +80,6 @@ protected override void LoadContent() // Create the tilemap from the XML configuration file. _tilemap = Tilemap.FromFile(Content, "images/tilemap-definition.xml"); _tilemap.Scale = new Vector2(4.0f, 4.0f); - - base.LoadContent(); } protected override void Update(GameTime gameTime) 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 index 0868e9dd..5ae662e4 100644 --- a/articles/tutorials/building_2d_games/14_soundeffects_and_music/index.md +++ b/articles/tutorials/building_2d_games/14_soundeffects_and_music/index.md @@ -175,7 +175,7 @@ Add these files to your content project using the MGCB Editor: Next, open the `Game1.cs` file and update it to the following: -[!code-csharp[](./snippets/game1.cs?highlight=3,6,39-43,92-111,205-206,224-225)] +[!code-csharp[](./snippets/game1.cs?highlight=3,6,39-43,92-111,203-204,222-223)] The key changes here are: 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 index 79971dd9..e06f1633 100644 --- 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 @@ -109,8 +109,6 @@ protected override void LoadContent() // Set the theme music to repeat. MediaPlayer.IsRepeating = true; - - base.LoadContent(); } protected override void Update(GameTime gameTime) diff --git a/articles/tutorials/building_2d_games/15_audio_controller/index.md b/articles/tutorials/building_2d_games/15_audio_controller/index.md index 2e39d04a..80f7bc69 100644 --- a/articles/tutorials/building_2d_games/15_audio_controller/index.md +++ b/articles/tutorials/building_2d_games/15_audio_controller/index.md @@ -131,7 +131,7 @@ The key changes made here are: Next, update the `Game1` class to use the audio controller for audio playback. Open `Game1.cs` and make the following updates: -[!code-csharp[](./snippets/game1.cs?highlight=45-46,77-78,104-105,199-200,218-219,272-290)] +[!code-csharp[](./snippets/game1.cs?highlight=45-46,77-78,104-105,197-198,216-217,270-288)] > [!NOTE] > Note there were a lot of replacements in the `LoadContent` method, switching from loading and initializing the background Song and replacing it with a call to the new `AudioController` to do all the work managing the Song reference. Much cleaner. 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 index 9839aad7..77c8d393 100644 --- a/articles/tutorials/building_2d_games/15_audio_controller/snippets/game1.cs +++ b/articles/tutorials/building_2d_games/15_audio_controller/snippets/game1.cs @@ -103,8 +103,6 @@ protected override void LoadContent() // Load the background theme music _themeSong = Content.Load("audio/theme"); - - base.LoadContent(); } protected override void Update(GameTime gameTime) 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 index 3af2748d..d3125f25 100644 --- a/articles/tutorials/building_2d_games/16_working_with_spritefonts/index.md +++ b/articles/tutorials/building_2d_games/16_working_with_spritefonts/index.md @@ -217,7 +217,7 @@ The key changes here are: Finally, open the `Game1.cs` file and make the following changes: -[!code-csharp[](./snippets/game1.cs?highlight=48-58,93-99,129-130,246-247,391-402)] +[!code-csharp[](./snippets/game1.cs?highlight=48-58,93-99,129-130,244-245,389-400)] The key changes made are: diff --git a/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/game1.cs b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/game1.cs index ad5ecc90..458f4a45 100644 --- a/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/game1.cs +++ b/articles/tutorials/building_2d_games/16_working_with_spritefonts/snippets/game1.cs @@ -127,8 +127,6 @@ protected override void LoadContent() // Load the font _font = Content.Load("fonts/04B_30"); - - base.LoadContent(); } protected override void Update(GameTime gameTime) diff --git a/articles/tutorials/building_2d_games/17_scenes/snippets/game1.cs b/articles/tutorials/building_2d_games/17_scenes/snippets/game1.cs index 7553f265..2b8fffb4 100644 --- a/articles/tutorials/building_2d_games/17_scenes/snippets/game1.cs +++ b/articles/tutorials/building_2d_games/17_scenes/snippets/game1.cs @@ -27,8 +27,6 @@ protected override void Initialize() protected override void LoadContent() { - base.LoadContent(); - // Load the background theme music _themeSong = Content.Load("audio/theme"); }