Motion & Input
Still pictures were yesterday. Today the canvas gets a
heartbeat — sixty frames a second — and you learn the one little number,
dt, that makes motion smooth on every machine on Earth.
You will be able to…
- Explain what
dtis, and why speeds are in pixels per second - Start and stop an animation with
Anim.start/Anim.stop - Write pure movement maths —
moveandbounce— that tests can trust - Read held keys with
Input.is_down
The flip-book and the magic number
Animation is a flip-book: draw a picture, wait a sliver of a second,
draw it again slightly different. Browsers flip roughly 60 pages per
second. The Gamekit hands you the book via Anim.start — you
give it a function, and that function gets called once per frame, forever,
until Anim.stop ().
Each call receives one argument: dt, the number of
seconds since the previous frame — about 0.016 at 60
fps. Why hand you that? Because frame rates lie. A gaming monitor
flips 120 pages a second, a busy laptop maybe 45. If your square moves "5
pixels per frame", it moves twice as fast on the fancy monitor — same
code, different game. The fix is one honest multiplication:
distance this frame = speed (pixels per second) × dt (seconds)
At 60 fps a speed of 300 px/s covers 300 × 1/60 = 5 px per frame; at 120 fps, 2.5 px per frame but twice as often. Same journey per second, everywhere, always. This is called frame-rate independence, and every serious game engine on the planet works this way.
A heartbeat on the canvas
Watch it breathe. This square glides right at 120 px/s and respawns on the left when it flies off the edge:
New machinery, honestly labelled: ref makes a
mutable box — !x reads it, x := …
overwrites it. Yes, after a lesson and a half of "names never change",
we're changing one. A ref is the shortest path to motion today, so we take
it — and in Phase 2 you'll learn the pure way, where the world isn't
overwritten but flows, new world from old, every tick. Until
then: refs stay small, and all the actual thinking goes into pure
functions. Note the two conversions at the borders too:
int_of_float turns our smooth float position into pixel
coordinates, and Draw.clear () starts each frame fresh.
Now the actual work. The glide's maths — "position, speed, time in; new position out" — deserves to be a pure function: no refs, no drawing, just an answer. Pure maths is what the Check button can test, and in Phase 2 it's what entire games are made of.
Write move : float -> float -> float -> float,
taking a position, a speed (px/s) and dt (seconds), and
returning the new position. One line of frame-rate-independent
physics.
speed *. dt.pos +. speed *. dt.Bouncing off the walls
Respawning at the edge is fine for screensavers; games bounce.
Our square is 20 px wide on a 400-px canvas, so its x may roam from
0. (touching the left wall) to 380. (its right
side touching the right wall — 400 minus 20). A bounce is a pure decision:
given a position and a velocity, return the corrected pair. Past or on a
wall? Clamp the position onto the wall and point the velocity back
into the field — Float.abs vx forces it rightward,
-. Float.abs vx leftward. Mid-air? Change nothing.
Why not just flip the sign? Because a square sitting exactly on a wall with an unlucky velocity would flip forever — trapped, vibrating. Pointing the velocity away from the wall can't be wrong twice. Wall-clamp logic like this guards every paddle, ball and camel you'll ever ship.
Write bounce : float -> float -> float * float.
Given x and velocity vx, return the pair
(x, vx) corrected for walls at 0. and
380.: touching or past a wall (x <= 0. or
x >= 380.) means clamp x to that wall and send the
velocity back into the field; otherwise return both unchanged.
x <= 0.), on/past the right wall (x >= 380.), or free air.(0., Float.abs vx). Float.abs makes the speed point right; -. Float.abs vx makes it point left.if x <= 0. then (0., Float.abs vx) else if x >= 380. then (380., -. Float.abs vx) else (x, vx)Two tested, trustworthy functions. Time to put them on stage — the
wiring below is written for you and calls your move
and bounce every frame.
Bring in your move and bounce (replace
the two TODO stand-ins). The provided Anim.start wiring
does the rest. Check verifies the maths, then press Run to watch it
bounce between the walls.
move and bounce over the TODO stand-ins — the wiring already calls them.bounce isn't clamping. Snail-paced? Your move forgot *. dt's partner, speed.Notice the shape of what you built: refs and drawing form a thin, boring shell, while every decision lives in pure, tested functions. Hold that thought — it's the architecture of everything to come.
Grab the controls
A game you can't steer is a movie. Input.is_down key
answers true while a key is held, false
otherwise — ask it fresh every frame. Keys go by their browser names:
"ArrowLeft", "ArrowRight", letters like
"a", and " " for the space bar.
Checkpoint quiz
Loot collected
Anim.startcalls your function every frame withdt= seconds since the last one;Anim.stop ()ends it.- Frame-rate independence: distance = speed (px/s)
*.dt. Never "per frame". ref/!/:=— a mutable box, kept small and dumb; the thinking lives in puremoveandbounce.- Walls are clamp + redirect, not blind sign-flips.
Input.is_down "ArrowLeft"polls the keyboard; click the canvas to give it focus.
That's Phase 1 cleared — engine lit, pixels painted, HUD styled, square bouncing. In Phase 2 the refs go away: worlds become values, and your game state gets a shape of its own. Onward.