Tutorial 10: Handling Keyboard Input
Keyboard::GetState()
The entire keyboard API is based on polling a snapshot of the current key state. Call Keyboard::GetState() once per Update() call to get the current state:
#include "Microsoft/Xna/Framework/Input/Keyboard.hpp"
#include "Microsoft/Xna/Framework/Input/Keys.hpp"
using namespace Microsoft::Xna::Framework::Input;
void MyGame::Update(GameTime& gameTime) {
KeyboardState kb = Keyboard::GetState();
// Use 'kb' to check key states for this frame
}
GetState() is a cheap call — it reads from an internal buffer that SDL updates each frame. Call it at the start of Update() and reuse the result throughout.
KeyboardState Struct
KeyboardState is a value type (passed by value, not pointer). It represents a frozen snapshot of the keyboard state at the moment GetState() was called. It has two main query methods:
| Method | Returns | Description |
|---|---|---|
IsKeyDown(Keys key) | bool | True if the key is currently held down |
IsKeyUp(Keys key) | bool | True if the key is currently released |
GetPressedKeys() | std::vector<Keys> | All keys currently pressed |
To detect press-and-hold (movement keys), use IsKeyDown(). To detect a single-press event (jump, pause), compare the current and previous state.
IsKeyDown and IsKeyUp
Use IsKeyDown() for continuous actions like movement:
void MyGame::Update(GameTime& gameTime) {
float dt = static_cast<float>(gameTime.getElapsedGameTime().getTotalSeconds());
KeyboardState kb = Keyboard::GetState();
const float speed = 200.0f; // pixels per second
if (kb.IsKeyDown(Keys::W) || kb.IsKeyDown(Keys::Up)) {
playerPos_.Y -= speed * dt;
}
if (kb.IsKeyDown(Keys::S) || kb.IsKeyDown(Keys::Down)) {
playerPos_.Y += speed * dt;
}
if (kb.IsKeyDown(Keys::A) || kb.IsKeyDown(Keys::Left)) {
playerPos_.X -= speed * dt;
}
if (kb.IsKeyDown(Keys::D) || kb.IsKeyDown(Keys::Right)) {
playerPos_.X += speed * dt;
}
if (kb.IsKeyDown(Keys::Escape)) {
Exit();
}
}
IsKeyUp() is the logical inverse of IsKeyDown(). It is most useful for detecting that a specific key is not held, but you will typically not use it directly — use the press/release pattern instead.
Detecting a Key Press (was up, now down)
A "key press" event happens on the first frame a key goes from released to held. To detect this you need both the current frame's state and the previous frame's state:
// In the class definition:
KeyboardState prevKb_; // saved from last frame
// In Update():
void MyGame::Update(GameTime& gameTime) {
KeyboardState kb = Keyboard::GetState();
// Key press: was up last frame, is down this frame
bool jumpPressed = kb.IsKeyDown(Keys::Space) && prevKb_.IsKeyUp(Keys::Space);
bool pausePressed = kb.IsKeyDown(Keys::P) && prevKb_.IsKeyUp(Keys::P);
bool firePressed = kb.IsKeyDown(Keys::LeftControl) && prevKb_.IsKeyUp(Keys::LeftControl);
if (jumpPressed && isGrounded_) {
velocity_.Y = -500.0f; // apply jump impulse
isGrounded_ = false;
}
if (pausePressed) {
isPaused_ = !isPaused_;
}
if (firePressed) {
spawnBullet();
}
// Save state for next frame
prevKb_ = kb;
}
This "was up, now down" pattern is the standard XNA approach for single-event input detection. It guarantees the action fires exactly once per key press, no matter how long the key is held.
Detecting a Key Release
A key release fires on the first frame a key goes from held to released — the inverse of a key press:
// Key release: was down last frame, is up this frame
bool jumpReleased = prevKb_.IsKeyDown(Keys::Space) && kb.IsKeyUp(Keys::Space);
if (jumpReleased && isJumping_) {
// Cut the jump short (variable-height jump)
if (velocity_.Y < -200.0f) velocity_.Y = -200.0f;
}
Variable-height jumps (tap for low jump, hold for high jump) use both key press and key release detection.
Modifier Keys
Modifier keys (Shift, Ctrl, Alt) are regular keys in CNA and can be queried like any other key:
bool ctrlHeld = kb.IsKeyDown(Keys::LeftControl) || kb.IsKeyDown(Keys::RightControl);
bool shiftHeld = kb.IsKeyDown(Keys::LeftShift) || kb.IsKeyDown(Keys::RightShift);
bool altHeld = kb.IsKeyDown(Keys::LeftAlt) || kb.IsKeyDown(Keys::RightAlt);
// Ctrl+S to save
bool savePressed = ctrlHeld && kb.IsKeyDown(Keys::S) && prevKb_.IsKeyUp(Keys::S);
if (savePressed) saveGame();
// Shift+direction for sprint
float moveSpeed = shiftHeld ? 400.0f : 200.0f;
if (kb.IsKeyDown(Keys::D)) playerPos_.X += moveSpeed * dt;
Keys Enum Reference
The Keys enum contains all keyboard keys. Selected values:
| Category | Keys values |
|---|---|
| Letters | Keys::A through Keys::Z |
| Digits | Keys::D0–Keys::D9 (top row); Keys::NumPad0–Keys::NumPad9 |
| Arrow keys | Keys::Left, Keys::Right, Keys::Up, Keys::Down |
| Action | Keys::Space, Keys::Enter, Keys::Escape, Keys::Tab, Keys::Back |
| Modifiers | Keys::LeftShift, Keys::RightShift, Keys::LeftControl, Keys::RightControl, Keys::LeftAlt, Keys::RightAlt |
| Function | Keys::F1 through Keys::F12 |
| Navigation | Keys::Home, Keys::End, Keys::PageUp, Keys::PageDown, Keys::Insert, Keys::Delete |
| Symbols | Keys::OemPlus, Keys::OemMinus, Keys::OemComma, Keys::OemPeriod |
The full list is in include/Microsoft/Xna/Framework/Input/Keys.hpp.
Complete keyboard input demo
class KeyboardDemo final : public Game {
public:
KeyboardDemo() : graphics_(this) {
graphics_.setPreferredBackBufferWidth(800);
graphics_.setPreferredBackBufferHeight(600);
}
protected:
void LoadContent() override {
spriteBatch_ = std::make_unique<SpriteBatch>(getGraphicsDeviceProperty());
pixel_ = std::make_unique<Texture2D>(getGraphicsDeviceProperty(), 1, 1);
Color w = Color::White;
pixel_->SetData(&w, 1);
}
void Update(GameTime& gameTime) override {
float dt = static_cast<float>(gameTime.getElapsedGameTime().getTotalSeconds());
KeyboardState kb = Keyboard::GetState();
// WASD / Arrow movement
const float speed = 250.0f;
if (kb.IsKeyDown(Keys::W) || kb.IsKeyDown(Keys::Up)) pos_.Y -= speed * dt;
if (kb.IsKeyDown(Keys::S) || kb.IsKeyDown(Keys::Down)) pos_.Y += speed * dt;
if (kb.IsKeyDown(Keys::A) || kb.IsKeyDown(Keys::Left)) pos_.X -= speed * dt;
if (kb.IsKeyDown(Keys::D) || kb.IsKeyDown(Keys::Right)) pos_.X += speed * dt;
// Clamp to screen
pos_.X = std::clamp(pos_.X, 0.0f, 800.0f - 40.0f);
pos_.Y = std::clamp(pos_.Y, 0.0f, 600.0f - 40.0f);
// Space to change color (single press)
if (kb.IsKeyDown(Keys::Space) && prevKb_.IsKeyUp(Keys::Space)) {
colorIndex_ = (colorIndex_ + 1) % 5;
}
// Escape to quit
if (kb.IsKeyDown(Keys::Escape)) Exit();
prevKb_ = kb;
}
void Draw(const GameTime&) override {
static const Color colors[] = {
Color::Red, Color::Green, Color::Blue, Color::Yellow, Color::Purple
};
auto& device = getGraphicsDeviceProperty();
device.Clear(Color::Black);
spriteBatch_->Begin();
spriteBatch_->Draw(*pixel_,
Rectangle(static_cast<int>(pos_.X), static_cast<int>(pos_.Y), 40, 40),
colors[colorIndex_]);
spriteBatch_->End();
device.Present();
}
private:
GraphicsDeviceManager graphics_;
std::unique_ptr<SpriteBatch> spriteBatch_;
std::unique_ptr<Texture2D> pixel_;
Vector2 pos_{400.0f, 280.0f};
KeyboardState prevKb_;
int colorIndex_ = 0;
};
Move the square with WASD or arrow keys. Press Space to cycle through colors. Press Escape to quit. In Tutorial 11 you will add mouse support.