Skip to content

Commit c76a79e

Browse files
committed
Update input manager to be a game component
1 parent c28c520 commit c76a79e

File tree

4 files changed

+82
-108
lines changed

4 files changed

+82
-108
lines changed

articles/tutorials/building_2d_games/06_content_pipeline/Game1.cs

Lines changed: 0 additions & 56 deletions
This file was deleted.

articles/tutorials/building_2d_games/06_content_pipeline/index.md

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: "Chapter 06: Content Pipeline"
33
description: Learn the advantages of using the Content Pipeline to load assets and go through the processes of loading your first asset
44
---
55

6-
Every game has assets; images to represent the visual graphics to players, audio to provide sound effects and background music, fonts to render text with, and much more. These assets start out as raw files (e.g. *.png* image files or *.mp3* audio files), which you'll need to load into the game to use.
6+
Every game has assets; images to represent the visual graphics to players, audio to provide sound effects and background music, fonts to render text with, and much more. These assets start out as raw files (e.g. *.png* image files or *.mp3* audio files), which you'll need to load into the game to use.
77

88
## Loading Assets
99

@@ -116,12 +116,14 @@ After changes have been made in the MGBC Editor, ensure that you save the change
116116
Save the changes and then close the MGCB Editor.
117117

118118
## Understanding Content Paths
119+
119120
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:
120121

121122
1. Compile the image into an optimized format in the **content project's** output directory (typically *ProjectRoot/Content/bin/Platform/Content*) as an *.xnb* file.
122-
2. Copy the compiled assets to your **game's** output directory (typically *ProjectRoot/bin/Debug/net8.0/Content* or *ProjectRoot/bin/Release/net8.0/Content*).
123+
2. Copy the compiled assets to your **game's** output directory (typically *ProjectRoot/bin/Debug/net8.0/Content* or *ProjectRoot/bin/Release/net8.0/Content*).
123124

124125
For example, if your content project contains:
126+
125127
```sh
126128
Content/
127129
├── images/
@@ -169,18 +171,30 @@ The [**Game**](xref:Microsoft.Xna.Framework.Game) class provides the [**Content*
169171
1. `T` Type Reference: The content type we are loading.
170172
2. `assetName` Parameter: A string path that matches the content path of the asset to load. As mentioned in the [Understanding Content Paths](#understanding-content-paths) section, the content path is relative to the [**ContentManager.RootDirectory**](xref:Microsoft.Xna.Framework.Content.ContentManager.RootDirectory), minus the extension. For instance, we added our image to the *images* folder in the content project, the content path for it will be `"images/logo"`.
171173

172-
Let's update the game now to load the image file using the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager). First, open the *Game1.cs* file in your project and replace the contents with the following:
174+
Let's update the game now to load the image file using the [**ContentManager**](xref:Microsoft.Xna.Framework.Content.ContentManager). Open the *Game1.cs* file in the game project and perform the following:
175+
176+
1. Add a new [**Texture2D**](xref:Microsoft.Xna.Framework.Graphics.Texture2D) field to store the logo texture in:
177+
178+
```cs
179+
private Texture2D _logo;
180+
```
181+
182+
2. In [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent), load the logo texture using the content pipeline:
173183

174-
[!code-csharp[](./Game1.cs?highlight=11,32,50-52)]
184+
```cs
185+
_logo = Content.Load<Texture2D>("images/logo");
186+
```
175187

176-
The key changes we made here are
188+
3. In [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)), draw the texture using the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch):
177189

178-
- The `_logo` member was added to store a reference to the logo texture once we load it.
179-
- In [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent), the logo texture is loaded using [**ContentManager.Load<T>**](xref:Microsoft.Xna.Framework.Content.ContentManager.Load``1(System.String)).
180-
- In [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)), the logo is rendered using the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch).
190+
```cs
191+
_spriteBatch.Begin();
192+
_spriteBatch.Draw(_logo, Vector2.Zero, Color.White);
193+
_spriteBatch.End();
194+
```
181195

182-
> [!NOTE]
183-
> We'll go more into detail about the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) in the next chapter.
196+
> [!NOTE]
197+
> We'll go more into detail about the [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch) in the next chapter.
184198

185199
Running the game now will show the MonoGame logo displayed in the upper-left corner of the game window.
186200

articles/tutorials/building_2d_games/12_input_management/index.md

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,7 @@ That's it for the `GamePadInfo` class. Next, let's create the actual input mana
789789

790790
## The InputManager Class
791791

792-
Now that we have classes to handle keyboard, mouse, and gamepad input individually, we can create a centralized manager class to coordinate all input handling. The `InputManager` class will be static, providing easy access to all input states from anywhere in our game.
792+
Now that we have classes to handle keyboard, mouse, and gamepad input individually, we can create a centralized manager class to coordinate all input handling. The `InputManager` class will be a [**GameComponent**](xref:Microsoft.Xna.Framework.GameComponent) like we discussed previously in [Chapter 05](../05_game_components/index.md).
793793

794794
In the *Input* directory of the *MonoGameLibrary* project, add a new file named *InputManager.cs* with this initial structure:
795795

@@ -798,45 +798,57 @@ using Microsoft.Xna.Framework;
798798

799799
namespace MonoGameLibrary.Input;
800800

801-
public static class InputManager
801+
public class InputManager : GameComponent
802802
{
803803

804804
}
805805
```
806806

807807
### InputManager Properties
808808

809-
The InputManager class needs properties to access each type of input device. Add these properties:
809+
The `InputManager` class needs properties to access each type of input device. Add these properties:
810810

811811
```cs
812812
/// <summary>
813813
/// Gets the state information of keyboard input.
814814
/// </summary>
815-
public static KeyboardInfo Keyboard { get; private set; }
815+
public KeyboardInfo Keyboard { get; private set; }
816816

817817
/// <summary>
818818
/// Gets the state information of mouse input.
819819
/// </summary>
820-
public static MouseInfo Mouse { get; private set; }
820+
public MouseInfo Mouse { get; private set; }
821821

822822
/// <summary>
823823
/// Gets the state information of a gamepad.
824824
/// </summary>
825-
public static GamePadInfo[] GamePads { get; private set; }
825+
public GamePadInfo[] GamePads { get; private set; }
826826
```
827827

828828
> [!NOTE]
829829
> The `GamePads` property is an array because MonoGame supports up to four gamepads simultaneously. Each gamepad is associated with a PlayerIndex (0-3).
830830
831+
### InputManager Constructor
832+
833+
The `InputManager` needs a constructor that accept a [**Game**](xref:Microsoft.Xna.Framework.Game) parameter due to inheriting from the [**GameComponent**](xref:Microsoft.Xna.Framework.GameComponent) class. Add the following constructor:
834+
835+
```cs
836+
/// <summary>
837+
/// Creates a new InputManager.
838+
/// </summary>
839+
/// <param name="game">The game this input manager belongs to.</param>
840+
public InputManager(Game game) : base(game) { }
841+
```
842+
831843
### InputManager Methods
832844

833-
First, we need a method to initialize our input devices:
845+
First, override the `Initialize` method so that we can ensure that the states for each input devices are initialized:
834846

835847
```cs
836848
/// <summary>
837849
/// Initializes this input manager.
838850
/// </summary>
839-
public static void Initialize()
851+
public override void Initialize()
840852
{
841853
Keyboard = new KeyboardInfo();
842854
Mouse = new MouseInfo();
@@ -849,14 +861,14 @@ public static void Initialize()
849861
}
850862
```
851863

852-
Next, we'll add a method to update all input states:
864+
Next override the `Update` method so that the states of the input devices are updated:
853865

854866
```cs
855867
/// <summary>
856868
/// Updates the state information for the keyboard, mouse, and gamepad inputs.
857869
/// </summary>
858870
/// <param name="gameTime">A snapshot of the timing values for the current frame.</param>
859-
public static void Update(GameTime gameTime)
871+
public override void Update(GameTime gameTime)
860872
{
861873
Keyboard.Update();
862874
Mouse.Update();
@@ -868,73 +880,78 @@ public static void Update(GameTime gameTime)
868880
}
869881
```
870882

871-
> [!TIP]
872-
> By centralizing input updates in the `InputManager`, we ensure all input states are updated consistently each frame. You only need to call `InputManager.Update` once in your game's [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)) method.
873-
874883
### Implementing the InputManager Class
875884

876885
Now that we have our input management system complete, let's update our game to use it. Instead of tracking input states directly, we'll use the `InputManager` to handle all our input detection. Open *Game1.cs* and make the following changes:
877886

878-
Let's update the input code in our game now to instead use the `InputManager` class to manage tracking input states which inputs are active. Open the *Game1.cs* file and perform the following:
887+
1. Add the `MonoGameLibrary.Input` using directive so we can access the `InputManager` class:
888+
889+
```cs
890+
using MonoGameLibrary.Input;
891+
```
879892

880-
1. First we need to set up the `InputManager`. In [**Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize), add this initialization code just before `base.Initialize()`:
893+
2. Add a field for the `InputManager` class:
881894

882895
```cs
883-
InputManager.Initialize();
896+
private InputManager _input;
884897
```
885898

886-
2. Next, in [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)), we need to ensure input states are updated each frame. Add the following as the first line of code inside the [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)) method:
899+
3. In the constructor, create the `InputManager` and add it to to the game component collection:
887900

888901
```cs
889-
InputManager.Update(gameTime);
902+
// Create and add the input manager component to the game's component collection
903+
_input = new InputManager(this);
904+
Components.Add(_input);
890905
```
891906

892-
3. Next, remove the `if` statement in [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)) that checks for the gamepad back button or the keyboard escape key being pressed and then exits the game.
907+
4. In [**Update**](xref:xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)), remove the `if` statement that checks for the gamepad back button or keyboard escape key being pressed and then calls exit. We're going to move this to the individual handle input methods in a moment.
893908

894-
4. Finally, update the game controls to use the `InputManager`. Replace the `HandleKeyboardInput`, `HandleMouseInput` and `HandleGamePadInput` methods with the following:
909+
5. In [**Update**](xref:xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)) move the `base.Update(gameTime)` call from being the last line of the method to the first line of the method. We do this because game components are updated during the `base.Update(gameTime)` call and we want to ensure that input is updated before we start handling input checks.
910+
911+
6. Update the `HandleKeyboardInput` method to use the new `InputManager`:
895912

896913
```cs
897914
private void HandleKeyboardInput()
898915
{
899-
if (InputManager.Keyboard.IsKeyDown(Keys.Escape))
916+
if (_input.Keyboard.IsKeyDown(Keys.Escape))
900917
{
901918
Exit();
902919
}
903-
if (InputManager.Keyboard.IsKeyDown(Keys.Up))
920+
921+
if (_input.Keyboard.IsKeyDown(Keys.Up))
904922
{
905923
_slimePosition.Y -= MOVEMENT_SPEED;
906924
}
907-
if (InputManager.Keyboard.IsKeyDown(Keys.Down))
925+
926+
if (_input.Keyboard.IsKeyDown(Keys.Down))
908927
{
909928
_slimePosition.Y += MOVEMENT_SPEED;
910929
}
911-
if (InputManager.Keyboard.IsKeyDown(Keys.Left))
930+
931+
if (_input.Keyboard.IsKeyDown(Keys.Left))
912932
{
913933
_slimePosition.X -= MOVEMENT_SPEED;
914934
}
915-
if (InputManager.Keyboard.IsKeyDown(Keys.Right))
935+
936+
if (_input.Keyboard.IsKeyDown(Keys.Right))
916937
{
917938
_slimePosition.X += MOVEMENT_SPEED;
918939
}
919940
}
941+
```
920942

921-
private void HandleMouseInput()
922-
{
923-
if (InputManager.Mouse.WasButtonJustPressed(MouseButton.Left))
924-
{
925-
_batPosition = InputManager.Mouse.Position.ToVector2();
926-
}
927-
}
943+
7. Update the `HandleGamePadInput` method to use the new `InputManager`:
928944

945+
```cs
929946
private void HandleGamepadInput()
930947
{
931-
GamePadInfo gamePadOne = InputManager.GamePads[(int)PlayerIndex.One];
932-
933-
if(gamePadOne.IsButtonDown(Buttons.Back))
948+
GamePadInfo gamePadOne = _input.GamePads[(int)PlayerIndex.One];
949+
950+
if (gamePadOne.IsButtonDown(Buttons.Back))
934951
{
935952
Exit();
936953
}
937-
954+
938955
if (gamePadOne.IsButtonDown(Buttons.A))
939956
{
940957
_slimePosition.X += gamePadOne.LeftThumbStick.X * 1.5f * MOVEMENT_SPEED;
@@ -949,6 +966,8 @@ Let's update the input code in our game now to instead use the `InputManager` cl
949966
}
950967
```
951968

969+
8. Remove the `HandleMouseInput` and the `HandleTouchInput` methods, we no longer need these since they were just for example purposes.
970+
952971
The key improvements in this implementation are:
953972

954973
1. Centralized Input Management:
@@ -961,9 +980,6 @@ The key improvements in this implementation are:
961980
- Gamepad vibration is handled through `SetVibration` with automatic duration.
962981
- Thumbstick values are easily accessed through `LeftThumbStick` property.
963982

964-
> [!NOTE]
965-
> Using `WasButtonJustPressed` instead of `IsButtonDown` for the mouse control means the bat only moves when you first click, not continuously while holding the button. This gives you more precise control over movement.
966-
967983
Running the game now, you will be able to control it the same as before, only now we're using our new `InputManager` class instead.
968984

969985
## Conclusion

articles/tutorials/building_2d_games/14_soundeffects_and_music/index.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -614,17 +614,17 @@ Next, let's implement the `AudioManager` game component that was created in our
614614
6. Update `HandleKeyboardInput` to add new inputs to control the audio manager:
615615

616616
```cs
617-
if (InputManager.Keyboard.WasKeyJustPressed(Keys.M))
617+
if (_input.Keyboard.WasKeyJustPressed(Keys.M))
618618
{
619619
_audioManager.ToggleMute();
620620
}
621621

622-
if (InputManager.Keyboard.WasKeyJustPressed(Keys.OemPlus))
622+
if (_input.Keyboard.WasKeyJustPressed(Keys.OemPlus))
623623
{
624624
_audioManager.IncreaseVolume(0.1f);
625625
}
626626

627-
if (InputManager.Keyboard.WasKeyJustPressed(Keys.OemMinus))
627+
if (_input.Keyboard.WasKeyJustPressed(Keys.OemMinus))
628628
{
629629
_audioManager.DecreaseVolume(0.1f);
630630
}

0 commit comments

Comments
 (0)