Creative Coding · Session c16

Objects & Classes

One blueprint, a whole herd.

In c10 you kept many things in an array, and in c13 you chased apples that were small bundles { x, y }. Now imagine a paddock with twelve horses. Each horse needs its own position, its own speed, its own coat colour, and the same behaviour: trot along, turn at the fence, get drawn. Writing that out twelve times would be a nightmare.

The fix is a class: a blueprint that says what every horse has (properties) and what every horse can do (methods). Then you stamp out as many horses as you like with new.

Picture it: one blueprint, three objects
class Horse
has: x, y, speed, shade
can: update(), display()
→
Storm
shade dark
x: 60, y: 50
speed: 2
Luna
shade grey
x: 200, y: 95
speed: -1.5
Honey
shade gold
x: 330, y: 140
speed: 3
Same shape, same abilities, different values. Each horse owns its own copy of the properties. A negative speed just means trotting left.

Live sketch: the paddock

Five horses are trotting. Tap or click inside the paddock to add a new horse at that spot (up to 15). Each one gets its own random speed and coat.

Here is the code, slightly simplified from the sketch above. Read the class first, then the part that uses it.

class Horse { constructor(x, y, speed, shade) { this.x = x; this.y = y; this.speed = speed; this.shade = shade; } update() { this.x += this.speed; if (this.x < 20 || this.x > width - 20) { this.speed *= -1; // turn at the fence } } display() { let dir = this.speed > 0 ? 1 : -1; noStroke(); fill(this.shade); circle(this.x, this.y, 30); // body circle(this.x + dir * 16, this.y - 8, 14); // head } } let herd = []; let coats = ['#8b5a2b', '#3b2a20', '#d6a461', '#9ca3af', '#c2410c']; function setup() { createCanvas(420, 280); for (let i = 0; i < 5; i++) { herd.push(new Horse(random(40, 380), 45 + i * 48, random(1, 3), coats[i])); } } function draw() { background('#bde0a8'); for (let h of herd) { h.update(); h.display(); } } function mousePressed() { let inside = mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height; if (inside && herd.length < 15) { herd.push(new Horse(mouseX, mouseY, random(1, 3), random(coats))); } }

Why bother? The messy way

Before classes, you would have used one array per property, called parallel arrays. It works, but look how much you have to keep in step:

let xs = [], ys = [], speeds = [], coats = []; function draw() { for (let i = 0; i < xs.length; i++) { xs[i] += speeds[i]; if (xs[i] < 20 || xs[i] > width - 20) speeds[i] *= -1; fill(coats[i]); circle(xs[i], ys[i], 30); } } // removing horse number 2 means four separate splices: xs.splice(2, 1); ys.splice(2, 1); speeds.splice(2, 1); coats.splice(2, 1);

Forget one of those four splices and horse 2's speed suddenly belongs to horse 3. With a class, one horse is one object, so herd.splice(2, 1) removes everything about it at once. Compare also the words: h.update() reads like "horse, update yourself".

Four words to remember

Crack the case

Two classic class bugs. Decide what is wrong before you open each one.

Case 1: a red error, "speed is not defined"

update() {
  this.x += speed;
}

The sketch stops the moment the first horse updates.
RevealThe method says speed instead of this.speed. Inside a method, plain speed looks for an ordinary variable called speed, and there is none. A property always needs this. in front. Fix: this.x += this.speed;
Case 2: "Class constructor Horse cannot be invoked without 'new'"

herd.push(Horse(100, 100, 2, '#fff'));

What small word is missing?
Revealnew. Calling Horse(...) like a normal function does not build an object. You must write new Horse(100, 100, 2, '#fff') so that the constructor runs and hands you a fresh horse.
Quick check: what does this print?

Using the Horse class above (the fence turns only happen outside 20 to width minus 20, so ignore them here):

let a = new Horse(50, 100, 2, '#000'); let b = new Horse(50, 100, -3, '#000'); a.update(); a.update(); b.update(); console.log(a.x, b.x);

Answer: 54 47. Horse a moved twice by 2 (50 + 2 + 2 = 54). Horse b moved once by -3 (50 - 3 = 47). Updating one horse never touches the other, because each owns its own x. (Careful: width is the canvas width, so this snippet works once your sketch has called createCanvas.)

Challenges
  1. Modify: add a fifth property size to the constructor (this.size = size;) and use it in display() instead of the fixed 30. Pass a different size when you create each horse, for example random(20, 40). Foals are small, stallions are big.
  2. Predict, then test: if you change the line this.speed *= -1; to this.speed = 0;, what will every horse do at the fence? Write your guess in one sentence, then run it.
  3. Debug: in your sketch delete this. from the this.speed in update() to recreate Case 1. Read the red message in the console, find the line number it names, then fix it.
  4. Create a method: add a new method gallop() that makes the horse's speed twice as big, for example this.speed *= 2;. Call it on the first horse only, with herd[0].gallop();, when the mouse is pressed. Can you make it stop at a top speed with constrain?
  5. Create a new class: write a Firefly class with x, y, a small size, and methods update() (move a random step each frame with random(-2, 2)) and display() (a glowing yellow circle). Make 20 of them in setup() on a dark background. Everything you used for horses carries over.
Next session → c17: Binary & Bits
You have seen how code organises things in memory. Next we zoom all the way down to the smallest piece there is: a switch that is either off or on. Eight of those make one number from 0 to 255, the same range as every red, green and blue value you used for colour in c02.
← All lessons
25:00

Ask your tutor