Input & Events
Handle keyboard, mouse, gamepad, and in-game events in Bevy - responsive controls without tangled systems.
Recipe
fn jump(
keys: Res<ButtonInput<KeyCode>>,
mut q: Query<&mut PlayerState>,
) {
if keys.just_pressed(KeyCode::Space) {
for mut state in &mut q {
state.request_jump = true;
}
}
}When to reach for this:
- Any player-controlled game
- Replay systems recording input edges
- UI buttons firing gameplay via events
- Multiplayer local couch co-op with multiple gamepads
Working Example
use bevy::prelude::*;
#[derive(Event)]
struct PlayerShot {
entity: Entity,
}
#[derive(Component)]
struct Cooldown(Timer);
fn shooting(
mouse: Res<ButtonInput<MouseButton>>,
mut commands: Commands,
mut reader_q: Query<(Entity, &mut Cooldown)>,
mut events: EventWriter<PlayerShot>,
time: Res<Time>,
) {
if !mouse.just_pressed(MouseButton::Left) {
return;
}
for (entity, mut cd) in &mut reader_q {
cd.0.tick(time.delta());
if cd.0.finished() {
events.write(PlayerShot { entity });
cd.0.reset();
}
}
}
fn on_shot(mut events: EventReader<PlayerShot>) {
for e in events.read() {
println!("shot from {:?}", e.entity);
}
}What this demonstrates:
just_pressededge for single fire per click- Cooldown timer gating event spam
- EventReader consumer separate from writer system
Deep Dive
How It Works
- Input resources updated once per frame before
Updatesystems. - Action maps (community
leafwing-input-manageror custom) bind keys to intents. - Events are queues drained by readers; order depends on system ordering.
- UI nodes can block pointer events when interactive.
Input Types
| Resource | Use |
|---|---|
ButtonInput<KeyCode> | Keyboard |
ButtonInput<MouseButton> | Mouse buttons |
Axis<GamepadAxis> | Sticks (via gamepad entities) |
Gotchas
- Using
pressedfor actions needing once - auto-repeat feel wrong. Fix:just_pressedfor discrete actions. - No delta time on mouse look - sensitivity varies with FPS. Fix: scale by
time.delta_secs(). - Systems reading events before writers run - one-frame lag. Fix: order
.after(shooting)on reader. - Thousands of events per frame - overhead. Fix: direct component mutation for bulk sims; events for sparse signals.
- Gamepad not assigned - player 2 idle. Fix: detect
Gamepadconnections on insert and map per device.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Polling only | Simple jam | Many rebindable actions |
| Channel from OS thread | Custom controllers | Bevy input sufficient |
| Networked input sync | Online multiplayer | Local single-player |
| UI-only buttons | Menu-heavy game | Fast twitch gameplay |
FAQs
Rebindable keys?
Store HashMap<Action, KeyCode> in resource; map in input system each frame.
Mouse delta?
AccumulatedMouseMotion resource (Bevy 0.14+) for FPS camera look.
Touch/mobile?
Bevy mobile support evolving - map touch to virtual joystick components.
UI click through?
Interaction component on UI nodes; skip world input when Hovered on menu.
Replay?
Record just_pressed edges with frame numbers; playback writes synthetic input resource.
Pause menu?
State enum disables gameplay systems with run_if(in_state(Playing)).
Multiple listeners?
Multiple EventReader instances each receive all events unless using reader ordering groups.
Leafwing crate?
Higher-level action state machine over raw buttons - good for complex controls.
Timing?
Input read in Update; physics response often in FixedUpdate - see Game Loops & Timing.
Bevy basics?
Game Dev Basics keyboard move example.
Related
- Bevy Engine - system ordering
- Game Loops & Timing - fixed update
- Game Dev Basics - input example
- Physics & Audio - event-driven SFX
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.