Lesson 3 of 22, in world 1: Foundations
Delta Time & Fixed Timesteps
Frames don’t arrive at a perfectly steady pace. One frame takes 16 ms, the next 21 ms, and a player on a 144 Hz monitor gets more than twice as many frames as one on a 60 Hz laptop. If you move things “a bit per frame”, your game runs at a different speed on every computer. The fix is delta time.
What is delta time?
Delta time (dt) is the number of seconds since the previous frame — about 0.0167 at 60 FPS. Instead of “move 2 pixels per frame”, you say “move 130 pixels per second” and multiply by how many seconds actually passed:
let last = performance.now()
function loop(now) {
const dt = (now - last) / 1000 // milliseconds → seconds
last = now
player.x += player.speed * dt // speed is in pixels per SECOND
render()
requestAnimationFrame(loop)
}Both racers are tuned to cross the track in 4 seconds on a 60 FPS machine. Drag the slider to simulate a slow laptop (15 FPS) or a gaming monitor (144 FPS). The pink racer's speed depends on the machine; the cyan one is always on time.
Timers are just numbers
let cooldown = 0
function update(dt) {
cooldown -= dt // counts down in real seconds
if (input.fire && cooldown <= 0) {
spawnBullet()
cooldown = 0.25 // 4 shots per second, on any machine
}
}When variable dt goes wrong: tunnelling
If a frame takes a long time (a hitch, a slow machine, a background tab), dt becomes large, and fast objects take huge steps. A bullet can be in front of a wall in one frame and behind it the next, so the collision check never sees an overlap. Physics also becomes less accurate and less repeatable.
Bullets fly at 900 px/s toward a 10 px wall. With a big delta time a bullet can jump from one side of the wall to the other in a single step and never 'touch' it. A fixed timestep runs physics in small, equal steps (1/120 s) no matter how slowly the screen renders.
The fixed timestep
The standard fix: keep rendering as fast as the screen allows, but run the simulation in small, constant steps. Accumulate real time, and spend it in fixed slices:
const STEP = 1 / 60 // physics always advances in exact 1/60 s steps
let accumulator = 0
function loop(now) {
const frameTime = Math.min((now - last) / 1000, 0.25) // clamp huge hitches
last = now
accumulator += frameTime
while (accumulator >= STEP) {
physicsUpdate(STEP) // deterministic, same on every machine
accumulator -= STEP
}
render()
requestAnimationFrame(loop)
}Key takeaways
- dt = seconds since last frame. Express speeds in units per second and multiply by dt.
- Timers, cooldowns and animations are numbers that count down by dt.
- Large dt causes tunnelling and unstable physics. Clamp dt, and use a fixed timestep for physics.
Check yourself
Q1. A ship moves at 200 px/s. The frame took 0.05 s. How far should it move this frame?
Q2. Why does "x += 3" every frame run faster on a 144 Hz monitor?
Q3. What does a fixed timestep give you?
Exercises
- In the race demo, at what FPS does the pink racer finish at exactly the same time as the cyan one? Why?
- In the tunnelling demo, find the lowest frame rate at which bullets still hit the wall without the fixed timestep.
- Write a "flash every 0.5 seconds" blinking text using only a timer variable and dt.