Stage 02 — The Dash
You will build: the genre's signature verb — a dash with duration, cooldown, and commitment, running on a real state machine from the start. You'll learn: state machines under pressure · designing commitment. This project runs two machines by the time it's done — this one and the enemy's in stage 05 — so if the pattern itself is new, Systems → State Machines is the general treatment. Warm-up — from memory:
- Your movement's three tuning numbers, and what each does.
- Why did stage 01's code multiply acceleration by
delta?
Why this exists#
A dash isn't "move fast briefly" — it's a promise: for 0.15 seconds you are dashing and
nothing else, then you can't again for a beat. Duration, commitment, cooldown. The moment
you try to bolt that onto free movement with an is_dashing flag, you meet the flag-soup
problem the fast genre is famous for — so this game runs a state machine from stage 02,
before the soup, not after. (Farming veterans: same pattern as your farmer's, met one
stage earlier and about to work much harder.)
Build it#
A — The machine#
In player.gd: enum State { RUN, DASH }, a state variable, a _set_state() with a
match for enter/exit effects, and _physics_process split per state — RUN does exactly
what stage 01 did; DASH overrides everything:
- Enter DASH: only from RUN, only if the cooldown timer isn't running, only with a nonzero... decide: dash toward movement input, or toward facing when standing still? (Try both. This is feel design; there's no wrong answer, only an undecided one.)
- During:
velocity = dash_direction * dash_speed— constant, ignoring input; steering during a dash is a different (floatier) game. - Exit: a duration timer ends it → back to RUN, start the cooldown, and decide what velocity you exit with (full dash speed? clamped to max_speed? — one feels slick, one feels abrupt; pick with your hands).
Numbers to start arguing with: speed 420, duration 0.15s, cooldown 0.4s. Two Timer nodes
(one_shot) or tracked floats — your call; be able to defend it.
B — The input#
Add a dash action (Space or Shift). Pressed during RUN → try to dash. Pressed during
DASH → ignored, silently — and notice that the state machine made that sentence one
match arm instead of three flags.
C — Invincibility frames (the flag, not yet the feature)#
Real dashes dodge through danger. Damage doesn't exist until stage 04, but the dash's
shape includes it now: an is_invincible bool, true during DASH. It does nothing yet. When
the Hurtbox arrives, it will check one property, and dashing-through-attacks will simply
work — that's what designing ahead buys.
D — Feel pass#
Re-tune stage 01's numbers with the dash in the loop — dashes change what top speed feels like. Then the readability pass: modulate the sprite during DASH and a different tint during cooldown, so the state machine is visible. Placeholder juice; the real thing is stage 06's job.
git add . && git commit -m "stage 02: dash state with duration, cooldown, i-frame flag"
Checkpoint — definition of done#
- Dash commits: input during the dash steers nothing, dash during cooldown does nothing
- Standing-still dash behaves the way you decided (input vs facing — which did you pick, and why?)
- Dashing into a wall doesn't wedge, jitter, or tunnel
- The cooldown is visible on the sprite, however crudely
- Zero warnings; committed. And you can explain: what specifically would the
is_dashing-flag version have gotten wrong when attack arrives next stage?
Stretch (no instructions)#
Afterimages: ghost sprites trailing the dash. (Instancing, a fade, queue_free — every
piece is something you've met.)
If you get stuck#
- Dash never ends → is the duration timer
one_shot? Did its timeout actually connect? Print inside_set_state— are you reaching the exit? - Dash feels like fast walking → it's respecting acceleration; the DASH state should set velocity, not move_toward it.
- Double-dash sneaks through → the cooldown check and the state check are different
guards; log both when
dashis pressed and read which one lied.