Tainted \\ Coders

Bevy Input

Last updated:

There are two ways to handle input in Bevy:

  1. Reacting to the events emitted automatically by Bevy’s input systems
  2. Through a resource like Input, Axis, Touches or Gamepads

Events are more broad and should be used when we care about the entire class of events, such as when we want to react to any keyboard input, not just a specific key:

// Listening to every keyboard event
fn keyboard_system(keyboard_events: EventReader<KeyboardInput>) {
    for event in keyboard_events.iter(){
        println!("KeyCode: {:?} ScanCode {:?}", event.key_code, event.scan_code);
    }
}

Or we can read the input directly using the resource:

fn read_mouse_button(
    mouse: Res<Input<MouseButton>>,
) {
    if mouse.pressed(MouseButton::Left) {
        info!("left mouse currently pressed");
    }

    if mouse.just_pressed(MouseButton::Left) {
        info!("left mouse just pressed");
    }
}

Input<KeyCode> or any Input<T> has convenient functions like pressed, just_pressed, and just_released that make it easier to write game logic that reacts to certain key presses like user input.

The input resource is quite simple:

#[derive(Debug, Clone, Resource, Reflect)]
#[reflect(Default)]
pub struct Input<T: Copy + Eq + Hash + Send + Sync + 'static> {
    // A collection of every button that is currently being pressed.
    pressed: HashSet<T>,
    // A collection of every button that has just been pressed.
    just_pressed: HashSet<T>,
    // A collection of every button that has just been released.
    just_released: HashSet<T>,
}

Internally systems like keyboard_input_system, mouse_button_input_system, and others are calling Input<T>::press or Input<T>::release to trigger the events for that particular input type.

There are a few key input types:

  • MouseButton represents a button on your mouse
  • KeyCode represents the meaning of a key
  • ScanCode represents the scan code of key
  • Touches represents touch based inputs
  • Gamepads represents game controllers
  • GamepadButton represents a single button just like a keycode

Keyboard

There is only one keyboard event we really care about:

pub struct KeyboardInput {
    // The scan code of the key.
    pub scan_code: u32,
    // The key code of the key.
    pub key_code: Option<KeyCode>,
    // The press state of the key.
    pub state: ButtonState,
    // Window that received the input.
    pub window: Entity,
}

KeyCode vs ScanCode

A scancode represents the unique identifier associated with a physical key on a keyboard. When you press a key on a keyboard, it generates an electrical signal that is interpreted by the keyboard’s controller. The controller then assigns a scancode to the pressed key and sends it to the computer. Scancodes are typically hardware-dependent and specific to the keyboard model. They do not convey information about the character or action associated with the key.

A keycode, on the other hand, represents the abstract identifier associated with a specific key on a keyboard, regardless of the physical location or layout. Keycodes are software-defined and are used by the operating system or software to interpret the input from the keyboard. They provide a consistent way to identify keys across different keyboard models and layouts.

You might want to use scancodes instead of keycodes if:

  • Custom Keybinds: This can be useful when players want to assign specific actions to keys that may vary in position across different keyboard layouts
  • Input Authentication: It can be more reliable to obtain low-level information directly from the hardware, helping to verify the legitimacy of the input and detect any potential tampering or unauthorized input injection.
  • Accessibility: Some players may use specialized keyboards or input devices that generate unique scancodes, and reading them directly allows for better compatibility and customization options.

However, it’s important to note that working with scancodes directly can be more complex and less portable across different platforms and keyboard models. In most cases you’ll probably want to use keycodes.

When input is sent by the hardware, their respective input handling systems run and emit events for each kind of input detected.

Modifiers

We can handle modifiers like shift or alt by using Input<T>::any_pressed and set local variables to modify the input:

// This system prints when `Ctrl + Shift + A` is pressed
fn keyboard_input_system(input: Res<Input<KeyCode>>) {
    let shift = input.any_pressed([KeyCode::LShift, KeyCode::RShift]);
    let ctrl = input.any_pressed([KeyCode::LControl, KeyCode::RControl]);

    if ctrl && shift && input.just_pressed(KeyCode::A) {
        info!("Just pressed Ctrl + Shift + A!");
    }
}

Mouse

There are a few mouse events that we can react to, as well as a couple iOS specific events for the touchpad:

pub struct MouseButtonInput {
    // The mouse button assigned to the event.
    pub button: MouseButton,
    // The pressed state of the button.
    pub state: ButtonState,
    // Window that received the input.
    pub window: Entity,
}

pub struct MouseMotion {
    // The change in the position of the pointing device since the last event was sent.
    pub delta: Vec2,
}

pub struct MouseWheel {
    // The mouse scroll unit.
    pub unit: MouseScrollUnit,
    // The horizontal scroll value.
    pub x: f32,
    // The vertical scroll value.
    pub y: f32,
    // Window that received the input.
    pub window: Entity,
}

// iOS based touchpad events
pub struct TouchpadMagnify(pub f32);
pub struct TouchpadRotate(pub f32);

Touch

Touches represent a users interaction with a touchscreen enabled device.

When a user first touches their screen a TouchPhase::Started event is sent with a unique ID. When the user lifts their finger a matching TouchPhase::Ended event is sent with that same ID.

During movement the TouchPhase::Moved is sent, or if the iOS screen changes a TouchPhase::Canceled is sent with the same ID matching system.

pub struct TouchInput {
    // The phase of the touch input.
    pub phase: TouchPhase,
    // The position of the finger on the touchscreen.
    pub position: Vec2,
    // Describes how hard the screen was pressed.
    //
    // May be [`None`] if the platform does not support pressure sensitivity.
    // This feature is only available on **iOS** 9.0+ and **Windows** 8+.
    pub force: Option<ForceTouch>,
    // The unique identifier of the finger.
    pub id: u64,
}

Touches are unique in that they are stored on Touches resource.

pub struct Touches {
    // A collection of every [`Touch`] that is currently being pressed.
    pressed: HashMap<u64, Touch>,
    // A collection of every [`Touch`] that just got pressed.
    just_pressed: HashMap<u64, Touch>,
    // A collection of every [`Touch`] that just got released.
    just_released: HashMap<u64, Touch>,
    // A collection of every [`Touch`] that just got canceled.
    just_canceled: HashMap<u64, Touch>,
}

pub struct Touch {
    // The id of the touch input.
    id: u64,
    // The starting position of the touch input.
    start_position: Vec2,
    // The starting force of the touch input.
    start_force: Option<ForceTouch>,
    // The previous position of the touch input.
    previous_position: Vec2,
    // The previous force of the touch input.
    previous_force: Option<ForceTouch>,
    // The current position of the touch input.
    position: Vec2,
    // The current force of the touch input.
    force: Option<ForceTouch>,
}

Gamepads

Each Gamepad is assigned a unique ID so we can associate them with the input from a specific player. We can list these IDs by asking the Gamepads resource.

Gamepads use a slightly different system with an Axis<T> resource:

pub struct Axis<T> {
    // The position data of the input devices.
    axis_data: HashMap<T, f32>,
}

So we have both Input<GamepadButton> and Axis<GamepadButton> resources depending on the kind of data we want to get back.

Axis<GamepadAxis> represents the different types of axis we have available on our controllers:

pub enum GamepadAxisType {
    // The horizontal value of the left stick.
    LeftStickX,
    // The vertical value of the left stick.
    LeftStickY,
    // The value of the left `Z` button.
    LeftZ,
    // The horizontal value of the right stick.
    RightStickX,
    // The vertical value of the right stick.
    RightStickY,
    // The value of the right `Z` button.
    RightZ,
    // Non-standard support for other axis types (i.e. HOTAS sliders, potentiometers, etc).
    Other(u8),
}
fn gamepad_system(
    gamepads: Res<Gamepads>,
    button_inputs: Res<Input<GamepadButton>>,
    button_axes: Res<Axis<GamepadButton>>,
    axes: Res<Axis<GamepadAxis>>,
) {
    for gamepad in gamepads.iter() {
        if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
            // ...
        }
    }
}

Could also be in response to events:

fn gamepad_events(
    mut gamepad_connection_events: EventReader<GamepadConnectionEvent>,
    mut gamepad_axis_events: EventReader<GamepadAxisChangedEvent>,
    mut gamepad_button_events: EventReader<GamepadButtonChangedEvent>,
) {
    for connection_event in gamepad_connection_events.iter() {
        // ...
    }
}

// However if we want ordered events we need to match instead of query
// separately:

fn gamepad_ordered_events(mut gamepad_events: EventReader<GamepadEvent>) {
    for gamepad_event in gamepad_events.iter() {
        match gamepad_event {
            GamepadEvent::Connection(connection_event) => info!("{:?}", connection_event),
            GamepadEvent::Button(button_event) => info!("{:?}", button_event),
            GamepadEvent::Axis(axis_event) => info!("{:?}", axis_event),
        }
    }
}

You can add haptic feedback by writing events to request it:

fn gamepad_system(
    gamepads: Res<Gamepads>,
    button_inputs: Res<Input<GamepadButton>>,
    mut rumble_requests: EventWriter<GamepadRumbleRequest>,
) {
    for gamepad in gamepads.iter() {
        // ...
        rumble_requests.send(GamepadRumbleRequest::Add {
            gamepad,
            intensity: GamepadRumbleIntensity::strong_motor(0.1),
            duration: Duration::from_secs(5),
        });
    }
}

The GamepadRumbleRequest::Add event triggers a force-feedback motor, controlling how long the vibration should last, the motor to activate, and the vibration strength. GamepadRumbleRequest::Stop immediately stops all motors.

Rumble features are provided by the bevy_gltf crate.

Window

There are also events that come from the bevy_window that are related to input and may also be used to trigger your game logic:

// This system prints out all char events as they come in
// ReceivedCharacter comes from `bevy_window` not `bevy_input`
fn print_char_event_system(
    mut char_input_events: EventReader<ReceivedCharacter>
) {
    for event in char_input_events.iter() {
        info!("{:?}: '{}'", event, event.char);
    }
}

fn cursor_events(
    mut cursor_evr: EventReader<CursorMoved>,
) {
    for ev in cursor_evr.iter() {
        println!(
            "New cursor position: X: {}, Y: {}, in Window ID: {:?}",
            ev.position.x, ev.position.y, ev.window
        );
    }
}

Read more