Tutorial 10: Handling Keyboard Input

CNA Tutorial Series  ·  Beginner

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:

MethodReturnsDescription
IsKeyDown(Keys key)boolTrue if the key is currently held down
IsKeyUp(Keys key)boolTrue 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:

CategoryKeys values
LettersKeys::A through Keys::Z
DigitsKeys::D0Keys::D9 (top row); Keys::NumPad0Keys::NumPad9
Arrow keysKeys::Left, Keys::Right, Keys::Up, Keys::Down
ActionKeys::Space, Keys::Enter, Keys::Escape, Keys::Tab, Keys::Back
ModifiersKeys::LeftShift, Keys::RightShift, Keys::LeftControl, Keys::RightControl, Keys::LeftAlt, Keys::RightAlt
FunctionKeys::F1 through Keys::F12
NavigationKeys::Home, Keys::End, Keys::PageUp, Keys::PageDown, Keys::Insert, Keys::Delete
SymbolsKeys::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.