Skip to content

Commit ae4a2ab

Browse files
committed
Revise input chapters
This revises the input chapters to move the input checks into their own methods to better organize the code as the documentation goes along
1 parent a925288 commit ae4a2ab

File tree

2 files changed

+153
-103
lines changed
  • articles/tutorials/building_2d_games

2 files changed

+153
-103
lines changed

articles/tutorials/building_2d_games/09_handling_input/index.md

Lines changed: 103 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -52,36 +52,45 @@ Let's implement keyboard controls to move our slime sprite around the screen. O
5252
private const float MOVEMENT_SPEED = 5.0f;
5353
```
5454

55-
2. Next, in [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)), check if the up, down, left, or right arrow keys are pressed, and if any of them are, adjust the slime's position. Add the following just before the call to `base.Update`:
55+
2. Next, add the following method which checks if the up, down, left, or right arrow keys are pressed, and if any of them are, adjusts the slime's position:
5656

5757
```cs
58-
KeyboardState keyboardState = Keyboard.GetState();
59-
60-
if(keyboardState.IsKeyDown(Keys.Up))
61-
{
62-
_slimePosition.Y -= MOVEMENT_SPEED;
63-
}
64-
65-
if(keyboardState.IsKeyDown(Keys.Down))
58+
private void HandleKeyboardInput()
6659
{
67-
_slimePosition.Y += MOVEMENT_SPEED;
68-
}
69-
70-
if(keyboardState.IsKeyDown(Keys.Left))
71-
{
72-
_slimePosition.X -= MOVEMENT_SPEED;
73-
}
74-
75-
if(keyboardState.IsKeyDown(Keys.Right))
76-
{
77-
_slimePosition.X += MOVEMENT_SPEED;
60+
KeyboardState keyboardState = Keyboard.GetState();
61+
62+
if(keyboardState.IsKeyDown(Keys.Up))
63+
{
64+
_slimePosition.Y -= MOVEMENT_SPEED;
65+
}
66+
67+
if(keyboardState.IsKeyDown(Keys.Down))
68+
{
69+
_slimePosition.Y += MOVEMENT_SPEED;
70+
}
71+
72+
if(keyboardState.IsKeyDown(Keys.Left))
73+
{
74+
_slimePosition.X -= MOVEMENT_SPEED;
75+
}
76+
77+
if(keyboardState.IsKeyDown(Keys.Right))
78+
{
79+
_slimePosition.X += MOVEMENT_SPEED;
80+
}
7881
}
7982
```
8083

8184
> [!IMPORTANT]
8285
> Why are we subtracting from the Y position when moving up instead of adding? Recall from [Chapter 05](../05_working_with_textures/index.md#drawing-a-texture) that MonoGame uses a coordinate system where the Y value **increases** moving down. So in order to move **up** the screen, we need to reduce the Y value.
8386

84-
3. Finally, in [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)), update the position of the slime when it is rendered by using the `_slimePosition` value:
87+
3. Next, in [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)), call the new `HandleKeyboardInput` method just before the call to `base.Update`:
88+
89+
```cs
90+
HandleKeyboardInput();
91+
```
92+
93+
4. Finally, in [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)), update the position of the slime when it is rendered by using the `_slimePosition` value:
8594

8695
```cs
8796
_slime.Draw(_spriteBatch, _slimePosition);
@@ -147,22 +156,31 @@ Let's implement mouse controls to move the bat sprite around the screen to the p
147156
>
148157
> We could have just as easily set the bat's position inside the [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent) method after creating the slime, but I wanted to demonstrate the importance of the call order relationship between [**Initialize**](xref:Microsoft.Xna.Framework.Game.Initialize) and [**LoadContent**](xref:Microsoft.Xna.Framework.Game.LoadContent)
149158

150-
3. Next, in [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)), check if the left mouse button is pressed, and if so, adjust the bat's position to the position of the mouse cursor. Add the following just before the call to `base.Update`:
159+
3. Next, add the following method which checks if the left mouse button is pressed, and if so, adjusts the bat's position to the position of the mouse cursor:
151160

152161
```cs
153-
MouseState mouseState = Mouse.GetState();
154-
155-
if(mouseState.LeftButton == ButtonState.Pressed)
162+
private void HandleMouseInput()
156163
{
157-
_batPosition = mouseState.Position.ToVector2();
164+
MouseState mouseState = Mouse.GetState();
165+
166+
if(mouseState.LeftButton == ButtonState.Pressed)
167+
{
168+
_batPosition = mouseState.Position.ToVector2();
169+
}
158170
}
159171
```
160172

161-
4. Finally, in [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)), update the position of the bat when it is rendered by using the `_batPosition` value:
173+
4. Next, in [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)), call the new `HandleMouseInput` method after the `HandleKeyboardInput` call:
162174

163-
```cs
164-
_bat.Draw(_spriteBatch, _batPosition);
165-
```
175+
```cs
176+
HandleMouseInput();
177+
```
178+
179+
5. Finally, in [**Draw**](xref:Microsoft.Xna.Framework.Game.Draw(Microsoft.Xna.Framework.GameTime)), update the position of the bat when it is rendered by using the `_batPosition` value:
180+
181+
```cs
182+
_bat.Draw(_spriteBatch, _batPosition);
183+
```
166184

167185
Running the game now, you can move the bat sprite around by clicking the left mouse button on the game screen and it will move to that position. Try it out!
168186

@@ -350,37 +368,52 @@ if(gamePadState.IsButtonDown(Buttons.A))
350368

351369
Let's implement gamepad controls as an alternative method of moving the slime sprite around. Open the *Game1.cs* file and perform the following:
352370

353-
1. In [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)), use the value of the left thumbstick to adjust the sprite's position. Since the value of the thumbstick is a range between `-1.0f` and `1.0f`, we can multiply those values by the `MOVEMENT_SPEED`. This will make the slime move slower or faster depending on how far in the direction the thumbstick is pushed. Add the following just before the `base.Update` call:
371+
1. Add the following method which takes the value of the left thumbstick and uses it to adjust the sprite's position:
354372

355-
```cs
356-
GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
373+
```cs
374+
private void HandleGamepadInput()
375+
{
376+
GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
357377

358-
_slimePos.X += gamePadState.ThumbSticks.Left.X * MOVEMENT_SPEED;
359-
_slimePos.Y -= gamePadState.ThumbSticks.Left.Y * MOVEMENT_SPEED;
360-
```
378+
_slimePosition.X += gamePadState.ThumbSticks.Left.X * MOVEMENT_SPEED;
379+
_slimePosition.Y -= gamePadState.ThumbSticks.Left.Y * MOVEMENT_SPEED;
380+
}
381+
```
382+
383+
> [!TIP]
384+
> Since the value of the thumbstick is a range between `1.0f` and `1.0f`, we can multiply those values by the `MOVEMENT_SPEED`. This will make the slime move slower or faster depending on how far in the direction the thumbstick is pushed.
385+
386+
2. Next, in [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)), call the new `HandleGamepadInput` method after the `HandleMouseInput` call:
387+
388+
```cs
389+
HandleGamepadInput();
390+
```
361391

362392
Running the game now, you can move the slime sprite around using the left thumbstick on your gamepad. Try it out! Notice that the more you push the thumbstick in a particular direction, the faster the slime moves up to the movement speed cap.
363393

364394
### GamePad Vibration
365395

366396
Another thing we can do with a gamepad is tell it to vibrate. To do this, the [**GamePad**](xref:Microsoft.Xna.Framework.Input.GamePad) class has a [**SetVibration**](xref:Microsoft.Xna.Framework.Input.GamePad.SetVibration(Microsoft.Xna.Framework.PlayerIndex,System.Single,System.Single)) method that requires the player index, and the speed of the left and right vibration motors. The speed can be any value from `0.0f` (no vibration) to `1.0f` (full vibration).
367397

368-
Let's adjust the current code so that when the A button is pressed on the gamepad, it gives a slight speed boost to the slime as it moves. When moving with a speed boost, we can apply vibration to the gamepad as feedback to the player. Update the code you just added to the following:
398+
Let's adjust the current code so that when the A button is pressed on the gamepad, it gives a slight speed boost to the slime as it moves. When moving with a speed boost, we can apply vibration to the gamepad as feedback to the player. Update the `HandleGamePadInput` method to the following:
369399

370400
```cs
371-
GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
372-
373-
if (gamePadState.Buttons.A == ButtonState.Pressed)
401+
private void HandleGamepadInput()
374402
{
375-
_slimePos.X += gamePadState.ThumbSticks.Left.X * 1.5f * MOVEMENT_SPEED;
376-
_slimePos.Y -= gamePadState.ThumbSticks.Left.Y * 1.5f * MOVEMENT_SPEED;
377-
GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f);
378-
}
379-
else
380-
{
381-
_slimePos.X += gamePadState.ThumbSticks.Left.X * MOVEMENT_SPEED;
382-
_slimePos.Y -= gamePadState.ThumbSticks.Left.Y * MOVEMENT_SPEED;
383-
GamePad.SetVibration(PlayerIndex.One, 0.0f, 0.0f);
403+
GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
404+
405+
if (gamePadState.Buttons.A == ButtonState.Pressed)
406+
{
407+
_slimePosition.X += gamePadState.ThumbSticks.Left.X * 1.5f * MOVEMENT_SPEED;
408+
_slimePosition.Y -= gamePadState.ThumbSticks.Left.Y * 1.5f * MOVEMENT_SPEED;
409+
GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f);
410+
}
411+
else
412+
{
413+
_slimePosition.X += gamePadState.ThumbSticks.Left.X * MOVEMENT_SPEED;
414+
_slimePosition.Y -= gamePadState.ThumbSticks.Left.Y * MOVEMENT_SPEED;
415+
GamePad.SetVibration(PlayerIndex.One, 0.0f, 0.0f);
416+
}
384417
}
385418
```
386419

@@ -521,8 +554,8 @@ while(TouchPanel.IsGestureAvailable)
521554
522555
> [!IMPORTANT]
523556
> 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.
524-
>
525-
> 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.
557+
>
558+
> 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.
526559
527560
### Implementing TouchPanel Input (Optional)
528561

@@ -531,17 +564,26 @@ while(TouchPanel.IsGestureAvailable)
531564
532565
Let's implement touch controls to move the bat sprite around the screen to the point that the screen is touched, similar to what we did for mouse controls in the [Implementing Mouse Input](#implementing-mouse-input) section above. Open *Game1.cs* and perform the following:
533566

534-
1. In [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)), check for a touch location and move the bat sprite to that location if a touch occurs. Add the following just before the `base.Update` call:
567+
1. Add the following method which checks for a touch location and moves the bat sprite to that location if a touch occurs:
535568

536-
```cs
537-
TouchCollection touchCollection = TouchPanel.GetState();
569+
```cs
570+
private void HandleTouchInput()
571+
{
572+
TouchCollection touchCollection = TouchPanel.GetState();
573+
574+
if (touchCollection.Count > 0)
575+
{
576+
TouchLocation touchLocation = touchCollection[0];
577+
_batPosition = touchLocation.Position;
578+
}
579+
}
580+
```
538581

539-
if(touchCollection.Count > 0)
540-
{
541-
TouchLocation touchLocation = touchCollection[0];
542-
_batSprite.Position = touchLocation.Position;
543-
}
544-
```
582+
2. Next, in [**Update**](xref:Microsoft.Xna.Framework.Game.Update(Microsoft.Xna.Framework.GameTime)), call the new `HandleTouchInput` method after the `HandleGamePadInput` call:
583+
584+
```cs
585+
HandleTouchInput()
586+
```
545587

546588
If you have your development environment setup for mobile development, running the game now, you can touch the screen to move the bat to the point that was touched.
547589

@@ -626,4 +668,4 @@ In the next chapter, we'll learn how to track previous input states to handle si
626668
> - [**Moved**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState): Touch point moved while maintaining contact
627669
> - [**Released**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState): Contact with the screen ended
628670
> - [**Invalid**](xref:Microsoft.Xna.Framework.Input.Touch.TouchLocationState): Touch data is not valid or tracking was lost
629-
</details><br />
671+
</details><br />

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

Lines changed: 50 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -889,55 +889,63 @@ Let's update the input code in our game now to instead use the `InputManager` cl
889889
InputManager.Update(gameTime);
890890
```
891891

892-
3. Now let's update our game controls to use the `InputManager`. First replace the exit condition code with the following:
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.
893893

894-
```cs
895-
GamePadInfo gamePadOne = InputManager.GamePads[(int)PlayerIndex.One];
896-
897-
if(gamePadOne.WasButtonJustPressed(Buttons.Back) || InputManager.Keyboard.WasKeyJustPressed(Keys.Escape))
898-
{
899-
Exit();
900-
}
901-
```
902-
903-
> [!NOTE]
904-
> 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.
905-
906-
4. Finally, replace the keyboard, mouse, and gamepad movement controls we implemented previously with the following:
894+
4. Finally, update the game controls to use the `InputManager`. Replace the `HandleKeyboardInput`, `HandleMouseInput` and `HandleGamePadInput` methods with the following:
907895

908896
```cs
909-
if(InputManager.Keyboard.IsKeyDown(Keys.Up))
910-
{
911-
_slimePos.Y -= MOVEMENT_SPEED;
912-
}
913-
if (InputManager.Keyboard.IsKeyDown(Keys.Down))
914-
{
915-
_slimePos.Y += MOVEMENT_SPEED;
916-
}
917-
if (InputManager.Keyboard.IsKeyDown(Keys.Left))
897+
private void KeyboardInputCheck()
918898
{
919-
_slimePos.X -= MOVEMENT_SPEED;
920-
}
921-
if (InputManager.Keyboard.IsKeyDown(Keys.Right))
922-
{
923-
_slimePos.X += MOVEMENT_SPEED;
924-
}
925-
926-
if (InputManager.Mouse.WasButtonJustPressed(MouseButton.Left))
927-
{
928-
_batPosition = InputManager.Mouse.Position.ToVector2();
899+
if (InputManager.Keyboard.IsKeyDown(Keys.Esc))
900+
{
901+
Exit();
902+
}
903+
if (InputManager.Keyboard.IsKeyDown(Keys.Up))
904+
{
905+
_slimePosition.Y -= MOVEMENT_SPEED;
906+
}
907+
if (InputManager.Keyboard.IsKeyDown(Keys.Down))
908+
{
909+
_slimePosition.Y += MOVEMENT_SPEED;
910+
}
911+
if (InputManager.Keyboard.IsKeyDown(Keys.Left))
912+
{
913+
_slimePosition.X -= MOVEMENT_SPEED;
914+
}
915+
if (InputManager.Keyboard.IsKeyDown(Keys.Right))
916+
{
917+
_slimePosition.X += MOVEMENT_SPEED;
918+
}
929919
}
930-
931-
if (gamePadOne.IsButtonDown(Buttons.A))
920+
921+
private void HandleMouseInput()
932922
{
933-
_slimePos.X += gamePadOne.LeftThumbStick.X * 1.5f * MOVEMENT_SPEED;
934-
_slimePos.Y -= gamePadOne.LeftThumbStick.Y * 1.5f * MOVEMENT_SPEED;
935-
gamePadOne.SetVibration(1.0f, TimeSpan.FromSeconds(0.5f));
923+
if (InputManager.Mouse.WasButtonJustPressed(MouseButton.Left))
924+
{
925+
_batPosition = InputManager.Mouse.Position.ToVector2();
926+
}
936927
}
937-
else
928+
929+
private void HandleGamepadInput()
938930
{
939-
_slimePos.X += gamePadOne.LeftThumbStick.X * MOVEMENT_SPEED;
940-
_slimePos.Y -= gamePadOne.LeftThumbStick.Y * MOVEMENT_SPEED;
931+
GamePadInfo gamePadOne = InputManager.GamePads[(int)PlayerIndex.One];
932+
933+
if(gamePadOne.IsButtonDown(Buttons.Back))
934+
{
935+
Exit();
936+
}
937+
938+
if (gamePadOne.IsButtonDown(Buttons.A))
939+
{
940+
_slimePosition.X += gamePadOne.LeftThumbStick.X * 1.5f * MOVEMENT_SPEED;
941+
_slimePosition.Y -= gamePadOne.LeftThumbStick.Y * 1.5f * MOVEMENT_SPEED;
942+
gamePadOne.SetVibration(1.0f, TimeSpan.FromSeconds(0.5f));
943+
}
944+
else
945+
{
946+
_slimePosition.X += gamePadOne.LeftThumbStick.X * MOVEMENT_SPEED;
947+
_slimePosition.Y -= gamePadOne.LeftThumbStick.Y * MOVEMENT_SPEED;
948+
}
941949
}
942950
```
943951

@@ -993,4 +1001,4 @@ In this chapter, you learned how to:
9931001
<summary>Question 3 Answer</summary>
9941002

9951003
> 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.
996-
</details><br />
1004+
</details><br />

0 commit comments

Comments
 (0)