Keyboard Input

Relevant official examples: keyboard_input, keyboard_input_events.


This page shows how to handle keyboard keys being pressed and released.

Note: Command Key on Mac corresponds to the Super/Windows Key on PC.

Similar to mouse buttons, keyboard input is available as a ButtonInput resource, events, and run conditions (see list). Use whichever pattern feels most appropriate to your use case.

Checking Key State

Most commonly for games, you might be interested in specific known keys and detecting when they are pressed or released. You can check specific keys using the ButtonInput<KeyCode> resource.

  • Use .pressed(…)/.released(…) to check if a key is being held down
    • These return true every frame, for as long as the key is in the respective state.
  • Use .just_pressed(…)/.just_released(…) to detect the actual press/release
    • These return true only on the frame update when the press/release happened.
fn keyboard_input(
    keys: Res<ButtonInput<KeyCode>>,
) {
    if keys.just_pressed(KeyCode::Space) {
        // Space was pressed
    }
    if keys.just_released(KeyCode::ControlLeft) {
        // Left Ctrl was released
    }
    if keys.pressed(KeyCode::KeyW) {
        // W is being held down
    }
    // we can check multiple at once with `.any_*`
    if keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]) {
        // Either the left or right shift are being held down
    }
    if keys.any_just_pressed([KeyCode::Delete, KeyCode::Backspace]) {
        // Either delete or backspace was just pressed
    }
}

To iterate over any keys that are currently held, or that have been pressed/released:

fn keyboard_iter(
    keys: Res<ButtonInput<KeyCode>>,
) {
    for key in keys.get_pressed() {
        println!("{:?} is currently held down", key);
    }
    for key in keys.get_just_pressed() {
        println!("{:?} was pressed", key);
    }
    for key in keys.get_just_released() {
        println!("{:?} was released", key);
    }
}

Run Conditions

Another workflow is to add run conditions to your systems, so that they only run when the appropriate inputs happen.

It is highly recommended you write your own run conditions, so that you can check for whatever you want, support configurable bindings, etc…

For prototyping, Bevy offers some built-in run conditions:

use bevy::input::common_conditions::*;

app.add_systems(Update, (
    handle_jump
        .run_if(input_just_pressed(KeyCode::Space)),
    handle_shooting
        .run_if(input_pressed(KeyCode::Enter)),
));

Keyboard Events

To get all keyboard activity, you can use KeyboardInput events:

fn keyboard_events(
    mut evr_kbd: EventReader<KeyboardInput>,
) {
    for ev in evr_kbd.read() {
        match ev.state {
            ButtonState::Pressed => {
                println!("Key press: {:?} ({:?})", ev.key_code, ev.logical_key);
            }
            ButtonState::Released => {
                println!("Key release: {:?} ({:?})", ev.key_code, ev.logical_key);
            }
        }
    }
}

Physical KeyCode vs. Logical Key

When a key is pressed, the event contains two important pieces of information:

  • The KeyCode, which always represents a specific key on the keyboard, regardless of the OS layout or language settings.
  • The Key, which contains the logical meaning of the key as interpreted by the OS.

When you want to implement gameplay mechanics, you want to use the KeyCode. This will give you reliable keybindings that always work, including for multilingual users with multiple keyboard layouts configured in their OS.

When you want to implement text/character input, you want to use the Key. This can give you Unicode characters that you can append to your text string and will allow your users to type just like they do in other applications.

If you'd like to handle special function keys or media keys on keyboards that have them, that can also be done via the logical Key.

Text Input

Here is a simple example of how to implement text input into a string (here stored as a local).

use bevy::input::ButtonState;
use bevy::input::keyboard::{Key, KeyboardInput};

fn text_input(
    mut evr_kbd: EventReader<KeyboardInput>,
    mut string: Local<String>,
) {
    for ev in evr_kbd.read() {
        // We don't care about key releases, only key presses
        if ev.state == ButtonState::Released {
            continue;
        }
        match &ev.logical_key {
            // Handle pressing Enter to finish the input
            Key::Enter => {
                println!("Text input: {}", &*string);
                string.clear();
            }
            // Handle pressing Backspace to delete last char
            Key::Backspace => {
                string.pop();
            }
            // Handle key presses that produce text characters
            Key::Character(input) => {
                // Ignore any input that contains control (special) characters
                if input.chars().any(|c| c.is_control()) {
                    continue;
                }
                string.push_str(&input);
            }
            _ => {}
        }
    }
}

Note how we implement special handling for keys like Backspace and Enter. You can easily add special handling for other keys that make sense in your application, like arrow keys or the Escape key.

Keys that produce useful characters for our text come in as small Unicode strings. It is possible that there might be more than one char per keypress in some languages.

Note: To support text input for international users who use languages with complex scripts (such as East Asian languages), or users who use assistive methods like handwriting recognition, you also need to support IME input, in addition to keyboard input.

Keyboard Focus

If you are doing advanced things like caching state to detect multi-key sequences or combinations of keys, you might end up in an inconsistent state if the Bevy OS window loses focus in the middle of keyboard input, such as with Alt-Tab or similar OS window switching mechanisms.

If you are doing such things and you think your algorithm might be getting stuck, Bevy offers a KeyboardFocusLost event to let you know when you should reset your state.

use bevy::input::keyboard::KeyboardFocusLost;

fn detect_special_sequence(
    mut evr_focus_lost: EventReader<KeyboardFocusLost>,
    mut remembered_keys: Local<Vec<KeyCode>>,
) {
    // Imagine we need to remeber a sequence of keypresses
    // for some special gameplay reason.
    // TODO: implement that; store state in `remembered_keys`

    // But it might go wrong if the user Alt-Tabs, we need to reset
    if !evr_focus_lost.is_empty() {
        remembered_keys.clear();
        evr_focus_lost.clear();
    }
}