From 2c370b561714923029d9afa0688dfe3b7518d86e Mon Sep 17 00:00:00 2001 From: Christopher Whitley Date: Thu, 6 Feb 2025 21:53:31 -0500 Subject: [PATCH 01/11] wip --- .../10_input_management/index.md | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 articles/tutorials/building_2d_games/10_input_management/index.md diff --git a/articles/tutorials/building_2d_games/10_input_management/index.md b/articles/tutorials/building_2d_games/10_input_management/index.md new file mode 100644 index 00000000..e8c117c5 --- /dev/null +++ b/articles/tutorials/building_2d_games/10_input_management/index.md @@ -0,0 +1,193 @@ +--- +title: "Chapter 10: Input Management" +description: TODO WRITE DESCRIPTION +--- + +In [Chapter 09](../09_handling_input/index.md), you learned how to handle input from various devices like keyboard, mouse, and gamepad. While checking if an input is currently down works well for continuous actions like movement, many game actions should only happen once when an input is first pressed; think firing a weapon or jumping. To handle these scenarios, we need to compare the current input state with the previous frame's state to detect when an input changes from up to down. + +In this chapter you will: + +- Learn the difference between an input being down versus being pressed +- Track input states between frames +- Create a reusable input management system +- Simplify handling input across multiple devices + +Let's start by understanding the concept of input state changes and how we can detect them. + +## Understanding Input States + +When handling input in games, there are two key scenarios we need to consider: + +- An input is being held down (like holding a movement key). +- An input was just pressed for one frame (like pressing a jump button). + +Let's look at the difference using keyboard input as an example. With our current implementation, we can check if a key is down using [**KeyboardState.IsKeyDown**](): + +```cs +if (keyboardState.IsKeyDown(Keys.Space)) +{ + // This runs EVERY frame the space key is held down +} +``` + +However, many game actions shouldn't repeat while a key is held. For instance, if the Space key makes your character jump, you probably don't want them to jump repeatedly just because the player is holding the key down. Instead, you want the jump to happen only on the first frame when Space is pressed. + +To detect this "just pressed" state, we need to compare two states: + +1. Is the key down in the current frame? +2. Was the key up in the previous frame? + +If both conditions are true, we know the key was just pressed. If we were to modify the above code to track the previous keyboard state it would look something like this: + +```cs +private KeyboardState _previousKeyboardState; + +protected override void Update(GameTime gameTime) +{ + KeyboardState keyboardState = Keyboard.GetState(); + + if (keyboardState.IsKeyDown(Keys.Space) && _previousKeyboardState.IsKeyUp(Keys.Space)) + { + // This will only run on the first frame Space is pressed and will not + // happen again until it has been released and then pressed again. + } + + _previousKeyboardState = keyboardState; + base.Update(gameTime); +} +``` + +This same concept applies to mouse buttons and gamepad input as well. Any time you need to detect a "just pressed" or "just released" state, you'll need to compare the current input state with the previous frame's state. + +So far, we've only been working with our game within the *Game1.cs* file. This has been fine for the examples given. Overtime, as the game grows, we're going to have a more complex system setup with different scenes, and each scene will need a way to track the state of input over time. We could do this by creating a lot of variables in each scene to track this information, or we can use object-oriented design concepts to create a reusable `InputManager` class to simplify this for us. + +Before we create the `InputManager` class, let's first create classes for the keyboard, mouse, and gamepad that encapsulates the information about those inputs which will then be exposed through the `InputManager`. + +To get started, create a new directory called *Input* in the *MonoGameLibrary* project. We'll put all of our input related classes here. + +## The KeyboardInfo Class + +The `KeyboardInfo` class will be the first one we create. This class will track the previous and current state of keyboard input and provide methods of checking the state of keys on the keyboard. In the *Input* directory of the *MonoGameLibrary* project, add a new file named *KeyboardInfo.cs*. Add the following code for the foundation fo the `KeyboardInfo` class: + +```cs +using Microsoft.Xna.Framework.Input; + +namespace MonoGameLibrary.Input; + +public class KeyboardInfo +{ + +} +``` + +### KeyboardInfo Properties + +The `KeyboardInfo` class will need two properties to track the previous and current state of keyboard input. Add the following properties: + +```cs +/// +/// Gets the state of keyboard input during the previous update cycle. +/// +public KeyboardState PreviousState { get; private set; } + +/// +/// Gets the state of keyboard input during the current input cycle. +/// +public KeyboardState CurrentState { get; private set; } +``` + +Both of these properties are get-only, so they are still accessible outside of the input management system we're creating if needed, but setting the values for each remains private within the class itself. + +### KeyboardInfo Constructor + +The `KeyboardInfo` class will only need a single default constructor. Add the following constructor: + +```cs +/// +/// Creates a new KeyboardInfo +/// +public KeyboardInfo() +{ + PreviousState = new KeyboardState(); + CurrentState = Keyboard.GetState(); +} +``` + +This creates a new `KeyboardInfo` instance and sets the initial state values by setting the `PreviousState` to a new empty [**KeyboardState**]() and the `CurrentState` to the state of the keyboard at that moment. + +### KeyboardInfo Methods + +First, the `KeyboardInfo` class needs a method to update the `PreviousState` and `CurrentState` properties. Add the following method: + +```cs +/// +/// Updates the state information about keyboard input. +/// +public void Update() +{ + PreviousState = CurrentState; + CurrentState = Keyboard.GetState(); +} +``` + +With this, each time `Update` is called, whatever the current state is will get cached into the `PreviousState` property and then the `CurrentState` property will be updated with the most recent state. + +Next, the `KeyboardInfo` class will need methods to retrieve the state of key inputs. Add the following methods: + +```cs +/// +/// Returns a value that indicates if the specified key is currently down. +/// +/// The key to check. +/// true if the specified key is currently down; otherwise, false. +public bool IsKeyDown(Keys key) +{ + return CurrentState.IsKeyDown(key); +} + +/// +/// Returns a value that indicates whether the specified key is currently up. +/// +/// The key to check. +/// true if the specified key is currently up; otherwise, false. +public bool IsKeyUp(Keys key) +{ + return CurrentState.IsKeyUp(key); +} + +/// +/// Returns a value that indicates if the specified key was just pressed on the current frame. +/// +/// The key to check. +/// true if the specified key was just pressed on the current frame; otherwise, false. +public bool WasKeyJustPressed(Keys key) +{ + return CurrentState.IsKeyDown(key) && PreviousState.IsKeyUp(key); +} + +/// +/// Returns a value that indicates if the specified key was just released on the current frame. +/// +/// The key to check. +/// true if the specified key was just released on the current frame; otherwise, false. +public bool WasKeyJustReleased(Keys key) +{ + return CurrentState.IsKeyUp(key) && PreviousState.IsKeyDown(key); +} +``` + +These methods handle different aspects of keyboard input: + +- `IsKeyDown` and `IsKeyUp` are simple wrappers for the existing methods offered by the current state. They will return true for as long as the key is held down or is up, respectively. +- `WasKeyJustPressed` and `WasKeyJustReleased` are used to check for frame specific key state changes. They only return true on the specific frame when a key state occurs; either from up-to-down (pressed) or down-to-up (released). + +The difference is that the first pair tracks continuous states wile the second pair tracks frame-to-frame changes. This is useful for distinguishing between holding a key down and tapping a key. + +## The MouseInfo Class + +The `MouseInfo` class will track the previous and current state of mouse input, properties for getting the position and scroll wheel values, and methods for checking the state of mouse buttons. + + + + + From a95ad628dc6877a91a29ad1ff840aa5c0d3e4465 Mon Sep 17 00:00:00 2001 From: Christopher Whitley Date: Sun, 9 Feb 2025 22:32:27 -0500 Subject: [PATCH 02/11] Chapter 10 --- .../10_input_management/index.md | 799 +++++++++++++++++- 1 file changed, 786 insertions(+), 13 deletions(-) diff --git a/articles/tutorials/building_2d_games/10_input_management/index.md b/articles/tutorials/building_2d_games/10_input_management/index.md index e8c117c5..fba981cf 100644 --- a/articles/tutorials/building_2d_games/10_input_management/index.md +++ b/articles/tutorials/building_2d_games/10_input_management/index.md @@ -24,6 +24,8 @@ When handling input in games, there are two key scenarios we need to consider: Let's look at the difference using keyboard input as an example. With our current implementation, we can check if a key is down using [**KeyboardState.IsKeyDown**](): ```cs +KeyboardState keyboardState = Keyboard.GetState(); + if (keyboardState.IsKeyDown(Keys.Space)) { // This runs EVERY frame the space key is held down @@ -67,7 +69,13 @@ To get started, create a new directory called *Input* in the *MonoGameLibrary* p ## The KeyboardInfo Class -The `KeyboardInfo` class will be the first one we create. This class will track the previous and current state of keyboard input and provide methods of checking the state of keys on the keyboard. In the *Input* directory of the *MonoGameLibrary* project, add a new file named *KeyboardInfo.cs*. Add the following code for the foundation fo the `KeyboardInfo` class: +Let's start our input management system by creating a class to handle keyboard input. The `KeyboardInfo` class will encapsulate all keyboard-related functionality, making it easier to: + +- Track current and previous keyboard states +- Detect when keys are pressed or released +- Check if keys are being held down + +In the *Input* directory of the *MonoGameLibrary* project, add a new file named *KeyboardInfo.cs* with this initial structure: ```cs using Microsoft.Xna.Framework.Input; @@ -82,7 +90,7 @@ public class KeyboardInfo ### KeyboardInfo Properties -The `KeyboardInfo` class will need two properties to track the previous and current state of keyboard input. Add the following properties: +To detect changes in keyboard input between frames, we need to track both the previous and current keyboard states. Add these properties to the `KeyboardInfo` class: ```cs /// @@ -96,11 +104,12 @@ public KeyboardState PreviousState { get; private set; } public KeyboardState CurrentState { get; private set; } ``` -Both of these properties are get-only, so they are still accessible outside of the input management system we're creating if needed, but setting the values for each remains private within the class itself. +> [!NOTE] +> These properties use a public getter but private setter pattern. This allows other parts of the game to read the keyboard states if needed, while ensuring only the `KeyboardInfo` class can update them. ### KeyboardInfo Constructor -The `KeyboardInfo` class will only need a single default constructor. Add the following constructor: +The `KeyboardInfo` class needs a constructor to initialize the keyboard states. Add this constructor: ```cs /// @@ -113,11 +122,16 @@ public KeyboardInfo() } ``` -This creates a new `KeyboardInfo` instance and sets the initial state values by setting the `PreviousState` to a new empty [**KeyboardState**]() and the `CurrentState` to the state of the keyboard at that moment. +The constructor: + +- Creates an empty state for `PreviousState` since there is no previous input yet +- Gets the current keyboard state as our starting point for `CurrentState` + +This initialization ensures we have valid states to compare against in the first frame of our game, preventing any potential null reference issues when checking for input changes. ### KeyboardInfo Methods -First, the `KeyboardInfo` class needs a method to update the `PreviousState` and `CurrentState` properties. Add the following method: +The `KeyboardInfo` class needs methods both for updating states and checking key states. Let's start with our update method: ```cs /// @@ -130,9 +144,10 @@ public void Update() } ``` -With this, each time `Update` is called, whatever the current state is will get cached into the `PreviousState` property and then the `CurrentState` property will be updated with the most recent state. +> [!NOTE] +> Each time `Update` is called, the current state becomes the previous state, and we get a fresh current state. This creates our frame-to-frame comparison chain. -Next, the `KeyboardInfo` class will need methods to retrieve the state of key inputs. Add the following methods: +Next, we'll add methods to check various key states: ```cs /// @@ -176,18 +191,776 @@ public bool WasKeyJustReleased(Keys key) } ``` -These methods handle different aspects of keyboard input: +These methods serve two distinct purposes. For checking continuous states: + +- `IsKeyDown`: Returns true as long as a key is being held down. +- `IsKeyUp`: Returns true as long as a key is not being pressed. -- `IsKeyDown` and `IsKeyUp` are simple wrappers for the existing methods offered by the current state. They will return true for as long as the key is held down or is up, respectively. -- `WasKeyJustPressed` and `WasKeyJustReleased` are used to check for frame specific key state changes. They only return true on the specific frame when a key state occurs; either from up-to-down (pressed) or down-to-up (released). +And for detecting state changes: -The difference is that the first pair tracks continuous states wile the second pair tracks frame-to-frame changes. This is useful for distinguishing between holding a key down and tapping a key. +- `WasKeyJustPressed`: Returns true only on the frame when a key changes from up-to-down. +- `WasKeyJustReleased`: Returns true only on the frame when a key changes from down-to-up. + +> [!TIP] +> Use continuous state checks (`IsKeyDown`/`IsKeyUp`) for actions that should repeat while a key is held, like movement. Use single-frame checks (`WasKeyJustPressed`/`WasKeyJustReleased`) for actions that should happen once per key press, like jumping or shooting. + +That's it for the `KeyboardInfo` class, let's move on to mouse input next. + +## MouseButton Enum + +Recall from the [Mouse Input](../09_handling_input/index.md#mouse-input) section of the previous chapter that the [**MouseState**]() struct provides button states through properties rather than methods like `IsButtonDown`/`IsButtonUp`. To keep our input management API consistent across devices, we'll create a `MouseButton` enum that lets us reference mouse buttons in a similar way to how we use [**Keys**]() for keyboard input and [**Buttons**]() for gamepad input. + +In the *Input* directory of the *MonoGameLibrary* project, add a new file named *MouseButton.cs* with the following code: + +```cs +namespace MonoGameLibrary.Input; + +public enum MouseButton +{ + Left, + Middle, + Right, + XButton1, + XButton2 +} +``` + +> [!NOTE] +> Each enum value corresponds directly to a button property in MouseState: + +> - `Left`: Maps to [**MouseState.LeftButton**]() +> - `Middle`: Maps to [**MouseState.MiddleButton**]() +> - `Right`: Maps to [**MouseState.RightButton**]() +> - `XButton1`: Maps to [**MouseState.XButton1**]() +> - `XButton2`: Maps to [**MouseState.XButton2**]() ## The MouseInfo Class -The `MouseInfo` class will track the previous and current state of mouse input, properties for getting the position and scroll wheel values, and methods for checking the state of mouse buttons. +To manage mouse input effectively, we need to track both current and previous states, as well as provide easy access to mouse position, scroll wheel values, and button states. The `MouseInfo` class will encapsulate all of this functionality, making it easier to: + +- Track current and previous mouse states. +- Track the mouse position and detect movement. +- Track scroll wheel changes. +- Detect when mouse buttons are pressed or released +- Check if mouse buttons are being held down + +Let's create this class in the *Input* directory of the *MonoGameLibrary* project. Add a new file named *MouseInfo.cs* with the following initial structure: + +```cs +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace MonoGameLibrary.Input; + +public class MouseInfo +{ + +} +``` + +### MouseInfo Properties + +The `MouseInfo` class needs properties to track both mouse states and provide easy access to common mouse information. Let's add these properties. + +First, we need properties for tracking mouse states: + +```cs +/// +/// The state of mouse input during the previous update cycle. +/// +public MouseState PreviousState { get; private set; } + +/// +/// The state of mouse input during the current update cycle. +/// +public MouseState CurrentState { get; private set; } +``` + +Next, we'll add properties for handling cursor position: + +```cs +/// +/// Gets or Sets the current position of the mouse cursor in screen space. +/// +public Point Position +{ + get => CurrentState.Position; + set => SetPosition(value.X, value.Y); +} + +/// +/// Gets or Sets the current x-coordinate position of the mouse cursor in screen space. +/// +public int X +{ + get => CurrentState.X; + set => SetPosition(value, CurrentState.Y); +} + +/// +/// Gets or Sets the current y-coordinate position of the mouse cursor in screen space. +/// +public int Y +{ + get => CurrentState.Y; + set => SetPosition(CurrentState.X, value); +} + +/// +/// Gets a value that indicates if the mouse cursor moved between the previous and current frames. +/// +public bool WasMoved => CurrentState.X != PreviousState.X || CurrentState.Y != PreviousState.Y; +``` + +> [!NOTE] +> The position properties use a `SetPosition` method that we'll implement later. This method will handle the actual cursor positioning on screen. + +These properties provide different ways to work with the cursor position: + +- `Position`: Gets/sets the cursor position as a [**Point**](). +- `X`: Gets/sets just the horizontal position. +- `Y`: Gets/sets just the vertical position. +- `WasMoved`: Indicates if the cursor moved this frame. + +Finally, we'll add properties for handling the scroll wheel: + +```cs +/// +/// Gets the cumulative value of the mouse scroll wheel since the start of the game. +/// +public int ScrollWheel => CurrentState.ScrollWheelValue; + +/// +/// Gets the value of the scroll wheel between the previous and current frame. +/// +public int ScrollWheelDelta => CurrentState.ScrollWheelValue - PreviousState.ScrollWheelValue; +``` + +The scroll wheel properties serve different purposes: + +- `ScrollWheel`: Gets the total accumulated scroll value since game start. +- `ScrollWheelDelta`: Gets the change in scroll value just in this frame. + +> [!TIP] +> Use `ScrollWheelDelta` when you need to respond to how much the user just scrolled, rather than tracking the total scroll amount. + +### MouseInfo Constructor + +The `KeyboardInfo` class needs a constructor to initialize the mouse states. Add this constructor: + +```cs +/// +/// Creates a new MouseInfo. +/// +public MouseInfo() +{ + PreviousState = new MouseState(); + CurrentState = Mouse.GetState(); +} +``` + +The constructor + +- Creates an empty state for `PreviousState` since there is no previous input yet. +- Gets the current mouse state as our starting point for `CurrentState`. + +This initialization ensures we have valid states to compare against in the first frame of our game, preventing any potential null reference issues when checking for input changes. + +### MouseInfo Methods + +The `MouseInfo` class needs methods for updating states, checking button states, and setting the cursor position. Let's start with our update method: + +```cs +/// +/// Updates the state information about mouse input. +/// +public void Update() +{ + PreviousState = CurrentState; + CurrentState = Mouse.GetState(); +} +``` + +Next, we'll add methods to check various button states: + +```cs +/// +/// Returns a value that indicates whether the specified mouse button is currently down. +/// +/// The mouse button to check. +/// true if the specified mouse button is currently down; otherwise, false. +public bool IsButtonDown(MouseButton button) +{ + switch (button) + { + case MouseButton.Left: + return CurrentState.LeftButton == ButtonState.Pressed; + case MouseButton.Middle: + return CurrentState.MiddleButton == ButtonState.Pressed; + case MouseButton.Right: + return CurrentState.RightButton == ButtonState.Pressed; + case MouseButton.XButton1: + return CurrentState.XButton1 == ButtonState.Pressed; + case MouseButton.XButton2: + return CurrentState.XButton2 == ButtonState.Pressed; + default: + return false; + } +} + +/// +/// Returns a value that indicates whether the specified mouse button is current up. +/// +/// The mouse button to check. +/// true if the specified mouse button is currently up; otherwise, false. +public bool IsButtonUp(MouseButton button) +{ + switch (button) + { + case MouseButton.Left: + return CurrentState.LeftButton == ButtonState.Released; + case MouseButton.Middle: + return CurrentState.MiddleButton == ButtonState.Released; + case MouseButton.Right: + return CurrentState.RightButton == ButtonState.Released; + case MouseButton.XButton1: + return CurrentState.XButton1 == ButtonState.Released; + case MouseButton.XButton2: + return CurrentState.XButton2 == ButtonState.Released; + default: + return false; + } +} + +/// +/// Returns a value that indicates whether the specified mouse button was just pressed on the current frame. +/// +/// The mouse button to check. +/// true if the specified mouse button was just pressed on the current frame; otherwise, false. +public bool WasButtonJustPressed(MouseButton button) +{ + switch (button) + { + case MouseButton.Left: + return CurrentState.LeftButton == ButtonState.Pressed && PreviousState.LeftButton == ButtonState.Released; + case MouseButton.Middle: + return CurrentState.MiddleButton == ButtonState.Pressed && PreviousState.MiddleButton == ButtonState.Released; + case MouseButton.Right: + return CurrentState.RightButton == ButtonState.Pressed && PreviousState.RightButton == ButtonState.Released; + case MouseButton.XButton1: + return CurrentState.XButton1 == ButtonState.Pressed && PreviousState.XButton1 == ButtonState.Released; + case MouseButton.XButton2: + return CurrentState.XButton2 == ButtonState.Pressed && PreviousState.XButton2 == ButtonState.Released; + default: + return false; + } +} + +/// +/// Returns a value that indicates whether the specified mouse button was just released on the current frame. +/// +/// The mouse button to check. +/// true if the specified mouse button was just released on the current frame; otherwise, false.F +public bool WasButtonJustReleased(MouseButton button) +{ + switch (button) + { + case MouseButton.Left: + return CurrentState.LeftButton == ButtonState.Released && PreviousState.LeftButton == ButtonState.Pressed; + case MouseButton.Middle: + return CurrentState.MiddleButton == ButtonState.Released && PreviousState.MiddleButton == ButtonState.Pressed; + case MouseButton.Right: + return CurrentState.RightButton == ButtonState.Released && PreviousState.RightButton == ButtonState.Pressed; + case MouseButton.XButton1: + return CurrentState.XButton1 == ButtonState.Released && PreviousState.XButton1 == ButtonState.Pressed; + case MouseButton.XButton2: + return CurrentState.XButton2 == ButtonState.Released && PreviousState.XButton2 == ButtonState.Pressed; + default: + return false; + } +} +``` + +These methods serve two distinct purposes. For checking continuous states: + + +- `IsKeyDown`: Returns true as long as a key is being held down. +- `IsKeyUp`: Returns true as long as a key is not being pressed. + +And for detecting state changes: +- `WasKeyJustPressed`: Returns true only on the frame when a key changes from up-to-down. +- `WasKeyJustReleased`: Returns true only on the frame when a key changes from down-to-up. + +> [!NOTE] +> Each method uses a switch statement to check the appropriate button property from the [**MouseState**]() based on which `MouseButton` enum value is provided. This provides a consistent API while handling the different button properties internally. + +Finally, we need a method to handle setting the cursor position: + +```cs +/// +/// Sets the current position of the mouse cursor in screen space and updates the CurrentState with the new position. +/// +/// The x-coordinate location of the mouse cursor in screen space. +/// The y-coordinate location of the mouse cursor in screen space. +public void SetPosition(int x, int y) +{ + Mouse.SetPosition(x, y); + CurrentState = new MouseState( + x, + y, + CurrentState.ScrollWheelValue, + CurrentState.LeftButton, + CurrentState.MiddleButton, + CurrentState.RightButton, + CurrentState.XButton1, + CurrentState.XButton2 + ); +} +``` + +> [!TIP] +> Notice that after setting the position, we immediately update the `CurrentState`. This ensures our state tracking remains accurate even when manually moving the cursor. + +That's it for the `MouseInfo` class, next we'll move onto gamepad input. + +## The GamePadInfo Class + +To manage gamepad input effectively, we need to track both current and previous states, as well as provide easy access to the thumbstick values, trigger values, and button states. The `GamePadInfo` class will encapsulate all of this functionality, making it easier to: + +- Track current and previous mouse states. +- Track the position of the left and right thumbsticks. +- Check the values of the left and right triggers. +- Detect when gamepad buttons are pressed or released. +- Check if gamepad buttons are being held down. +- Start and Stop vibration of a gamepad. + +Let's create this class in the *Input* directory of the *MonoGameLibrary* project. Add a new file named *GamePadInfo.cs* with the following initial structure: + +```cs +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace MonoGameLibrary.Input; + +public class GamePadInfo +{ + +} +``` + +### GamePadInfo Properties + +We use vibration in gamepads to provide haptic feedback to the player. The [**GamePad**]() class provides the [**SetVibration**]() method to tell the gamepad to vibrate, but it does not provide a timing mechanism for it if we wanted to only vibrate for a certain period of time. Add the following private field to the `GamePadInfo` class: + +```cs +private TimeSpan _vibrationTimeRemaining = TimeSpan.Zero; +``` + +Recall from the [previous chapter](../09_handling_input/index.md#gamepad-input) that a [**PlayerIndex**]() value needs to be supplied when calling [**Gamepad.GetState**](). Doing this returns the state of the gamepad connected at that player index. So we'll need a property to track the player index this gamepad info is for. + +```cs +/// +/// Gets the index of the player this gamepad is for. +/// +public PlayerIndex PlayerIndex { get; } +``` + +To detect changes in the gamepad input between frames, we need to track both the previous and current gamepad states. Add these properties to the `GamePadInfo` class: + +```cs +/// +/// Gets the state of input for this gamepad during the previous update cycle. +/// +public GamePadState PreviousState { get; private set; } + +/// +/// Gets the state of input for this gamepad during the current update cycle. +/// +public GamePadState CurrentState { get; private set; } +``` + +There are times that a gamepad can disconnect for various reasons; being unplugged, bluetooth disconnection, or battery dying are just some examples. To track if the gamepad is connected, add the following property: + +```cs +/// +/// Gets a value that indicates if this gamepad is currently connected. +/// +public bool IsConnected => CurrentState.IsConnected; +``` + +The values of the thumbsticks and triggers can be accessed through the `CurrentState`. However, instead of having to navigate through multiple property chains to get this information, add the following properties to get direct access to the values: + +```cs +/// +/// Gets the value of the left thumbstick of this gamepad. +/// +public Vector2 LeftThumbStick => CurrentState.ThumbSticks.Left; + +/// +/// Gets the value of the right thumbstick of this gamepad. +/// +public Vector2 RightThumbStick => CurrentState.ThumbSticks.Right; + +/// +/// Gets the value of the left trigger of this gamepad. +/// +public float LeftTrigger => CurrentState.Triggers.Left; + +/// +/// Gets the value of the right trigger of this gamepad. +/// +public float RightTrigger => CurrentState.Triggers.Right; +``` + +### GamePadInfo Constructor + +The `GamePadInfo` class needs a constructor to initialize the gamepad states. Add this constructor + +```cs +/// +/// Creates a new GamePadInfo for the gamepad connected at the specified player index. +/// +/// The index of the player for this gamepad. +public GamePadInfo(PlayerIndex playerIndex) +{ + PlayerIndex = playerIndex; + PreviousState = new GamePadState(); + CurrentState = GamePad.GetState(playerIndex); +} +``` + +This constructor + +- Requires a [**PlayerIndex**]() value which is stored and will be used to get the states for the correct gamepad +- Creates an empty state for `PreviousState` since there is no previous state yet. +- Gets the current gamepad state as our starting `CurrentState`. + +This initialization ensures we have valid states to compare against in the first frame of our game, preventing any potential null reference issues when checking for input changes. + +### GamePadInfo Methods + +The `GamePadInfo` class needs methods for updating states, checking button states, and controlling vibration. Let's start with our update method: + +```cs +/// +/// Updates the state information for this gamepad input. +/// +/// +public void Update(GameTime gameTime) +{ + PreviousState = CurrentState; + CurrentState = GamePad.GetState(PlayerIndex); + + if (_vibrationTimeRemaining > TimeSpan.Zero) + { + _vibrationTimeRemaining -= gameTime.ElapsedGameTime; + + if (_vibrationTimeRemaining <= TimeSpan.Zero) + { + StopVibration(); + } + } +} +``` + +> [!NOTE] +> Unlike keyboard and mouse input, the gamepad update method takes a [**GameTime**]() parameter. This allows us to track and manage timed vibration effects. + +Next, we'll add methods to check various button states: + +```cs +/// +/// Returns a value that indicates whether the specified gamepad button is current down. +/// +/// The gamepad button to check. +/// true if the specified gamepad button is currently down; otherwise, false. +public bool IsButtonDown(Buttons button) +{ + return CurrentState.IsButtonDown(button); +} + +/// +/// Returns a value that indicates whether the specified gamepad button is currently up. +/// +/// The gamepad button to check. +/// true if the specified gamepad button is currently up; otherwise, false. +public bool IsButtonUp(Buttons button) +{ + return CurrentState.IsButtonUp(button); +} + +/// +/// Returns a value that indicates whether the specified gamepad button was just pressed on the current frame. +/// +/// +/// true if the specified gamepad button was just pressed on the current frame; otherwise, false. +public bool WasButtonJustPressed(Buttons button) +{ + return CurrentState.IsButtonDown(button) && PreviousState.IsButtonUp(button); +} + +/// +/// Returns a value that indicates whether the specified gamepad button was just released on the current frame. +/// +/// +/// true if the specified gamepad button was just released on the current frame; otherwise, false. +public bool WasButtonJustReleased(Buttons button) +{ + return CurrentState.IsButtonUp(button) && PreviousState.IsButtonDown(button); +} +``` + +These methods serve two distinct purposes. For checking continuous states: + +- `IsButtonDown`: Returns true as long as a button is being held down. +- `IsButtonUp`: Returns true as long as a button is not being pressed. + +And for detecting state changes: + +- `WasButtonJustPressed`: Returns true only on the frame when a button changes from up-to-down. +- `WasButtonJustReleased`: Returns true only on the frame when a button changes from down-to-up. + +Finally, we'll add methods for controlling gamepad vibration: + +```cs + /// +/// Sets the vibration for all motors of this gamepad. +/// +/// The strength of the vibration from 0.0f (none) to 1.0f (full). +/// The amount of time the vibration should occur. +public void SetVibration(float strength, TimeSpan time) +{ + _vibrationTimeRemaining = time; + GamePad.SetVibration(PlayerIndex, strength, strength); +} + +/// +/// Stops the vibration of all motors for this gamepad. +/// +public void StopVibration() +{ + GamePad.SetVibration(PlayerIndex, 0.0f, 0.0f); +} +``` + +The vibration methods provide control over the gamepad's haptic feedback: + +- `SetVibration`: Starts vibration at the specified strength for a set duration. +- `StopVibration`: Immediately stops all vibration. + +> [!TIP] +> When setting vibration, you can specify both the strength (`0.0f` to `1.0f`) and duration. The vibration will automatically stop after the specified time has elapsed, so you don't need to manage stopping it manually. + + +That's it for the `GamePadInfo` class. Next, let's create the actual input manager. + +## The InputManager Class + +Now that we have classes to handle keyboard, mouse, and gamepad input individually, we can create a centralized manager class to coordinate all input handling. The `InputManager` class will be static, providing easy access to all input states from anywhere in our game. + +In the *Input* directory of the *MonoGameLibrary* project, add a new file named *InputManager.cs* with this initial structure: + + +```cs +using Microsoft.Xna.Framework; + +namespace MonoGameLibrary.Input; + +public static class InputManager +{ + +} +``` + +### InputManager Properties + +The InputManager class needs properties to access each type of input device. Add these properties: + +```cs +/// +/// Gets the state information of keyboard input. +/// +public static KeyboardInfo Keyboard { get; private set; } + +/// +/// Gets the state information of mouse input. +/// +public static MouseInfo Mouse { get; private set; } + +/// +/// Gets the state information of a gamepad. +/// +public static GamePadInfo[] GamePads { get; private set; } +``` + +> [!NOTE] +> The `GamePads` property is an array because MonoGame supports up to four gamepads simultaneously. Each gamepad is associated with a PlayerIndex (0-3). + +### InputManager Methods + +First, we need a method to initialize our input devices: + +```cs +/// +/// Initializes this input manager. +/// +public static void Initialize() +{ + Keyboard = new KeyboardInfo(); + Mouse = new MouseInfo(); + + GamePads = new GamePadInfo[4]; + for (int i = 0; i < 4; i++) + { + GamePads[i] = new GamePadInfo((PlayerIndex)i); + } +} +``` + +Next, we'll add a method to update all input states: + +```cs +/// +/// Updates the state information for the keyboard, mouse, and gamepad inputs. +/// +/// A snapshot of the timing values for the current frame. +public static void Update(GameTime gameTime) +{ + Keyboard.Update(); + Mouse.Update(); + + for (int i = 0; i < 4; i++) + { + GamePads[i].Update(gameTime); + } +} +``` + +> [!TIP] +> 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**]() method. + + +### Implementing the InputManager Class + +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: + +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: + +1. First we need to set up the `InputManager`. In [**Initialize**](), add this initialization code just before `base.Initialize()`: + + ```cs + InputManager.Initialize(); + ``` + +2. Next, in [**Update**](), we need to ensure input states are updated each frame. Add the following as the first line of code inside the [**Update**]() method: + + ```cs + InputManager.Update(gameTime); + ``` + +3. Now let's update our game controls to use the `InputManager`. First replace the exit condition code with the following: + + ```cs + GamePadInfo gamePadOne = InputManager.GamePads[(int)PlayerIndex.One]; + + if(gamePadOne.WasButtonJustPressed(Buttons.Back) || InputManager.Keyboard.WasKeyJustPressed(Keys.Escape)) + { + Exit(); + } + ``` + + > [!NOTE] + > Notice how we store a reference to `GamePadInfo` for player one. This makes our code more readable and efficient since we don't need to access the `GamePads` array multiple times. + +4. Finally, replace the keyboard, mouse, and gamepad movement controls we implemented previously with the following: + + ```cs + if(InputManager.Keyboard.IsKeyDown(Keys.Up)) + { + _slimePos.Y -= MOVEMENT_SPEED; + } + if (InputManager.Keyboard.IsKeyDown(Keys.Down)) + { + _slimePos.Y += MOVEMENT_SPEED; + } + if (InputManager.Keyboard.IsKeyDown(Keys.Left)) + { + _slimePos.X -= MOVEMENT_SPEED; + } + if (InputManager.Keyboard.IsKeyDown(Keys.Right)) + { + _slimePos.X += MOVEMENT_SPEED; + } + + if (InputManager.Mouse.WasButtonJustPressed(MouseButton.Left)) + { + _batPosition = InputManager.Mouse.Position.ToVector2(); + } + + if (gamePadOne.IsButtonDown(Buttons.A)) + { + _slimePos.X += gamePadOne.LeftThumbStick.X * 1.5f * MOVEMENT_SPEED; + _slimePos.Y -= gamePadOne.LeftThumbStick.Y * 1.5f * MOVEMENT_SPEED; + gamePadOne.SetVibration(1.0f, TimeSpan.FromSeconds(0.5f)); + } + else + { + _slimePos.X += gamePadOne.LeftThumbStick.X * MOVEMENT_SPEED; + _slimePos.Y -= gamePadOne.LeftThumbStick.Y * MOVEMENT_SPEED; + } + ``` + +The key improvements in this implementation are: + +1. Centralized Input Management: + - All input is now accessed through the `InputManager`. + - Input states are automatically tracked between frames. + - No need to manually store previous states. + +2. Improved Input Detection: + - Mouse movement now only triggers on initial click using `WasButtonJustPressed`. + - Gamepad vibration is handled through `SetVibration` with automatic duration. + - Thumbstick values are easily accessed through `LeftThumbStick` property. + +> [!NOTE] +> 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. + +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. + +## Conclusion + +In this chapter, you learned how to: + +- Detect the difference between continuous and single-frame input states. +- Create classes to manage different input devices. +- Build a centralized `InputManager` to coordinate all input handling that is: + - Reusable across different game projects + - Easy to maintain and extend + - Consistent across different input devices + +## Test Your Knowledge + +1. What's the difference between checking if an input is "down" versus checking if it was "just pressed"? +
+ Question 1 Answer + + > "Down" checks if an input is currently being held, returning true every frame while held. "Just pressed" only returns true on the first frame when the input changes from up to down, requiring comparison between current and previous states. +

+2. Why do we track both current and previous input states? +
+ Question 2 Answer + + > Tracking both states allows us to detect when input changes occur by comparing the current frame's state with the previous frame's state. This is essential for implementing "just pressed" and "just released" checks. +

+3. What advantage does the `InputManager` provide over handling input directly? +
+ Question 3 Answer + + > The `InputManager` centralizes all input handling, automatically tracks states between frames, and provides a consistent API across different input devices. This makes the code more organized, reusable, and easier to maintain. +

\ No newline at end of file From db55086317e3b5dcb7815a418e6c04a7dda06929 Mon Sep 17 00:00:00 2001 From: Christopher Whitley Date: Sun, 9 Feb 2025 22:33:52 -0500 Subject: [PATCH 03/11] Add description --- .../tutorials/building_2d_games/10_input_management/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/tutorials/building_2d_games/10_input_management/index.md b/articles/tutorials/building_2d_games/10_input_management/index.md index fba981cf..bf57cc7e 100644 --- a/articles/tutorials/building_2d_games/10_input_management/index.md +++ b/articles/tutorials/building_2d_games/10_input_management/index.md @@ -1,6 +1,6 @@ --- title: "Chapter 10: Input Management" -description: TODO WRITE DESCRIPTION +description: "Learn how to create an input management system to handle keyboard, mouse, and gamepad input, including state tracking between frames and creating a reusable framework for handling player input." --- In [Chapter 09](../09_handling_input/index.md), you learned how to handle input from various devices like keyboard, mouse, and gamepad. While checking if an input is currently down works well for continuous actions like movement, many game actions should only happen once when an input is first pressed; think firing a weapon or jumping. To handle these scenarios, we need to compare the current input state with the previous frame's state to detect when an input changes from up to down. From fe34bca90ab854b8bb8cdef0e8122dce4029db78 Mon Sep 17 00:00:00 2001 From: Christopher Whitley Date: Sun, 9 Feb 2025 22:34:34 -0500 Subject: [PATCH 04/11] Update toc --- articles/toc.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/articles/toc.yml b/articles/toc.yml index d6fe30b8..707547fe 100644 --- a/articles/toc.yml +++ b/articles/toc.yml @@ -129,7 +129,9 @@ - name: "08: The AnimatedSprite Class" href: tutorials/building_2d_games/08_the_animatedsprite_class/ - name: "09: Handling Input" - href: tutorials/building_2d_games/09_handling_input/ + href: tutorials/building_2d_games/09_handling_input/ + - name: "10: Input Management" + href: tutorials/building_2d_games/10_input_management/ - name: Console Access href: console_access.md - name: Help and Support From b3f9ef7605cfe6be5ab2e72b8d54bbf4438716e3 Mon Sep 17 00:00:00 2001 From: Christopher Whitley Date: Sun, 9 Feb 2025 22:35:38 -0500 Subject: [PATCH 05/11] Add chapter 10 to chapter list --- articles/tutorials/building_2d_games/index.md | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/articles/tutorials/building_2d_games/index.md b/articles/tutorials/building_2d_games/index.md index ad2e6abb..6cc2b3b4 100644 --- a/articles/tutorials/building_2d_games/index.md +++ b/articles/tutorials/building_2d_games/index.md @@ -19,22 +19,23 @@ This documentation will introduce game development concepts using the MonoGame f > [!CAUTION] > This is currently a work in progress and is not finished. -| Chapter | Summary | Source Files | -|------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|--------------| -| [01: What Is MonoGame](01_what_is_monogame/index.md) | Learn about the history of MonoGame and explore the features it provides developers when creating games. | | -| [02: Getting Started](02_getting_started/index.md) | Setup your development environment for dotnet development and MonoGame using Visual Studio Code as your IDE. | | -| [03: The Game1 File](03_the_game1_file/index.md) | Explore the contents of the Game1 file generated when creating a new MonoGame project. | | -| [04: Content Pipeline](04_content_pipeline/index.md) | Learn the advantages of using the **Content Pipeline** to load assets and go through the processes of loading your first asset | | -| [05: Working with Textures](05_working_with_textures/index.md) | Learn how to load and render textures using the MonoGame content pipeline and [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch). | | -| [06: Optimizing Texture Rendering](06_optimizing_texture_rendering/index.md) | Explore optimization techniques when rendering textures using a texture atlas. | | -| [07: The Sprite Class](07_the_sprite_class/index.md) | Explore creating a reusable Sprite class to efficiently sprites and their rendering properties, including position, rotation, scale, and more. | | -| [08: The AnimatedSprite Class](07_the_sprite_class/index.md) | Create an AnimatedSprite class that builds upon our Sprite class to support frame-based animations. | | -| [09: Handling Input](09_handling_input/index.md) | Learn how to handle keyboard, mouse, and gamepad input in MonoGame. | | +| Chapter | Summary | Source Files | +| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| [01: What Is MonoGame](01_what_is_monogame/index.md) | Learn about the history of MonoGame and explore the features it provides developers when creating games. | | +| [02: Getting Started](02_getting_started/index.md) | Setup your development environment for dotnet development and MonoGame using Visual Studio Code as your IDE. | | +| [03: The Game1 File](03_the_game1_file/index.md) | Explore the contents of the Game1 file generated when creating a new MonoGame project. | | +| [04: Content Pipeline](04_content_pipeline/index.md) | Learn the advantages of using the **Content Pipeline** to load assets and go through the processes of loading your first asset | | +| [05: Working with Textures](05_working_with_textures/index.md) | Learn how to load and render textures using the MonoGame content pipeline and [**SpriteBatch**](xref:Microsoft.Xna.Framework.Graphics.SpriteBatch). | | +| [06: Optimizing Texture Rendering](06_optimizing_texture_rendering/index.md) | Explore optimization techniques when rendering textures using a texture atlas. | | +| [07: The Sprite Class](07_the_sprite_class/index.md) | Explore creating a reusable Sprite class to efficiently sprites and their rendering properties, including position, rotation, scale, and more. | | +| [08: The AnimatedSprite Class](07_the_sprite_class/index.md) | Create an AnimatedSprite class that builds upon our Sprite class to support frame-based animations. | | +| [09: Handling Input](09_handling_input/index.md) | Learn how to handle keyboard, mouse, and gamepad input in MonoGame. | | +| [10: Input Management](10_input_management//index.md) | Learn how to create an input management system to handle keyboard, mouse, and gamepad input, including state tracking between frames and creating a reusable framework for handling player input. | | In additional to the chapter documentation, supplemental documentation is also provided to give a more in-depth look at different topics with MonoGame. These are provided through the Appendix documentation below: | Appendix | Summary | -|----------|---------| +| -------- | ------- | | | | ## Conventions Used in This Documentation From ec38ecd7eba384712c68c3ae3fe650a394e6e958 Mon Sep 17 00:00:00 2001 From: Christopher Whitley Date: Sun, 9 Feb 2025 22:43:58 -0500 Subject: [PATCH 06/11] Add xref links --- .../10_input_management/index.md | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/articles/tutorials/building_2d_games/10_input_management/index.md b/articles/tutorials/building_2d_games/10_input_management/index.md index bf57cc7e..149118f5 100644 --- a/articles/tutorials/building_2d_games/10_input_management/index.md +++ b/articles/tutorials/building_2d_games/10_input_management/index.md @@ -21,7 +21,7 @@ When handling input in games, there are two key scenarios we need to consider: - An input is being held down (like holding a movement key). - An input was just pressed for one frame (like pressing a jump button). -Let's look at the difference using keyboard input as an example. With our current implementation, we can check if a key is down using [**KeyboardState.IsKeyDown**](): +Let's look at the difference using keyboard input as an example. With our current implementation, we can check if a key is down using [**KeyboardState.IsKeyDown**](xref:Microsoft.Xna.Framework.Input.KeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys)): ```cs KeyboardState keyboardState = Keyboard.GetState(); @@ -208,7 +208,7 @@ That's it for the `KeyboardInfo` class, let's move on to mouse input next. ## MouseButton Enum -Recall from the [Mouse Input](../09_handling_input/index.md#mouse-input) section of the previous chapter that the [**MouseState**]() struct provides button states through properties rather than methods like `IsButtonDown`/`IsButtonUp`. To keep our input management API consistent across devices, we'll create a `MouseButton` enum that lets us reference mouse buttons in a similar way to how we use [**Keys**]() for keyboard input and [**Buttons**]() for gamepad input. +Recall from the [Mouse Input](../09_handling_input/index.md#mouse-input) section of the previous chapter that the [**MouseState**](xref:Microsoft.Xna.Framework.Input.MouseState) struct provides button states through properties rather than methods like `IsButtonDown`/`IsButtonUp`. To keep our input management API consistent across devices, we'll create a `MouseButton` enum that lets us reference mouse buttons in a similar way to how we use [**Keys**](xref:Microsoft.Xna.Framework.Input.Keys) for keyboard input and [**Buttons**](xref:Microsoft.Xna.Framework.Input.Buttons) for gamepad input. In the *Input* directory of the *MonoGameLibrary* project, add a new file named *MouseButton.cs* with the following code: @@ -228,11 +228,11 @@ public enum MouseButton > [!NOTE] > Each enum value corresponds directly to a button property in MouseState: -> - `Left`: Maps to [**MouseState.LeftButton**]() -> - `Middle`: Maps to [**MouseState.MiddleButton**]() -> - `Right`: Maps to [**MouseState.RightButton**]() -> - `XButton1`: Maps to [**MouseState.XButton1**]() -> - `XButton2`: Maps to [**MouseState.XButton2**]() +> - `Left`: Maps to [**MouseState.LeftButton**](xref:Microsoft.Xna.Framework.Input.MouseState.LeftButton). +> - `Middle`: Maps to [**MouseState.MiddleButton**](xref:Microsoft.Xna.Framework.Input.MouseState.MiddleButton). +> - `Right`: Maps to [**MouseState.RightButton**](xref:Microsoft.Xna.Framework.Input.MouseState.RightButton). +> - `XButton1`: Maps to [**MouseState.XButton1**](xref:Microsoft.Xna.Framework.Input.MouseState.XButton1). +> - `XButton2`: Maps to [**MouseState.XButton2**](xref:Microsoft.Xna.Framework.Input.MouseState.XButton2). ## The MouseInfo Class @@ -317,7 +317,7 @@ public bool WasMoved => CurrentState.X != PreviousState.X || CurrentState.Y != P These properties provide different ways to work with the cursor position: -- `Position`: Gets/sets the cursor position as a [**Point**](). +- `Position`: Gets/sets the cursor position as a [**Point**](xref:Microsoft.Xna.Framework.Point). - `X`: Gets/sets just the horizontal position. - `Y`: Gets/sets just the vertical position. - `WasMoved`: Indicates if the cursor moved this frame. @@ -492,7 +492,7 @@ And for detecting state changes: - `WasKeyJustReleased`: Returns true only on the frame when a key changes from down-to-up. > [!NOTE] -> Each method uses a switch statement to check the appropriate button property from the [**MouseState**]() based on which `MouseButton` enum value is provided. This provides a consistent API while handling the different button properties internally. +> Each method uses a switch statement to check the appropriate button property from the [**MouseState**](xref:Microsoft.Xna.Framework.Input.MouseState) based on which `MouseButton` enum value is provided. This provides a consistent API while handling the different button properties internally. Finally, we need a method to handle setting the cursor position: @@ -551,13 +551,13 @@ public class GamePadInfo ### GamePadInfo Properties -We use vibration in gamepads to provide haptic feedback to the player. The [**GamePad**]() class provides the [**SetVibration**]() method to tell the gamepad to vibrate, but it does not provide a timing mechanism for it if we wanted to only vibrate for a certain period of time. Add the following private field to the `GamePadInfo` class: +We use vibration in gamepads to provide haptic feedback to the player. The [**GamePad**](xref:Microsoft.Xna.Framework.Input.GamePad) class provides the [**SetVibration**](xref:Microsoft.Xna.Framework.Input.GamePad.SetVibration(Microsoft.Xna.Framework.PlayerIndex,System.Single,System.Single)) method to tell the gamepad to vibrate, but it does not provide a timing mechanism for it if we wanted to only vibrate for a certain period of time. Add the following private field to the `GamePadInfo` class: ```cs private TimeSpan _vibrationTimeRemaining = TimeSpan.Zero; ``` -Recall from the [previous chapter](../09_handling_input/index.md#gamepad-input) that a [**PlayerIndex**]() value needs to be supplied when calling [**Gamepad.GetState**](). Doing this returns the state of the gamepad connected at that player index. So we'll need a property to track the player index this gamepad info is for. +Recall from the [previous chapter](../09_handling_input/index.md#gamepad-input) that a [**PlayerIndex**](xref:Microsoft.Xna.Framework.PlayerIndex) value needs to be supplied when calling [**Gamepad.GetState**](xref:Microsoft.Xna.Framework.Input.GamePad.GetState(Microsoft.Xna.Framework.PlayerIndex)). Doing this returns the state of the gamepad connected at that player index. So we'll need a property to track the player index this gamepad info is for. ```cs /// @@ -632,7 +632,7 @@ public GamePadInfo(PlayerIndex playerIndex) This constructor -- Requires a [**PlayerIndex**]() value which is stored and will be used to get the states for the correct gamepad +- Requires a [**PlayerIndex**](xref:Microsoft.Xna.Framework.PlayerIndex) value which is stored and will be used to get the states for the correct gamepad - Creates an empty state for `PreviousState` since there is no previous state yet. - Gets the current gamepad state as our starting `CurrentState`. @@ -665,7 +665,7 @@ public void Update(GameTime gameTime) ``` > [!NOTE] -> Unlike keyboard and mouse input, the gamepad update method takes a [**GameTime**]() parameter. This allows us to track and manage timed vibration effects. +> Unlike keyboard and mouse input, the gamepad update method takes a [**GameTime**](xref:Microsoft.Xna.Framework.GameTime) parameter. This allows us to track and manage timed vibration effects. Next, we'll add methods to check various button states: @@ -838,7 +838,7 @@ public static void Update(GameTime gameTime) ``` > [!TIP] -> 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**]() method. +> 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. ### Implementing the InputManager Class @@ -847,13 +847,13 @@ Now that we have our input management system complete, let's update our game to 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: -1. First we need to set up the `InputManager`. In [**Initialize**](), add this initialization code just before `base.Initialize()`: +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()`: ```cs InputManager.Initialize(); ``` -2. Next, in [**Update**](), we need to ensure input states are updated each frame. Add the following as the first line of code inside the [**Update**]() method: +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: ```cs InputManager.Update(gameTime); From c9e4c74358a167d8bd99532067768004a3c832ba Mon Sep 17 00:00:00 2001 From: Christopher Whitley Date: Sun, 9 Feb 2025 22:50:20 -0500 Subject: [PATCH 07/11] Fix bullets in conclusion --- .../building_2d_games/10_input_management/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/articles/tutorials/building_2d_games/10_input_management/index.md b/articles/tutorials/building_2d_games/10_input_management/index.md index 149118f5..a29be7ef 100644 --- a/articles/tutorials/building_2d_games/10_input_management/index.md +++ b/articles/tutorials/building_2d_games/10_input_management/index.md @@ -935,9 +935,9 @@ In this chapter, you learned how to: - Detect the difference between continuous and single-frame input states. - Create classes to manage different input devices. - Build a centralized `InputManager` to coordinate all input handling that is: - - Reusable across different game projects - - Easy to maintain and extend - - Consistent across different input devices + - Reusable across different game projects + - Easy to maintain and extend + - Consistent across different input devices ## Test Your Knowledge From 7015bc2d58d85581618367d2d4e6de9178b1314f Mon Sep 17 00:00:00 2001 From: Christopher Whitley <103014489+AristurtleDev@users.noreply.github.com> Date: Mon, 10 Feb 2025 13:37:33 -0500 Subject: [PATCH 08/11] Markdown lint fixes --- .../10_input_management/index.md | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/articles/tutorials/building_2d_games/10_input_management/index.md b/articles/tutorials/building_2d_games/10_input_management/index.md index a29be7ef..41ccfbcf 100644 --- a/articles/tutorials/building_2d_games/10_input_management/index.md +++ b/articles/tutorials/building_2d_games/10_input_management/index.md @@ -227,7 +227,7 @@ public enum MouseButton > [!NOTE] > Each enum value corresponds directly to a button property in MouseState: - +> > - `Left`: Maps to [**MouseState.LeftButton**](xref:Microsoft.Xna.Framework.Input.MouseState.LeftButton). > - `Middle`: Maps to [**MouseState.MiddleButton**](xref:Microsoft.Xna.Framework.Input.MouseState.MiddleButton). > - `Right`: Maps to [**MouseState.RightButton**](xref:Microsoft.Xna.Framework.Input.MouseState.RightButton). @@ -238,7 +238,7 @@ public enum MouseButton To manage mouse input effectively, we need to track both current and previous states, as well as provide easy access to mouse position, scroll wheel values, and button states. The `MouseInfo` class will encapsulate all of this functionality, making it easier to: -- Track current and previous mouse states. +- Track current and previous mouse states. - Track the mouse position and detect movement. - Track scroll wheel changes. - Detect when mouse buttons are pressed or released @@ -359,7 +359,7 @@ public MouseInfo() } ``` -The constructor +The constructor: - Creates an empty state for `PreviousState` since there is no previous input yet. - Gets the current mouse state as our starting point for `CurrentState`. @@ -488,6 +488,7 @@ These methods serve two distinct purposes. For checking continuous states: - `IsKeyUp`: Returns true as long as a key is not being pressed. And for detecting state changes: + - `WasKeyJustPressed`: Returns true only on the frame when a key changes from up-to-down. - `WasKeyJustReleased`: Returns true only on the frame when a key changes from down-to-up. @@ -724,7 +725,7 @@ And for detecting state changes: Finally, we'll add methods for controlling gamepad vibration: ```cs - /// +/// /// Sets the vibration for all motors of this gamepad. /// /// The strength of the vibration from 0.0f (none) to 1.0f (full). @@ -752,7 +753,6 @@ The vibration methods provide control over the gamepad's haptic feedback: > [!TIP] > When setting vibration, you can specify both the strength (`0.0f` to `1.0f`) and duration. The vibration will automatically stop after the specified time has elapsed, so you don't need to manage stopping it manually. - That's it for the `GamePadInfo` class. Next, let's create the actual input manager. ## The InputManager Class @@ -761,7 +761,6 @@ Now that we have classes to handle keyboard, mouse, and gamepad input individual In the *Input* directory of the *MonoGameLibrary* project, add a new file named *InputManager.cs* with this initial structure: - ```cs using Microsoft.Xna.Framework; @@ -840,7 +839,6 @@ public static void Update(GameTime gameTime) > [!TIP] > 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. - ### Implementing the InputManager Class 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: @@ -935,9 +933,9 @@ In this chapter, you learned how to: - Detect the difference between continuous and single-frame input states. - Create classes to manage different input devices. - Build a centralized `InputManager` to coordinate all input handling that is: - - Reusable across different game projects - - Easy to maintain and extend - - Consistent across different input devices + - Reusable across different game projects + - Easy to maintain and extend + - Consistent across different input devices ## Test Your Knowledge @@ -945,7 +943,7 @@ In this chapter, you learned how to:
Question 1 Answer - + > "Down" checks if an input is currently being held, returning true every frame while held. "Just pressed" only returns true on the first frame when the input changes from up to down, requiring comparison between current and previous states.

@@ -953,7 +951,7 @@ In this chapter, you learned how to:
Question 2 Answer - + > Tracking both states allows us to detect when input changes occur by comparing the current frame's state with the previous frame's state. This is essential for implementing "just pressed" and "just released" checks.

@@ -961,6 +959,6 @@ In this chapter, you learned how to:
Question 3 Answer - + > The `InputManager` centralizes all input handling, automatically tracks states between frames, and provides a consistent API across different input devices. This makes the code more organized, reusable, and easier to maintain.

\ No newline at end of file From 577b0e8881f2fcf62d215122de06b22754cd98b6 Mon Sep 17 00:00:00 2001 From: Christopher Whitley <103014489+AristurtleDev@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:12:39 -0500 Subject: [PATCH 09/11] Add statements about gamepad connection --- .../tutorials/building_2d_games/10_input_management/index.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/articles/tutorials/building_2d_games/10_input_management/index.md b/articles/tutorials/building_2d_games/10_input_management/index.md index 41ccfbcf..85339e32 100644 --- a/articles/tutorials/building_2d_games/10_input_management/index.md +++ b/articles/tutorials/building_2d_games/10_input_management/index.md @@ -526,9 +526,10 @@ That's it for the `MouseInfo` class, next we'll move onto gamepad input. ## The GamePadInfo Class -To manage gamepad input effectively, we need to track both current and previous states, as well as provide easy access to the thumbstick values, trigger values, and button states. The `GamePadInfo` class will encapsulate all of this functionality, making it easier to: +To manage gamepad input effectively, we need to track both current and previous states, is the gamepad still connected, as well as provide easy access to the thumbstick values, trigger values, and button states. The `GamePadInfo` class will encapsulate all of this functionality, making it easier to: -- Track current and previous mouse states. +- Track current and previous gamepad states. +- Check if the gamepad is still connected. - Track the position of the left and right thumbsticks. - Check the values of the left and right triggers. - Detect when gamepad buttons are pressed or released. From bea1cc76bd554e0572febc80b8a2b66ee29a7b12 Mon Sep 17 00:00:00 2001 From: Christopher Whitley <103014489+AristurtleDev@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:21:06 -0500 Subject: [PATCH 10/11] Add position deltas for mouse info --- .../10_input_management/index.md | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/articles/tutorials/building_2d_games/10_input_management/index.md b/articles/tutorials/building_2d_games/10_input_management/index.md index 85339e32..cf2dde16 100644 --- a/articles/tutorials/building_2d_games/10_input_management/index.md +++ b/articles/tutorials/building_2d_games/10_input_management/index.md @@ -320,7 +320,37 @@ These properties provide different ways to work with the cursor position: - `Position`: Gets/sets the cursor position as a [**Point**](xref:Microsoft.Xna.Framework.Point). - `X`: Gets/sets just the horizontal position. - `Y`: Gets/sets just the vertical position. -- `WasMoved`: Indicates if the cursor moved this frame. + +Next, we'll add properties for determining if the mouse cursor moved between game frames and if so how much: + +```cs +/// +/// Gets the difference in the mouse cursor position between the previous and current frame. +/// +public Point PositionDelta => CurrentState.Position - PreviousState.Position; + +/// +/// Gets the difference in the mouse cursor x-position between the previous and current frame. +/// +public int XDelta => CurrentState.X - PreviousState.X; + +/// +/// Gets the difference in the mouse cursor y-position between the previous and current frame. +/// +public int YDelta => CurrentState.Y - PreviousState.Y; + +/// +/// Gets a value that indicates if the mouse cursor moved between the previous and current frames. +/// +public bool WasMoved => PositionDelta != Point.Zero; +``` + +The properties provide different ways of detecting mouse movement between frames: + +- `PositionDelta`: Gets how much the cursor moved between frames as a [**Point**](xref:Microsoft.Xna.Framework.Point). +- `XDelta`: Gets how much the cursor moved horizontally between frames. +- `YDelta`: Gets how much the cursor moved vertically between frames. +- `WasMoved`: Indicates if the cursor moved between frames. Finally, we'll add properties for handling the scroll wheel: From 304200fd745400da522660e468a3c3b5ff46eb68 Mon Sep 17 00:00:00 2001 From: Christopher Whitley <103014489+AristurtleDev@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:21:59 -0500 Subject: [PATCH 11/11] Update list to include new delta properties --- .../tutorials/building_2d_games/10_input_management/index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/articles/tutorials/building_2d_games/10_input_management/index.md b/articles/tutorials/building_2d_games/10_input_management/index.md index cf2dde16..d198da67 100644 --- a/articles/tutorials/building_2d_games/10_input_management/index.md +++ b/articles/tutorials/building_2d_games/10_input_management/index.md @@ -239,7 +239,8 @@ public enum MouseButton To manage mouse input effectively, we need to track both current and previous states, as well as provide easy access to mouse position, scroll wheel values, and button states. The `MouseInfo` class will encapsulate all of this functionality, making it easier to: - Track current and previous mouse states. -- Track the mouse position and detect movement. +- Track the mouse position. +- Check the change in mouse position between frames and if it was moved. - Track scroll wheel changes. - Detect when mouse buttons are pressed or released - Check if mouse buttons are being held down