doc 1 of 10

Precision Platformer — Learning Plan

The goal: a 2D platformer whose movement is modelled on Celeste — run, variable-height jump, air-dash, wall slide and wall jump — built from scratch, every line typed and understood, with each number traced back to a reason rather than copied from a video.

This is the project where "game feel" stops being a vibe and becomes arithmetic. Celeste's source code is public, so the constants in this plan aren't guesses — they're the real ones, converted into Godot's units, with the conversion shown. You'll learn why a jump feels good, which is a thing you can carry into every game you make afterwards.

Early stages give you complete, explained code; later stages hand you less and less finished work on purpose, because writing it yourself is where the learning happens. Every stage ends with checks you can verify in the engine alone.

When you're done, all of this is true#

  • Holding jump longer jumps higher, and you can explain the mechanism without saying "gravity is lower"
  • Walking off a ledge and jumping a moment later still jumps
  • Pressing jump just before landing jumps on landing, not never
  • An eight-direction dash that refills on the ground and preserves speed you'd already built
  • Wall slide and wall jump that let you climb a shaft
  • A jump that clips a corner by two pixels slides around it instead of stopping dead
  • Every tuning number lives in one Inspector panel, editable while the game runs
  • You can draw the player's state machine from memory
  • You can explain every script without opening it

What you'll learn#

New here: 2D physics and collision in earnest — CharacterBody2D, layers and masks, is_on_floor, move_and_slide · tilemaps and cameras — TileMapLayer, Camera2D limits · node-based state machines, the rung above the farming game's enum.

Practiced along the way: typed GDScript everywhere, custom Resources as tuning data, signals, scene composition, git as a working habit.

Deliberately left out: animation and particles, audio, menus, saving, moving platforms, enemies. A platformer that feels superb with a white rectangle for a player is a real achievement. One with beautiful art and a mushy jump is not, and no amount of the second fixes the first.

Why this project, and why now#

The farming game taught tilemaps, resources and an enum state machine, but a top-down farm never needs real physics — no gravity, no airborne states, no collision subtleties. This project is squarely aimed at that gap, and it's the reason a platformer is the classic second project: the concepts are unavoidable rather than optional.

You'll also feel the enum state machine break. Stage 05 is the upgrade, and it lands after you've written enough movement code to want it — not before, when it would look like ceremony.

About the numbers#

Every constant here comes from Celeste's released source, which its authors published under a permissive licence. Two facts make the conversion clean:

  • Celeste runs at a fixed 60 fps on a 320×180 internal resolution.
  • Its movement constants are already per second — every use multiplies by delta time.

So MaxRun = 90 means 90 pixels per second, and it drops into Godot unchanged. What you must scale is space: if your tiles aren't 8 pixels like Celeste's, or your viewport isn't 320×180, then 90 px/s covers a different fraction of the screen. Stage 01 sets a matching viewport so the numbers land as intended, and says how to scale them if you'd rather not.

One honest limitation, stated now rather than discovered in stage 08: Celeste stores positions as integers and steps movement one pixel at a time. Godot's move_and_slide uses floats and a small collision margin. Most behaviour ports exactly; the pixel-precise corner and ledge handling does not, quite. Stage 08 is about closing that gap as far as it closes, and about being honest where it doesn't.

Throughout, the typed GDScript rules apply to every line — stage 01 turns on the warnings that enforce them.

House motto: aim for improvement every day. One stage is one sitting; if a stage regularly spills into tomorrow, it's sized wrong — shrink the bite, not the ambition.

Act one — a body that moves well#

Nothing here needs a level. A rectangle on a floor is enough, and keeping it that plain means nothing distracts from how the movement feels.

#What you build (observable)What you learn
01The project; a box that runs with weight and fallsproject setup · CharacterBody2D · acceleration vs. instant speed · git
02A jump whose height answers how long you hold the buttonvariable jump height · the apex trick · impulse vs. hold
03Jumps that "shouldn't" work now docoyote time · input buffering · why fairness is measurable

Act two — a game around it#

#What you build (observable)What you learn
04A tiled room with spikes, a camera, and instant respawnTileMapLayer · collision layers · Camera2D limits · Area2D sensors
05The same character, restructured into state nodesnode-based state machines · refactoring without changing behaviour
06An eight-direction dash with freeze frames and speed retentionthe signature move · why it keeps your speed · refill rules
07Wall slide and wall jump; optionally stamina climbingwall detection · directional vs. neutral jumps · scope decisions
08Corner correction, then prove the whole thing is yourssub-pixel collision reality · rebuild-from-memory · self-review

Several stages ask you to make a real design decision between sound options — those are flagged where they appear, with trade-offs laid out. Record what you chose and why in the architecture document; future-you will thank you.