Input Handling in MonoGame
Input Handling in MonoGame
MonoGame provides a straightforward polling input API: each frame you call a static method to capture the current hardware state, then query that snapshot. This article covers all four input sources — keyboard, mouse, gamepad, and touch — and shows how to build a reusable InputManager that tracks both the current and previous frame states for reliable “just pressed” detection.
💡 MonoGame’s input API is polling-based, not event-based. Always call GetState() once per frame at the top of Update() and store the result, rather than calling it multiple times.
Keyboard Input
Keyboard.GetState() returns a KeyboardState struct that is a snapshot of every key on the keyboard at that instant. The two most useful methods are:
IsKeyDown(Keys key)— returnstruewhile the key is heldIsKeyUp(Keys key)— returnstruewhile the key is not held
// In Update()
KeyboardState current = Keyboard.GetState();
// Held — true every frame the key is down
if (current.IsKeyDown(Keys.Space))
player.Jump();
// Held — typical WASD movement
float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
if (current.IsKeyDown(Keys.A)) player.Position.X -= 200f * delta;
if (current.IsKeyDown(Keys.D)) player.Position.X += 200f * delta;
if (current.IsKeyDown(Keys.W)) player.Position.Y -= 200f * delta;
if (current.IsKeyDown(Keys.S)) player.Position.Y += 200f * delta;
// Getting all currently pressed keys
Keys[] pressedKeys = current.GetPressedKeys();
foreach (Keys k in pressedKeys)
Console.WriteLine($"Pressed: {k}");
Detecting a key that was just pressed (i.e. down this frame but up last frame) requires comparing two frames. Store the previous state:
private KeyboardState _previousKeyboard;
private KeyboardState _currentKeyboard;
protected override void Update(GameTime gameTime)
{
_previousKeyboard = _currentKeyboard;
_currentKeyboard = Keyboard.GetState();
// Just pressed: down NOW, up LAST frame
if (_currentKeyboard.IsKeyDown(Keys.Enter)
&& _previousKeyboard.IsKeyUp(Keys.Enter))
{
ConfirmSelection();
}
// Just released: up NOW, down LAST frame
if (_currentKeyboard.IsKeyUp(Keys.Space)
&& _previousKeyboard.IsKeyDown(Keys.Space))
{
EndCharge();
}
base.Update(gameTime);
}
⚠️ Do not compare KeyboardState structs with == for just-pressed detection — use IsKeyDown / IsKeyUp on both the current and previous states as shown above.
Mouse Input
Mouse.GetState() returns a MouseState struct containing the cursor position, all button states, and the scroll wheel delta.
private MouseState _previousMouse;
private MouseState _currentMouse;
protected override void Update(GameTime gameTime)
{
_previousMouse = _currentMouse;
_currentMouse = Mouse.GetState();
// Cursor position in window coordinates
int mouseX = _currentMouse.X;
int mouseY = _currentMouse.Y;
Vector2 mousePos = new Vector2(mouseX, mouseY);
// Left button held
if (_currentMouse.LeftButton == ButtonState.Pressed)
Shoot(mousePos);
// Right button just pressed
bool rightJustPressed =
_currentMouse.RightButton == ButtonState.Pressed &&
_previousMouse.RightButton == ButtonState.Released;
if (rightJustPressed)
OpenContextMenu(mousePos);
// Scroll wheel — value is cumulative; take the delta between frames
int scrollDelta = _currentMouse.ScrollWheelValue
- _previousMouse.ScrollWheelValue;
if (scrollDelta > 0) ZoomIn();
if (scrollDelta < 0) ZoomOut();
base.Update(gameTime);
}
You can also reposition the cursor programmatically:
// Warp cursor to centre of window (useful for FPS-style camera look)
int centreX = GraphicsDevice.Viewport.Width / 2;
int centreY = GraphicsDevice.Viewport.Height / 2;
Mouse.SetPosition(centreX, centreY);
// Hide cursor for full-screen games
IsMouseVisible = false;
💡 Use the scroll wheel delta (current minus previous) rather than the raw cumulative value, which grows unboundedly during a session.
Gamepad / Controller Input
MonoGame supports up to four gamepads via the XInput API on Windows (and the equivalent on other platforms). Call GamePad.GetState(PlayerIndex) to retrieve a GamePadState for a specific player slot.
private GamePadState _previousPad;
private GamePadState _currentPad;
protected override void Update(GameTime gameTime)
{
_previousPad = _currentPad;
_currentPad = GamePad.GetState(PlayerIndex.One);
// Only process input if a controller is connected
if (!_currentPad.IsConnected) return;
float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
// Left thumbstick — Vector2 with components in [-1, 1]
Vector2 leftStick = _currentPad.ThumbSticks.Left;
// Apply a small dead zone to ignore stick drift
const float DeadZone = 0.15f;
if (leftStick.Length() > DeadZone)
{
// Y axis is inverted relative to screen space
player.Position.X += leftStick.X * 200f * delta;
player.Position.Y += leftStick.Y * -200f * delta;
}
// Right thumbstick for camera
Vector2 rightStick = _currentPad.ThumbSticks.Right;
if (rightStick.Length() > DeadZone)
camera.Rotate(rightStick.X * 90f * delta);
// Triggers — float in [0, 1]
float leftTrigger = _currentPad.Triggers.Left;
float rightTrigger = _currentPad.Triggers.Right;
if (rightTrigger > 0.5f) Shoot();
// Face buttons
if (_currentPad.Buttons.A == ButtonState.Pressed
&& _previousPad.Buttons.A == ButtonState.Released)
{
Jump(); // just pressed
}
// Shoulder buttons
if (_currentPad.Buttons.LeftShoulder == ButtonState.Pressed)
Dash(Direction.Left);
base.Update(gameTime);
}
To make a controller rumble (where supported by the platform):
// Set vibration — leftMotor and rightMotor are floats in [0, 1]
// Left motor = low-frequency rumble; right motor = high-frequency
GamePad.SetVibration(PlayerIndex.One, leftMotor: 0.8f, rightMotor: 0.3f);
// Stop vibration after a short duration using a timer
private float _rumbleTimer = 0f;
protected override void Update(GameTime gameTime)
{
if (_rumbleTimer > 0f)
{
_rumbleTimer -= (float)gameTime.ElapsedGameTime.TotalSeconds;
if (_rumbleTimer <= 0f)
GamePad.SetVibration(PlayerIndex.One, 0f, 0f); // stop
}
}
public void TriggerRumble(float duration = 0.25f)
{
GamePad.SetVibration(PlayerIndex.One, 0.7f, 0.4f);
_rumbleTimer = duration;
}
💡 Always stop vibration explicitly when done — the motors stay on until you set them to zero or the game closes.
Building a Reusable InputManager
Rather than scattering state storage across every class, centralise all input in a single InputManager that is updated once per frame and queried everywhere:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace MySpriteGame;
/// <summary>
/// Centralised input manager. Call Update() once per frame, then query
/// IsKeyDown, IsKeyJustPressed, etc. from anywhere.
/// </summary>
public class InputManager
{
// Keyboard
private KeyboardState _kPrev;
private KeyboardState _kCurr;
// Mouse
private MouseState _mPrev;
private MouseState _mCurr;
// Gamepad (player one)
private GamePadState _gPrev;
private GamePadState _gCurr;
public Vector2 MousePosition => new Vector2(_mCurr.X, _mCurr.Y);
public Vector2 MouseDelta => new Vector2(_mCurr.X - _mPrev.X, _mCurr.Y - _mPrev.Y);
public int ScrollDelta => _mCurr.ScrollWheelValue - _mPrev.ScrollWheelValue;
public bool GamePadConnected => _gCurr.IsConnected;
// -----------------------------------------------------------------------
// Update — call exactly once per frame at the start of Game.Update()
// -----------------------------------------------------------------------
public void Update()
{
_kPrev = _kCurr;
_kCurr = Keyboard.GetState();
_mPrev = _mCurr;
_mCurr = Mouse.GetState();
_gPrev = _gCurr;
_gCurr = GamePad.GetState(PlayerIndex.One);
}
// -----------------------------------------------------------------------
// Keyboard helpers
// -----------------------------------------------------------------------
public bool IsKeyDown(Keys key) => _kCurr.IsKeyDown(key);
public bool IsKeyUp(Keys key) => _kCurr.IsKeyUp(key);
public bool IsKeyJustPressed(Keys key) => _kCurr.IsKeyDown(key) && _kPrev.IsKeyUp(key);
public bool IsKeyJustReleased(Keys key)=> _kCurr.IsKeyUp(key) && _kPrev.IsKeyDown(key);
// -----------------------------------------------------------------------
// Mouse helpers
// -----------------------------------------------------------------------
public bool IsMouseButtonDown(MouseButton btn) => GetButtonState(_mCurr, btn) == ButtonState.Pressed;
public bool IsMouseButtonJustPressed(MouseButton btn) => GetButtonState(_mCurr, btn) == ButtonState.Pressed
&& GetButtonState(_mPrev, btn) == ButtonState.Released;
private static ButtonState GetButtonState(MouseState ms, MouseButton btn) => btn switch
{
MouseButton.Left => ms.LeftButton,
MouseButton.Right => ms.RightButton,
MouseButton.Middle => ms.MiddleButton,
_ => ButtonState.Released
};
// -----------------------------------------------------------------------
// Gamepad helpers
// -----------------------------------------------------------------------
public bool IsButtonDown(Buttons btn) => _gCurr.IsButtonDown(btn);
public bool IsButtonJustPressed(Buttons btn) => _gCurr.IsButtonDown(btn) && !_gPrev.IsButtonDown(btn);
public Vector2 LeftStick => _gCurr.ThumbSticks.Left;
public Vector2 RightStick => _gCurr.ThumbSticks.Right;
public float LeftTrigger => _gCurr.Triggers.Left;
public float RightTrigger => _gCurr.Triggers.Right;
}
/// <summary>Convenience enum for mouse buttons.</summary>
public enum MouseButton { Left, Right, Middle }
Use it in Game1.cs like this:
private readonly InputManager _input = new InputManager();
protected override void Update(GameTime gameTime)
{
// Always update input first, before any other Update logic
_input.Update();
if (_input.IsKeyDown(Keys.Escape)) Exit();
float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
float speed = 200f;
// Keyboard or gamepad movement — whichever is active
Vector2 move = Vector2.Zero;
if (_input.IsKeyDown(Keys.A) || _input.IsKeyDown(Keys.Left)) move.X -= 1f;
if (_input.IsKeyDown(Keys.D) || _input.IsKeyDown(Keys.Right)) move.X += 1f;
if (_input.IsKeyDown(Keys.W) || _input.IsKeyDown(Keys.Up)) move.Y -= 1f;
if (_input.IsKeyDown(Keys.S) || _input.IsKeyDown(Keys.Down)) move.Y += 1f;
if (_input.GamePadConnected && _input.LeftStick.Length() > 0.15f)
move = _input.LeftStick * new Vector2(1f, -1f); // invert Y
if (move != Vector2.Zero)
{
move.Normalize(); // prevent diagonal speed boost
_playerPosition += move * speed * delta;
}
// Jump on A button or Space — just pressed, not held
if (_input.IsButtonJustPressed(Buttons.A) || _input.IsKeyJustPressed(Keys.Space))
Jump();
base.Update(gameTime);
}
💡 Normalise diagonal movement: if a player holds both W and D simultaneously, the combined vector has a length of √2 ≈ 1.41, making diagonal movement 41% faster than axis-aligned movement. Calling move.Normalize() fixes this.
Action Mapping
For games with rebindable controls, map abstract actions to keys rather than hard-coding keys everywhere. A simple dictionary approach:
using Microsoft.Xna.Framework.Input;
using System.Collections.Generic;
namespace MySpriteGame;
public enum GameAction
{
MoveLeft, MoveRight, MoveUp, MoveDown,
Jump, Attack, Pause
}
public class ActionMap
{
private readonly Dictionary<GameAction, Keys> _keyBindings = new()
{
[GameAction.MoveLeft] = Keys.A,
[GameAction.MoveRight] = Keys.D,
[GameAction.MoveUp] = Keys.W,
[GameAction.MoveDown] = Keys.S,
[GameAction.Jump] = Keys.Space,
[GameAction.Attack] = Keys.J,
[GameAction.Pause] = Keys.Escape,
};
private readonly Dictionary<GameAction, Buttons> _padBindings = new()
{
[GameAction.Jump] = Buttons.A,
[GameAction.Attack] = Buttons.X,
[GameAction.Pause] = Buttons.Start,
};
public void Rebind(GameAction action, Keys newKey)
=> _keyBindings[action] = newKey;
public bool IsActionDown(GameAction action, InputManager input)
{
bool keyHeld = _keyBindings.TryGetValue(action, out Keys key)
&& input.IsKeyDown(key);
bool padHeld = _padBindings.TryGetValue(action, out Buttons btn)
&& input.IsButtonDown(btn);
return keyHeld || padHeld;
}
public bool IsActionJustPressed(GameAction action, InputManager input)
{
bool keyPressed = _keyBindings.TryGetValue(action, out Keys key)
&& input.IsKeyJustPressed(key);
bool padPressed = _padBindings.TryGetValue(action, out Buttons btn)
&& input.IsButtonJustPressed(btn);
return keyPressed || padPressed;
}
}
Next Steps
- Extend
InputManagerwith support for multiple gamepads using aPlayerIndexparameter - Persist key bindings to a JSON file so players can rebind controls across sessions
- Add touch input support via
TouchPanel.GetState()for mobile and tablet targets - Read the First Sprite Tutorial to put movement code into a working game