Learn what a program is, explore the SDLC, and write your first JavaScript using console.log().
Understand how to store and work with data using let, const, and typeof.
Practice arithmetic, string concatenation, and comparison operators.
→ View full module pageUnit 2.1 slides — canvas, sprites, keyboard input, and the frame loop. Includes runnable code examples.
5-min walkthrough introducing canvas, sprite, and run cycle. Watch before the Hello Sprite lesson.
How q5play calls `setup()` once and `draw()` every frame. Read before 2.1.3a (Canvas).
How new Canvas(w, h) opens the drawing area and why (0, 0) is top-left with y increasing downward. Read before 2.1.3b (Sprite).
How new Sprite(x, y, w, h) creates a rectangle the engine renders for you, and how .color sets its fill. Read before 2.1.3c (lab).
Practice from 2.1.3b: create a canvas, drop a single sprite at the centre, and set its color. Three steps, no movement yet.
Why you declare a let at file scope and assign the sprite in setup() — and what goes wrong when you create sprites inside draw(). Read before 2.1.3e.
Why background(color) must be the first call inside draw() to clear the previous frame — and what motion trails look like without it. Read before 2.1.3f.
How to read and write the core sprite properties pos, rotation, and layer after the sprite is created. Read before 2.1.4 (worked example).
Your first q5play sketch: a canvas, a sprite, a background color.
3-min walkthrough of the setup/draw frame loop at 60 fps. Watch after Hello Sprite, before Make it Move.
How `kb.pressing(key)` checks whether a key is held this frame. Read before 2.1.7a (Velocity).
Teacher-led walkthrough of the if/else-if/else pattern for keyboard movement and the drift-bug. Read before Make it Move.
How `sprite.vel.x` and `vel.y` work as pixels-per-frame values the engine integrates into position. Read after 2.1.7.
The canonical if/else-if/else pattern that maps key presses to velocity changes inside `draw()`. Read after 2.1.7a.
Why velocity persists across frames and why the else branch is what stops the sprite. Read after 2.1.7b.
Start from a working WASD movement pattern, delete the two else lines, observe drift, then restore them.
Why graded labs use 'a'/'d'/'w'/'s' instead of arrow keys, and how to swap them into the movement pattern. Read after 2.1.7d.
Drive a sprite with the arrow keys by setting vel.x and vel.y based on what's pressed.
Build a sprite playground combining canvas, WASD-controlled sprite, auto-motion, and on-screen text. Auto-graded.
Half-page written reflection on setup/draw and the 60fps math. AI-graded with hints.
Pick one or more of the stretch challenges and implement them in the editor. Auto-graded.
→ View full module pageUnit 2.2 slides — classes, objects, `this`, and procedural vs OOP. Includes runnable code examples.
4-min DevTools walkthrough. Opens a running q5play sketch, inspects a Sprite instance, and names what a class is. Watch before the DevTools reveal example.
The words class and instance and how they relate — a class is a blueprint; `new` builds one instance from it. Read before 2.2.3a.
What `new` does mechanically: allocate, run constructor, return. Read right after 2.2.3.
How the constructor receives arguments from `new` and stores them on `this`. Read right after 2.2.3a.
A property is data stored on a single instance via `this.name = value`. Read before the property labs.
Read `b.color` and `b.size` off a Box instance and print them to the on-canvas console using `console.log`. First practice with dot-notation reads.
Mutate `b.color` on a space-bar press to practice assigning a new value to an instance property. First practice with dot-notation writes.
Instantiate b1 and b2 from the same class with different colors, then mutate only b1 to confirm each instance's state is independent.
`this` always refers to one specific thing — the instance the method was called on. Read before the substitution rule example.
Low-stakes autograded practice. Instantiate the Sprite class twice (rectangle form and circle form), set properties on each instance, and give one sprite a stroke. Bridges the 2.2.4 / 2.2.5 worked examples and the 2.2.8 Enemy class lab.
A tour of the Sprite class re-read as OOP. Every `new Sprite(...)` is a constructor call; every `.color` / `.stroke` is a property on an instance.
3-min whiteboard walkthrough of `this` inside methods. Shows why `this.color` and `this.hp` refer to the specific instance the method was called on.
Method declaration syntax: inside a class body, methods are written without the `function` keyword. Read before the method labs.
Write `Counter.tick()` — a method that takes no arguments and mutates `this.n`. First method you write from scratch.
Add `Counter.addBy(n)` — a method that takes an argument and uses it to increment `this.n`. Keys 1/2/3 add different amounts.
Add `Counter.isHigh()` which returns `true` when `this.n > 10`. The driver uses the return value to color the display red.
Write `Counter.bigStep()` which calls `this.addBy(5)` and returns `this.isHigh()` — one method dispatching another on the same instance.
Composition over inheritance: why wrapping a Sprite as `this.sprite` is safer than inheriting from it. Read before the sprite mutation labs.
Write `Mover.moveRight(dx)` which adds `dx` to `this.sprite.x`, reaching through composition to move the wrapped sprite.
Write `Bubble.pop()` which calls `this.sprite.delete()` so the object removes its own sprite on click.
Instantiate e1, e2, and e3 from the Enemy class at three different positions — three independent instances, each in its own named variable.
Store five Enemy instances in a single array by pushing new Enemy(...) calls into `enemies` — practice managing many instances without separate named variables.
Write a `for...of` loop in `draw()` that calls `.render()` on each enemy — dispatching a method to every element in the array with one loop.
Side-by-side comparison of the same problem (an 'enemy fleet') solved two ways: with parallel arrays (procedural) and with an array of class instances (OOP). Read between the Enemy class example and the Procedural-vs-OOP example.
Three yes-or-no questions that tell you whether a class is the right tool. Read after the Procedural vs OOP example.
Write a Collectible class, instantiate at least 5, and detect overlap with the player. SLO-2 lab — the class is the point. Auto-graded.
1-page comparison of procedural and object-oriented programming.
Name the three OOP pillars you'll encounter in downstream courses — no implementation required this week. Read at the close of Unit 2.2.
Pick one (or more) OOP stretch challenges — extend your Enemy class, write a Player class, or try subclassing with `extends`. Auto-graded.
→ View full module pageUnit 2.3 slides — Groups, overlap detection, safe despawn, edge-triggered input, and ground-gated jumping. Includes runnable code examples.
Why a `Group` is a class with shared defaults — and how a single line creates fifty enemies. Watch before `2.3.5 Groups Sandbox`.
q5play docs chapter on Groups. Read before `2.3.5 Groups Sandbox`.
Groups let you treat many sprites as one. Create, configure, spawn into, and clean up a Group.
The two flavors of `overlaps()` and why the callback form is cleaner for despawn-on-collide. Watch before `2.3.7 Apple Catcher`.
q5play docs chapter on collisions and overlap detection. Read before `2.3.10 Safe Despawn`.
Live demo of why `for (let i = 0; i < group.length; i++)` + `.delete()` skips items, and how to fix it. Watch before `2.3.10 Safe Despawn`.
Combine Groups, overlap detection, and safe despawn into a playable asteroid scene. WASD steers the ship; an overlap with any asteroid ends the game.
Pick one (or more) Groups stretch challenges — add a lives counter to Apple Catcher, vary apple sizes for varied points, or implement a `cull()` helper. Auto-graded.
Why holding space launches a super-jump 60 frames in a row, and how the edge-triggered form fixes it. Watch before `2.3.15 Edge-triggered Input`.
q5play docs chapter on input — `kb.pressing` (level-triggered) vs `kb.presses` (edge-triggered). Read before `2.3.15 Edge-triggered Input`.
The infinite-jump bug live, and the `colliding(ground)` fix. Watch before `2.3.18 Ground Detection`.
q5play docs on `sprite.colliding(other)` and the ground-gated jump idiom. Read before `2.3.18 Ground Detection`.
Build a one-sprite platformer: WASD moves, space jumps — but only when touching the ground.
Build a two-wheeled car using WheelJoints and drive it up a ramp with WASD. The harder of the two A14.1 options.
→ View full module pageUnit 2.4 slides — animated sprites with `addAni` / `changeAni`, camera follow, smoothing with `lerp`, and `layer` for render order. Includes runnable code examples.
How `addAni` registers named animations and `changeAni` swaps the active one — driven by player state (idle vs run). Watch before `2.4.5 Animated Sprites Sandbox`.
q5play docs chapter on Animation. Read before `2.4.5 Animated Sprites Sandbox`.
How sprite.addAni(name, frame1, ...) registers a named animation and makes it the active default. Read before 2.4.3b (changeAni).
How sprite.changeAni(name) swaps the active animation between previously registered names. Read before 2.4.4 (Worked Example).
How sprite.animation.frameDelay = N slows or speeds an animation cycle. Read before 2.4.5 (Animated Sprites Sandbox).
How sprite.image = url displays a still image without any animation cycle. Read before 2.4.5 (Animated Sprites Sandbox).
Drive a sprite's visual from input state — the same swap pattern used by addAni / changeAni, with sprite.image swapping as the asset-free placeholder. Idle when standing, run when moving, jump when 'w' is held.
Why the camera is a coordinate transform, not a 'real' object — the world stays put, only the viewport moves. Watch before `2.4.8 Worked Example — Camera Follow`.
q5play docs chapter on Camera. Read before `2.4.8 Worked Example — Camera Follow`.
How assigning camera.x and camera.y shifts the viewport center each frame. Read before 2.4.7b (Camera-follow pattern).
How camera.x = player.pos.x inside draw() locks the viewport onto a moving sprite. Read before 2.4.8 (Worked Example — Camera Follow).
How lerp(current, target, t) replaces a hard-set camera follow with a smooth elastic trail. Read before 2.4.9 (Worked Example — Smooth Camera).
How sprite.layer = N controls draw order so HUD elements always render on top of world geometry. Read before 2.4.10 (A15.1 Platformer).
Combine animated sprites, a camera that follows the player, three or more platforms, working jump mechanics, and a visible end goal into a playable side-scroller. Auto-graded.
Pick one (or more) Animation & Camera stretches — parallax background, mirror the sprite when it walks left, or add vertical camera follow. Auto-graded.
→ View full module pageUnit 2.5 opening slides — why game data disappears on refresh, what persistence means, and the plan for building a save/load system your players can count on.
A game resets to zero on refresh. Variables live in RAM — gone when the tab closes. Saved data lives on disk — still there tomorrow. This short video contrasts both so you understand what persistence actually means.
How storeItem(key, value) writes a value into the browser's save slot so it survives page reloads. Your game can remember things between sessions.
Step-by-step: build a survival game, detect when the game ends, and use storeItem to save the player's high score so it survives a reload.
How getItem(key) reads back a value your game saved earlier. Covers the basics of retrieving save data — the string type gotcha is in 2.5.5a.
getItem always gives you a string, even when you stored a number. String comparisons give wrong results — here's the Number() fix and the || 0 fallback.
Read your saved high score on startup, coerce it with Number(), display it next to the current score, and update it when the player beats their record.
A high score is one number. But your game has position, level, inventory — many pieces. JSON bundles them together.
JSON.stringify takes a JS object and turns it into a string you can store. Think of it as packing your game state into a box.
JSON.parse unpacks a JSON string back into a JS object. It's the reverse of JSON.stringify.
The full pattern: build a save object, stringify it, store it. Then getItem, parse, and restore. Complete round-trip.
Build a game with a moving player, score, and level. Save everything as one JSON object with storeItem.
Load a saved game: getItem, JSON.parse, coerce numbers, then apply to your game objects.
loadJSON loads external JSON files (levels, dialogue, config) — different from save/load. It's async and needs a callback.
One save isn't enough — games have multiple slots. Each slot is just a different key name passed to storeItem/getItem.
removeItem(key) deletes one saved key. clearStorage() wipes everything. When and how to use each.
Build three save slots. Each slot is a differently-named key. Press 1/2/3 to save, load with a simple text menu.
Manage save slots: delete old saves with removeItem, confirm before overwriting, handle the empty-slot case.
If a save exists, show 'Continue'. If not, only 'New Game'. The title screen pattern every game uses.
Build a title screen that checks for a save on startup. If save exists, show 'Press C to Continue'. Always show 'Press N for New Game'.
When the player tries to save over an existing slot, show an 'Are you sure?' prompt before writing.
Not every save needs a button press. Auto-save triggers on level transitions, checkpoints, or a timer — the player doesn't think about it.
An auto-save fires when something important happens — reaching a checkpoint, finishing a level, hitting a score milestone.
Implement two auto-save triggers: when the player reaches a level-complete zone, and a timer-based save every 30 seconds.
Graded: build a game with a complete save system — at least 3 save slots, load functionality, auto-save on level complete, and a Continue option on the title screen.
Extend your save system: save file previews (show slot contents before loading), save timestamps, multiple profiles, or a 'delete all saves' with double-confirmation.
→ View full module pageUnit 2.6 opening slides — title screen, gameplay, pause, game over. Every screen is a state. One variable controls which screen is active.
Every game has states: title → play → game over → title. A state machine is just a variable that remembers which screen you're on.
switch is like if/else but cleaner when checking one variable against many values. Syntax: switch(variable) { case VALUE: ... break; }
In q5play games, switch goes inside draw(). Each frame, it checks the state variable and draws the right screen.
A plain switch with three cases that each draw different text. Not a game yet — just getting comfortable with the syntax.
break exits the switch; without it, execution falls through to the next case. default handles unexpected values.
One variable controls which screen is visible. Every frame, draw() checks state to decide what to render. This is the single source of truth for your game's screen.
Build three distinct screens — red, green, blue backgrounds with different text — all controlled by one state variable with switch.
Use lowercase strings for state names: 'title', 'play', 'pause', 'gameover'. Be consistent. Good names make the switch statement self-documenting.
A title screen is just a state. It shows the game name, maybe instructions, and waits for the player to press a key to start playing.
Build a title screen state that shows the game name and 'Press ENTER to start'. When they press Enter, change state to 'play'.
The game over screen shows the final score and 'Press R to restart'. Restart means resetting variables + changing state back to 'play' (or 'title').
The complete beginner state machine: title screen → gameplay → game over → restart. The classic arcade loop.
Input-driven transitions happen when the player presses a key. Title → Play (press Enter), Play → Pause (press P). The player is in control.
Build a game where every state transition is triggered by the keyboard: Enter (start), P (pause/unpause), Escape (quit to title).
Condition-driven transitions happen automatically when something in the game changes. Score ≥ 100 → 'win', health ≤ 0 → 'gameover'. The game state triggers it.
Implement condition-driven transitions: score reaches 50 triggers a win screen, health drops to 0 triggers game over. The game decides when states change.
Real games have more than 3 states. Pause freezes the action. Inventory shows items. Settings adjusts volume. All use the same switch pattern you already know.
Add a pause state to your game. When paused, stop movement and physics, show a PAUSED overlay. Press P to toggle pause on and off.
A win screen shows when the player reaches the goal. It's a distinct state with congratulations, final stats, and a 'Play Again?' option.
States and saves work together: when the player reaches 'gameover', auto-save their score. When they hit 'win', save their victory. States trigger saves.
Graded: build a game with at least 4 states (title, play, pause, gameover) with full save/load integration. Saves trigger on state transitions. Continue restores the right state.
Extend your state machine: animated transitions, a loading screen state, state history (undo last transition), or an options/settings state that modifies gameplay.
You've built save systems and state machines — real game infrastructure. Unit 3 preview: what comes after the fundamentals.
→ View full module pageUnit 2.7 opening slides — the three big ideas before capstone: mouse input, joints, and the slingshot pattern. Everything in this unit feeds directly into A17.1 and your capstone design.
This is the last content unit before you design your own game. Mouse input, joints, and the slingshot pattern are the final building blocks — learn them here and you'll have everything you need to pitch and build a real project.
mouse.x and mouse.y give you the cursor's position on the canvas every frame. Read this before the mouse input worked examples.
mouse.pressing() is true every frame the mouse button is held down. Read this before 2.7.5 so you understand the held vs. one-shot difference.
mouse.presses() is true on exactly one frame — the moment the button first goes down. Read this after 2.7.4 to see how one-shot input differs from held input.
A hit-test checks whether the cursor is currently over a sprite. This technique gates click-and-drag — the drag only starts when the mouse is on the sprite.
Clicking is one-shot, but dragging follows the cursor across frames — and physics will fight you if you don't know the trick. This video shows the snap-and-zero-velocity pattern that makes a drag feel smooth before you code it yourself.
How to move a sprite exactly to the cursor position each frame during a drag. Zeroing the velocity is the step that keeps physics from fighting your snap.
Three joint types in five minutes: a distance joint keeps two sprites a fixed length apart, a hinge lets one pivot around another, and a slider rides a track. You don't need to master all three — just watch and pick the one that fits your game.
How DistanceJoint constrains two sprites to stay at a fixed distance from each other. Read this before the pendulum worked example.
How HingeJoint pins two sprites to a shared pivot point so they rotate around it. Read this before the rotating-arm worked example.
How joint.delete() releases a constraint at runtime so previously-joined sprites become independent. This is the release step in the slingshot pattern.
applyForce(fx, fy) adds a one-frame impulse to a sprite — the physics engine turns it into motion. Read this before 2.7.19 Worked Example — Launch a Sprite with applyForce.
Two numbers — dx and dy — encode the direction from one sprite to another. This is the math behind every force that points somewhere on purpose. Read this before 2.7.19 Worked Example — Launch a Sprite with applyForce.
This is the moment the unit snaps together: a joint holds the ball, you drag it back with the mouse, release removes the joint, and a force vector launches it toward the target. Watch the whole pattern before you read the code.
One keyboard, two players — WASD on the left, arrow keys on the right, each with their own sprite and score. This video shows the pattern once so you can wire it up in A17.1 without guessing.
Two players on one keyboard means two separate key sets and two separate sets of variables — one for each player. Read this before 2.7.25 Worked Example — Two Paddles, Two Schemes.
sprite.bounciness controls how much energy a sprite keeps after a collision — 0 is a dead thud, 1 is nearly lossless. Read this before 2.7.24a Worked Example — Bounciness Comparison.
Graded: build a two-player Pong-Sumo game — two paddles, one ball, push the ball past the opponent to score. Incorporates two-player input, push-collision physics, a win condition, and at least one joint. Auto-graded.
Three optional stretch prompts for Unit 2.7: build a SliderJoint piston sandbox, a trebuchet that uses a DistanceJoint + HingeJoint together, or a swinging rope chain with three or more HingeJoints.
You've built every piece — mouse input, joints, forces, two-player games, state machines, saves. This short video closes Unit 2.7 and previews what Unit 2.8 asks you to do: design and pitch your own game from scratch.
→ View full module page