Handling Input and Time
React to the player and the clock.
Handling Input and Time is a free Learn Rust Coding lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Learn Rust Coding learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Input as a Resource
Keyboard state lives in the ButtonInput<KeyCode> resource, provided by DefaultPlugins.
Request it read-only with Res. Bevy updates it each frame before your systems run, so you always see the current state.
fn read_keys(keys: Res<ButtonInput<KeyCode>>) {
if keys.pressed(KeyCode::Space) {
info!("space held");
}
}pressed vs just_pressed
pressed is true every frame a key is held — ideal for continuous movement. just_pressed is true only on the frame the key goes down.
Use just_pressed for discrete actions like jumping or firing so a single tap fires once.
fn jump(keys: Res<ButtonInput<KeyCode>>) {
if keys.just_pressed(KeyCode::ArrowUp) {
info!("jump!");
}
}Building a Direction Vector
Combine multiple keys into a movement direction. Start at zero and add a unit on each axis for each pressed key.
Normalizing the result prevents diagonal movement from being faster than straight movement.
fn direction(keys: Res<ButtonInput<KeyCode>>) -> Vec2 {
let mut d = Vec2::ZERO;
if keys.pressed(KeyCode::KeyW) { d.y += 1.0; }
if keys.pressed(KeyCode::KeyS) { d.y -= 1.0; }
if keys.pressed(KeyCode::KeyA) { d.x -= 1.0; }
if keys.pressed(KeyCode::KeyD) { d.x += 1.0; }
d.normalize_or_zero()
}The Time Resource
Frame rates vary by machine, so never move by a fixed amount per frame. The Time resource gives you delta_secs(), the seconds since the last frame.
Multiplying speed by delta makes movement frame-rate independent.
fn move_player(time: Res<Time>, mut q: Query<&mut Transform, With<Player>>) {
let dt = time.delta_secs();
for mut tf in &mut q {
tf.translation.x += 200.0 * dt;
}
}Combining Input and Time
The real pattern joins both resources in one system: read the direction from input, then scale it by speed and delta.
This produces smooth, consistent control regardless of whether the game runs at 30 or 144 frames per second.
fn drive(
keys: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
mut q: Query<&mut Transform, With<Player>>,
) {
let mut d = Vec2::ZERO;
if keys.pressed(KeyCode::KeyA) { d.x -= 1.0; }
if keys.pressed(KeyCode::KeyD) { d.x += 1.0; }
let step = d.normalize_or_zero() * 300.0 * time.delta_secs();
for mut tf in &mut q {
tf.translation += step.extend(0.0);
}
}Mouse Buttons
Mouse buttons use the same ButtonInput API with MouseButton instead of KeyCode.
This consistency means the pressed and just_pressed habits you built for the keyboard transfer directly to clicks.
fn shoot(buttons: Res<ButtonInput<MouseButton>>) {
if buttons.just_pressed(MouseButton::Left) {
info!("fire");
}
}Timers for Cooldowns
A Timer tracks elapsed time toward a target duration. Tick it with the frame delta each update.
Use a repeating timer for a firing cadence and check just_finished() to know when to act.
#[derive(Component)]
struct Cooldown(Timer);
fn tick(time: Res<Time>, mut q: Query<&mut Cooldown>) {
for mut cd in &mut q {
cd.0.tick(time.delta());
if cd.0.just_finished() { info!("ready"); }
}
}Creating a Timer
Construct a timer from a duration and a TimerMode. Once stops after firing; Repeating resets automatically.
Store it on a component so each entity, such as a turret, can have its own independent cadence.
use std::time::Duration;
commands.spawn((
Turret,
Cooldown(Timer::new(Duration::from_millis(500), TimerMode::Repeating)),
));FixedUpdate for Determinism
Physics and netcode often need a fixed timestep. Systems in the FixedUpdate schedule run a constant number of times per second, regardless of render rate.
Inside them, use Time<Fixed> whose delta is always the same, giving reproducible simulation.
App::new()
.add_plugins(DefaultPlugins)
.add_systems(FixedUpdate, physics_step)
.run();Elapsed vs Delta
time.delta_secs() is the gap since last frame. time.elapsed_secs() is the total time since startup.
Elapsed is great for animations driven by a sine wave, like a bobbing pickup, where you feed total time into the function.
fn bob(time: Res<Time>, mut q: Query<&mut Transform, With<Pickup>>) {
let t = time.elapsed_secs();
for mut tf in &mut q {
tf.translation.y = (t * 2.0).sin() * 10.0;
}
}Clamping to Bounds
After moving, keep the player on screen by clamping the transform within the window's half-extents.
f32::clamp bounds each axis. Read the window size from the Window query when you need exact limits.
fn clamp(mut q: Query<&mut Transform, With<Player>>) {
for mut tf in &mut q {
tf.translation.x = tf.translation.x.clamp(-300.0, 300.0);
tf.translation.y = tf.translation.y.clamp(-200.0, 200.0);
}
}Quick Check
Consider frame-rate independence.
Recap: Input and Time
Input lives in ButtonInput resources; use pressed for held actions and just_pressed for taps. The Time resource and Timer components keep movement and cooldowns frame-rate independent.
With control in place, the final lesson adds collisions and game state transitions.
Frequently asked questions
Is the “Handling Input and Time” lesson free?
Yes — the full text of “Handling Input and Time” is free to read here on the web, and the Learn Rust Coding course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Learn Rust Coding course, upgrade to CoddyKit PRO.
What will I learn in “Handling Input and Time”?
React to the player and the clock. You practise Learn Rust Coding with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Learn Rust Coding?
No prior experience is required. Learn Rust Coding on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Handling Input and Time” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Learn Rust Coding lesson?
Yes. Every Learn Rust Coding lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- The ECS Mindset
- Spawning and Moving Entities
- Handling Input and Time
- Collisions and Game State