The Creative Coder’s Field Guide

Everything You Need to Survive and Thrive with p5.js

Creative Coding · Week 2

Moving-Parts

Week 2 is one idea wearing many outfits: a variable is a labeled container for a piece of data. Name it, fill it, change it, and the canvas starts to move.

JavaScript + p5.js · editor.p5js.org · Understand → Know → Do
01The idea

A variable is a labeled container

Last week you borrowed p5's ready-made variables (width, mouseX) without making any of your own. This week you make your own.

A variable is a container with a name on the label and a piece of data inside. The data can be:

  • a number: 200, 3.5, -40
  • some text in quotes (called a string): "hello"
  • true or false (called a boolean): is the mouse pressed right now?

Despite the name, variables don't have to vary. Some hold a value that changes every frame; others are set once and never touched again. What they all give you is a name, so instead of the mystery number 347 scattered through your code, you have sunX, and everyone (including you, next week) knows what it means.

02Syntax

The assignment command: let and =

A thirty-second reminder from last week: syntax is JavaScript's grammar, the rules for how words, symbols, and punctuation have to be arranged, as opposed to vocabulary like p5's function names. And the grammar isn't a suggestion: get it wrong and the computer just stops. Last week you only had to recognize it on sight; from here on you're writing it, so the examples get more exact.

Two pieces of that grammar do all the work this week. let creates a variable; = puts a value into one.

let x = width / 2; the keyword “make a new variable” the name you choose the value worked out first = assigns, right to left: work out the right side, store it in the left
One statement, three jobs: let makes the container, x names it, and = fills it. The right side always runs first: width / 2 becomes 200, and the result is what lands in x.
  • let x = 200;: create x and fill it, in one line.
  • let x; then later x = 200;: the same thing, split in two. (You'll see why the split matters in section 06.)
  • The left side of = must be a variable name. The right side can be anything that produces a value: a number, some math, another variable, mouseX… Code like that has a name: an expression (section 04).

What counts as a legal name

  • Letters and numbers, no spaces; start with a letter. Capitalization counts: sunx and sunX are two different variables.
  • That's what the computer accepts. What makes a name good is a style question (section 10).
03Updating

Changing what's in the container

Assigning again replaces what was inside, and JavaScript has shorthands for the most common replacements.

WriteSay it asThe long way
x = 5set x to 5n/a
x += 3grow x by 3x = x + 3
x -= 3shrink x by 3x = x - 3
x *= 2double xx = x * 2
x /= 2halve xx = x / 2
x++add 1 to xx = x + 1
x--subtract 1 from xx = x - 1

Three spellings, one meaning

x++, x += 1, and x = x + 1 do exactly the same thing. The long form shows what's really happening; the other two are abbreviations of it. Read all three fluently, then write whichever you like. You'll meet all of them in other people's code.

= is not “equals.” In algebra, x = x + 1 is impossible: nothing equals itself plus one. In JavaScript it's not a claim, it's a command: take what's in x, add 1, put the result back in x. Right side first, then store. Once that clicks, the whole line reads naturally.

04Expressions

Expressions: code that becomes a number

That “right side of =” deserves its proper name. An expression is any piece of code the computer can work out (evaluate) down to a single value. This week, every expression we write evaluates to a number.

The syntax of an expression

Numerical expressions are built from three kinds of ingredients:

  • Numbers, written plainly: 200, 1.5, -40. (Already an expression, just a very short one.)
  • Variables, which stand in for the number they hold: width, x, mouseX.
  • Operators (+ - * /), each sitting between two ingredients: width / 2, x + 70.

Two grammar rules complete the picture. Parentheses group: (width + 10) / 2 says “add first, then divide.” And any expression can be an ingredient in a bigger one: mouseX - pmouseX is an expression, so (mouseX - pmouseX) * 2 is too. That nesting is how small pieces grow into exactly the value you need.

Order of operations works like math class: * and / before + and -. When in doubt, add parentheses: they cost nothing and say exactly what you mean.

(width + 10) / 2 look up the variables: width holds 400 (400 + 10) / 2 parentheses first: 400 + 10 → 410 410 / 2 divide: 410 / 2 → 205 205 ← one value, ready to store or pass
Evaluation in slow motion. In real life the whole collapse happens instantly, every single time the line runs, so in x = (width + 10) / 2;, the 205 is what = actually stores.

Where expressions can go

Anywhere the code needs a single number, a whole expression can stand in its place:

  • On the right side of =: x = width / 2;
  • As a parameter: ellipse(width / 2, height / 2, 40, 40); needs no in-between variable.
  • Inside a bigger expression: (width + height) / 2.

One thing an expression is not: a complete command. width / 2 on a line by itself does nothing: it's a value with nowhere to go, a phrase without a sentence. It only matters once something uses it: an assignment stores it, a function call receives it.

Expressions come in other flavors too: ones that evaluate to text, or to true/false. Those arrive in later weeks. This week: numbers.

05Lifecycle

A variable's life in four steps

Every variable you'll write this semester goes through some or all of these, in this order:

  1. Declare (let x;): make the container.
  2. Initialize (x = width / 2;): put a first value in.
  3. Update (x = x + 1;): change it. Optional: variables don't have to vary.
  4. Use (ellipse(x, y, 40, 40);): read what's inside. Technically optional too, since nothing breaks if you never use it, but then what was the point?
declare let x; initialize x = width/2; update x = x + 1; use ellipse(x, …) make the box first value in change it (or don't) read what's inside once, before the first frame every frame, inside draw()
Declaring and initializing are one-time jobs. Updating and using happen inside draw(), so they repeat about 60 times a second, which is where motion will come from (section 09).
06Scope

Where a variable lives

Where you declare a variable decides who can see it: that's called its scope.

  • Declared inside a function (between its braces), the variable is local. It's born when the function runs and gone the moment it ends. Nobody else can see it.
  • Declared at the top of the sketch, outside every function: the variable is global. Both setup() and draw() can see it, and it survives from frame to frame.

That survival is the whole game. A variable declared inside draw() is rebuilt from scratch 60 times a second, so it can never accumulate change. For anything that should move, the variable has to live outside, where the frames can't reset it.

YOUR SKETCH FILE let x; ← outside every function: global function setup() { createCanvas(400, 300); x = width / 2; let secret = 5; } sees x ✓ · owns secret function draw() { background(220); ellipse(x, 150, 40, 40); print(secret); } sees x ✓ · secret? error ✗ secret was born inside setup() and died the moment setup() ended.
Global x is visible everywhere and keeps its value between frames. Local secret belongs to setup() alone: asking for it in draw() is an error.

So why not initialize at the top too?

Tempting: let x = width / 2; on line one. But line one runs before createCanvas() has made the canvas, so at that moment width doesn't hold anything useful yet. The value has to be assigned after the canvas exists, which means inside setup().

The Week 2 house pattern: declare at the top (global), initialize in setup(), update in draw(). Almost every sketch this week has that shape.

07Debug

Seeing inside: print()

A variable's contents are invisible while the sketch runs, and half of debugging is answering one question: what is actually in this container right now? print() answers it. Whatever you hand it appears in the console, the panel at the bottom of the editor.

print(x);            // 200
print("x is", x);    // x is 200  (label it so you know which print this is)
print(mouseX);       // wherever the mouse is this frame
  • print() is p5's name for it. Plain JavaScript calls it console.log(): same job, works everywhere, and it's what you'll see outside p5.
  • Careful where you put it: a print() inside draw() fires ~60 times a second. That's exactly what you want when watching a variable change, and a firehose when you don't.
  • When a sketch misbehaves, don't stare at the code: print() the variable you're suspicious of and look.
08Relationships

Variables define relationships

Here's the deeper reason variables matter, beyond tidy names: once a value has a name, other code can be written in terms of it.

let x = 100;

ellipse(x, 200, 50, 50);        // the anchor
ellipse(x + 70, 200, 50, 50);   // always 70 to the right of it
ellipse(x, 140, 30, 30);        // always directly above it

Change x once and all three move together, still in formation. The positions aren't three separate facts anymore: they're one fact plus two relationships.

A set of rules like this (“compute this from that”) is an algorithm. You've already been using them: (width / 2, height / 2) doesn't say where the center is, it says how to find the center of any canvas. And ellipse(mouseX, mouseY, 40, 40) is a relationship with the mouse itself, re-evaluated every frame, which is why the circle follows the cursor.

Hard-coded numbers describe one picture. Relationships describe every picture the rule allows: that's the shift from drawing to programming.

09Animation

The illusion of motion

Nothing on the canvas ever actually moves.

What p5 gives you is about 60 still pictures every second, the frames from last week. Make each frame slightly different from the one before, and your eye does the rest. It's a flip-book; it's every movie ever made.

So each run of draw() follows the same three-beat recipe:

  1. background(...): clear the previous frame away.
  2. Draw the scene at the variable's current value.
  3. Nudge the variable a little, so the next frame lands somewhere new.
let x;

function setup() {
  createCanvas(400, 300);
  x = 0;
}

function draw() {
  background(220);            // 1. clear
  ellipse(x, 150, 40, 40);    // 2. draw at the current x
  x = x + 1;                  // 3. nudge: next frame is 1px further
}
frame 1 frame 2 frame 3 frame 4 clear → draw → nudge x, about 60 times every second skip background() and nothing is erased: every frame's circle stays behind. Trails are a classic look, so make them a choice, not an accident.
The dashed ghost is where the circle was one frame ago, already cleared by background() before the new one is drawn, just a sliver behind. That clear-and-redraw is the entire secret of animation.

One more piece: p5 keeps a variable called frameCount, which counts how many frames have been drawn so far. It follows the full lifecycle without you lifting a finger, so ellipse(frameCount, 150, 40, 40) slides right all on its own. A free clock, ticking 60 times a second.

10Style

Coding style: writing for humans

The computer will run almost anything. Style is for the other readers of your code: your classmates, your teacher, and above all you, two weeks from now.

Indentation and Tidy Code

Indentation makes structure visible: every line inside a pair of braces steps in one level, so a glance tells you what belongs to setup() and what belongs to draw(). The computer ignores indentation completely, which is exactly why keeping it right is your job, not JavaScript's.

  • The editor does the work for you: Tidy Code (Cmd/Ctrl - Shift - F) re-indents and re-spaces the whole sketch in one keystroke. Run it constantly; there's no reason to hand in messy spacing when the fix is free.
  • Messy indentation is where missing braces hide. When a sketch won't run and you can't see why, tidy first: a stray or absent } usually becomes obvious the moment the indentation snaps into place.

Names that mean something

  • Multi-word names use camelCase: eyeSize, ballSpeed, skyBlue. First word lowercase, every following word capitalized.
  • Pick names that say what's inside. x is fine for a position; thing2 is a note to your future self that says nothing.
  • Related variables should look related: x and y, or ballX and ballY, not ballX and height2.

Comments: two styles

A comment is a note the computer skips over entirely: it exists only for humans. JavaScript gives you two kinds:

  • Line comment (//): everything from the slashes to the end of that line is ignored. Toggle one on any line with Cmd/Ctrl - /.
  • Block comment (/* … */): everything between the markers is ignored, even across many lines. The natural home for the paragraph at the top of a sketch saying what it is.
/*
  Drifting dot: Week 2 exercise.
  Starts at the left edge and drifts right
  2 pixels every frame.
*/

let x;                        // horizontal position, in pixels

function setup() {
  createCanvas(400, 300);
  x = 0;
}

function draw() {
  background(220);
  // ellipse(x, 75, 90, 90);   switched off while I test
  ellipse(x, 150, 40, 40);
  x += 2;
}
  • Comment the why, not the what. x += 2; // add 2 to x restates the code and helps no one; // drift right, 2px per frame records a decision.
  • Commenting out is a debugging move, not just documentation: switch a suspicious line off with // instead of deleting it, run, and switch it back when you've learned what you needed.

Style isn't decoration. Most “bugs” this week turn out to be a stray brace or a mystery number, and tidy indentation, honest names, and a comment or two are how you find them fast. Tidy before you ask anyone (including yourself) to read your code.

11Reference

p5's built-in variables

p5 maintains a whole shelf of pre-filled containers, declared, initialized, and updated for you, fresh every frame. Your only job is step four of the lifecycle: use them.

VariableHolds
width, heightThe canvas's size in pixels, set by createCanvas()
mouseX, mouseYThe cursor's position, right now (this frame)
pmouseX, pmouseYThe cursor's position one frame ago (the p is for previous)
frameCountHow many frames have been drawn since the sketch started
mouseIsPressedtrue while a mouse button is held down; false otherwise
keyIsPressedtrue while any key is held down
keyThe most recent key pressed, as text: "a", "G", " "
  • mouseX - pmouseX is how far the mouse traveled in one frame: in other words, its speed. Two variables and a minus sign, and you've measured motion. (That's a relationship, section 08.)
  • mouseIsPressed and keyIsPressed hold booleans, the true/false data type from section 01. For now, try print(mouseIsPressed) and watch it flip; making the sketch decide things with them arrives with if in a coming week.
12Practice

Be able to do: the four-corner journey

One exercise, worth doing slowly: move an ellipse from the center of the canvas to each of the four corners, on a canvas that is not a square. The not-a-square part looks like a footnote; it's what keeps you honest about width and height.

Set the stage

The house pattern from section 06: declare at the top, initialize in setup().

let x;
let y;

function setup() {
  createCanvas(400, 300);   // not a square, on purpose
  x = width / 2;            // 200
  y = height / 2;           // 150
}

Aim at a corner

For each corner, decide two things before typing: does x need to grow or shrink to get there? Does y? (Remember: y grows downward.)

(0, 0) (400, 0) (0, 300) (400, 300) x−− y−− x++ y−− x−− y++ x++ y++ (width/2, height/2) = (200, 150)

Write one journey

Bottom-right first, since both values grow:

function draw() {
  background(220);
  ellipse(x, y, 40, 40);
  x++;    // or x += 1, or x = x + 1: your pick
  y++;
}

Run it: the ellipse drifts steadily down and to the right, and off the canvas. That's one corner done.

Now do all four

Same sketch, only the signs on the two update lines change:

CornerUpdate lines in draw()
Top-left (0, 0)x--; y--;
Top-right (400, 0)x++; y--;
Bottom-left (0, 300)x--; y++;
Bottom-right (400, 300)x++; y++;

Two honest notes: the ellipse won't stop at the corner. It sails right off the canvas. Stopping needs if, which is coming. And if anything looks wrong along the way, print(x, y) in draw() (section 07) and watch the numbers travel.

Hitting the corners exactly

Watch a journey closely and you'll notice the ellipse actually leaves through an edge near its corner, not the corner itself. That's the non-square canvas at work: from the center, x has 200 pixels to travel but y only has 150, so at equal speeds, y runs out of canvas first and the ellipse slips out early.

The fix: give the speeds the same ratio as the distances.

x += 2;
y += 1.5;   // 2 : 1.5, the same ratio as 200 : 150

Now both arrive at the same moment, and the path runs straight through the corner. Notice what you just did: the speeds are a relationship (section 08), and they encode the shape of the canvas.