🔥 The Prompt That Sparked This Tutorial
🤖 Prompt given to GitHub Copilot (Claude Sonnet 4.6) — 2026-05-01
“Claude, you are a professional coder and high school teacher and you notice a student, new to p5js, has (with permission) used AI to create the artwork and animation attached. They admit they used the ‘Particle Class’ as created with AI, but are uncertain as to what it does or why it is needed. Can you create a primer, a tutorial, allAboutTheParticleClassAndClassDesign.html that fits into our SW ecosystem that will serve as a step-by-step guide as to how to create and use a class in general, and the ‘Particle Class’ in particular.”
💣 Why This Page Exists
When you click the canvas in origBomb.html, 150 glowing sparks
fly outward, slow down, fade, and disappear. Each spark is a Particle object,
born from the Particle class at the bottom of
origBombSketch.js.
That class was written with AI help, which is completely legitimate — professional developers use AI every day. But there’s a critical difference between using AI output and understanding AI output. If you can’t explain what the code does, you can’t debug it, extend it, or reuse the technique in your next project.
This tutorial fixes that. By the end you will be able to:
- Explain what a class is in plain English (and in code).
- Define constructor, property, method, and instance.
- Walk through every line of the
Particleclass and explain exactly what it does. - Explain why a class was the right tool for this animation.
- Modify the explosion yourself with confidence.
📐 Part 1 — What IS a Class?
A class is a blueprint. A blueprint is not a building; it is the plan that tells you how to build a building.
🏗️ Architecture analogy
An architect draws one blueprint for a house. A contractor uses that blueprint to build ten identical (but separate) houses. Change one house’s paint color and the other nine are unaffected.
In JavaScript:
• The class = the blueprint — written once.
• Each object = a house built from that blueprint.
• We call each object an instance of the class.
In origBombSketch.js, the Particle class is written once.
Every time you click, 150 new Particle objects (instances) are built from
that one blueprint — each in its own random position, heading in its own random
direction.
Imagine tracking just five sparks without a class. You’d need separate variables for every piece of data:
WITHOUT a class — five sparks, explosion of variableslet spark1x, spark1y, spark1vx, spark1vy, spark1alpha, spark1size;
let spark2x, spark2y, spark2vx, spark2vy, spark2alpha, spark2size;
let spark3x, spark3y, spark3vx, spark3vy, spark3alpha, spark3size;
let spark4x, spark4y, spark4vx, spark4vy, spark4alpha, spark4size;
let spark5x, spark5y, spark5vx, spark5vy, spark5alpha, spark5size;
// ... and we need 150 sparks, not 5 ... nightmare.
function draw() {
// Update spark 1
spark1vx *= 0.95;
spark1x += spark1vx;
spark1alpha -= 4;
// Draw spark 1
fill(255, 100, 0, spark1alpha);
ellipse(spark1x, spark1y, spark1size);
// Update spark 2 — copy/paste 148 more times ...
}
That is clearly unworkable. A class lets us package all of a spark’s data and behaviour together, then stamp out as many copies as we need:
WITH a class — any number of sparks, clean codelet particles = []; // one array holds ALL the sparks
function mousePressed() {
for (let i = 0; i < 150; i++) {
particles.push(new Particle(mouseX, mouseY)); // stamp: new instance
}
}
function draw() {
// Update & draw every spark — doesn't matter if there are 3 or 3000
for (let i = particles.length - 1; i >= 0; i--) {
particles[i].update(); // each spark updates ITSELF
particles[i].show(); // each spark draws ITSELF
if (particles[i].finished()) {
particles.splice(i, 1);
}
}
}
🧬 Part 2 — The Anatomy of a JavaScript Class
Every JavaScript class is built from the same four ingredients:
| Ingredient | What it is | Keyword |
|---|---|---|
| class declaration | Names the blueprint and opens the definition block | class Particle { } |
| constructor | A special method called automatically when a new instance is created. Sets up the object’s starting data. | constructor() |
| properties | The variables that belong to one specific instance. Each instance has its own copy. | this.x = ... |
| methods | Functions defined inside the class. They can read and change the instance’s own properties. | update() { } |
Here is the simplest possible class — a coin that knows its value and can report it:
MINIMAL EXAMPLE — read this before the Particle classclass Coin { // ← class declaration: names the blueprint constructor(value) { // ← constructor: runs when we write 'new Coin(...)' this.value = value; // ← property: 'this' means "this specific coin" this.flipped = false; } flip() { // ← method: a function the coin can perform this.flipped = !this.flipped; } report() { // ← another method return `I am worth ${this.value} cents.`; } } // ← end of class definition // ── Using the blueprint ─────────────────────────────────────────────────── let penny = new Coin(1); // 'new' stamps out an instance; constructor runs let quarter = new Coin(25); // a completely separate instance penny.flip(); console.log(penny.report()); // "I am worth 1 cents." console.log(quarter.report()); // "I am worth 25 cents." console.log(penny.flipped); // true — penny was flipped console.log(quarter.flipped); // false — quarter was NOT affected
this:
Inside a class, this always refers to the specific instance the
method is being called on. When you call penny.flip(), inside
flip() the word this means penny. When you call
quarter.flip(), this means quarter. Same method,
different object. That’s the magic.
💥 Part 3 — The Particle Class, Line by Line
Here is the complete Particle class from origBombSketch.js,
exactly as written. We will dissect it section by section below.
class Particle { constructor(x, y) { this.pos = createVector(x, y); this.vel = p5.Vector.random2D().mult(random(5, 20)); this.acc = createVector(0, 0.15); this.alpha = 255; this.size = random(4, 120); this.color = color(random(200, 255), random(0, 100), 0); } update() { this.vel.mult(0.95); this.vel.add(this.acc); this.pos.add(this.vel); this.alpha -= 4; } show() { noStroke(); fill(red(this.color), green(this.color), blue(this.color), this.alpha); ellipse(this.pos.x, this.pos.y, this.size); } finished() { return this.alpha < 0; } }
Four sections: one constructor and three methods
(update, show, finished).
Let’s take them one at a time.
constructor(x, y) — Building a Spark from Scratch
The constructor runs once per particle, the instant new Particle(mouseX, mouseY)
is called. Its job: give the new spark its starting values.
constructor(x, y) {
this.pos = createVector(x, y);
// pos = position: a p5 Vector storing (x, y) as one object.
// x and y come from the mouse click — every spark starts at the cursor.
this.vel = p5.Vector.random2D().mult(random(5, 20));
// vel = velocity: direction + speed.
// random2D() picks a random direction (any angle, 0–360°).
// mult(...) scales that unit vector to a random speed between 5 and 20.
// Result: each spark blasts off in a unique direction at a unique speed.
this.acc = createVector(0, 0.15);
// acc = acceleration: a constant downward push each frame.
// x=0 means no left/right push. y=0.15 means a small, steady pull DOWN.
// This IS gravity. It's constant, so it never changes after construction.
this.alpha = 255;
// alpha = opacity. p5 uses 0 (invisible) to 255 (fully solid).
// Starting at 255 means the spark is born fully visible.
this.size = random(4, 120);
// size = diameter of the spark circle.
// The wide range (4–120) is deliberate: a mix of tiny sparks and
// big glowing blobs makes the explosion feel more natural.
this.color = color(random(200, 255), random(0, 100), 0);
// color = the spark's RGB color, stored as a p5 color object.
// Red channel: 200–255 → always warm/hot
// Green channel: 0–100 → some sparks lean yellow/orange, some are deep red
// Blue channel: 0 → no blue — fire never looks icy cold
}
acc at all?
Gravity never changes for a particle, so it could have been a constant outside the class.
Storing it as this.acc is a common OOP pattern that makes the class more
flexible — a future version might give underwater sparks a different (or even
upward) acceleration. Designing for that possibility now costs nothing.
origBombSketch.js, change
random(5, 20) to random(2, 8). The sparks will still explode
outward but much more lazily. Change it to random(15, 40) for a violent
blast. Notice that only the constructor needs to change.
update() Method — Physics Every Frame
draw() in p5 runs roughly 60 times per second. Each time, every living
particle calls update() to advance its physics and fade a little.
update() {
this.vel.mult(0.95);
// Air drag: multiply the velocity by 0.95 each frame.
// After 1 frame: vel × 0.95¹ = 95% of original speed
// After 10 frames: vel × 0.95¹⁰ ≈ 60% of original speed
// After 30 frames: vel × 0.95³⁰ ≈ 21% of original speed
// The spark decelerates naturally, just like a real object in air.
this.vel.add(this.acc);
// Apply gravity: add the downward acceleration to the velocity.
// Every frame the downward component of vel grows by 0.15.
// Sparks shot upward eventually stop, then fall — just like physics.
this.pos.add(this.vel);
// Move: add the current velocity to the position.
// This is the Euler integration step — the single most common
// physics update pattern in game programming.
this.alpha -= 4;
// Fade: subtract 4 from opacity each frame.
// 255 ÷ 4 ≈ 64 frames ≈ 1 second at 60fps.
// Each spark lives for roughly one second.
}
🏀 The basketball analogy
Imagine throwing a basketball into the air. Each moment:
1. Gravity pulls it down a little faster (vel += acc).
2. Air resistance slows it slightly (vel *= 0.95).
3. It moves to its new position (pos += vel).
That is exactly what update() simulates.
this.acc = createVector(0, 0.15)
in the constructor to createVector(0, 0). With no gravity, sparks will
drift outward in perfectly straight lines like stars in space.
Change it to createVector(0, -0.15) and sparks will arc upward like
a rocket launch.
show() Method — Drawing the Spark
After physics, each particle draws itself.
show() knows exactly where to draw, what color, and how transparent
because all that information lives in the particle’s own properties.
show() {
noStroke();
// Explicitly clear any stroke left over from previous draw calls.
// Never rely on inherited drawing state — own it, declare it.
fill(red(this.color), green(this.color), blue(this.color), this.alpha);
// p5's color() object doesn't accept a fourth alpha argument directly
// in fill(), so we unpack the RGB channels with red(), green(), blue()
// and supply this.alpha as the fourth argument to fill().
// As this.alpha counts down from 255→0, the spark fades to invisible.
ellipse(this.pos.x, this.pos.y, this.size);
// Draw a circle at the particle's current (x, y) position.
// this.pos.x and this.pos.y are the numeric coordinates inside
// the p5 Vector we stored in the constructor.
}
this.color is a p5 color object — a convenient way to
store RGB values together. But fill() needs plain numbers when you
want to add an alpha channel. The red(), green(),
and blue() helper functions extract those numbers.
A Stage 2 improvement would replace this with named color constants stored
as arrays (see the
Stage 2 Laundry List
for the pattern).
finished() Method — Knowing When to Die
Each particle needs a way to announce “I’m invisible — remove me.”
finished() is a simple predicate method: it asks a yes/no
question and returns a boolean.
finished() {
return this.alpha < 0;
// Returns true when alpha has been decremented below zero.
// At that point the particle is completely invisible.
// The main draw loop uses this signal to remove the particle
// from the array, freeing memory.
}
Here is how the main draw() loop uses all four parts together:
// Iterate backwards! When we splice() an element out mid-loop, // iterating forward would skip the element that slides into its spot. // Going backwards means the already-checked items are the ones we skip. for (let i = particles.length - 1; i >= 0; i--) { particles[i].update(); // physics: move + fade particles[i].show(); // drawing: paint on canvas if (particles[i].finished()) { particles.splice(i, 1); // remove dead spark from the array } }
splice(3, 1) removes the element at index 3, every element
at index 4, 5, 6… shifts left by one. If you are looping
forward, the loop’s counter just moved past the element that
slid into index 3, silently skipping it. Iterating backwards avoids this
problem entirely because we always remove from the end of what we’ve
already visited.
🗺️ Part 4 — The Big Picture
Every single spark in the explosion follows the same lifecycle. Reading the flow helps you see why each part of the class exists:
-
Click! —
mousePressed()fires. A loop runs 150 times, each iteration callingnew Particle(mouseX, mouseY). -
Birth —
For each
new, the constructor runs instantly. It gives the spark its position (at the cursor), a random velocity (random direction and speed), gravity (constant downward acceleration), full opacity (alpha = 255), a random size, and a random reddish-orange color. -
Living —
Every frame,
draw()loops through theparticlesarray. For each surviving spark it callsupdate()(move + fade) thenshow()(draw). -
Fading —
alphadrops by 4 each frame. The spark grows dimmer every frame until it is completely invisible. -
Death —
Once
alpha < 0,finished()returnstrue. The main loop removes the particle from the array withsplice(). JavaScript will garbage-collect the object. Memory is reclaimed.
console.log(particles.length); inside draw(). Watch the
number spike to 150 on click, then gradually count back down to 0 as sparks expire.
That is the full particle lifecycle, logged in real time.
💭 Part 5 — Why Was a Class the Right Tool?
Object-Oriented Programming (OOP) is a design philosophy, not a rule. Here are the three specific reasons a class was the right choice for this explosion:
| Reason | What it means for the Particle |
|---|---|
| Encapsulation |
Each spark owns its data (pos, vel, alpha…)
and the code that changes that data (update()).
The main loop never needs to know how a spark moves; it just
calls update(). The implementation is hidden inside the class.
|
| Independent state |
Each of the 150 sparks has its own velocity, alpha, size, and color.
They are independent — one spark fading doesn’t affect another.
This is only possible because each instance has its own copy of every
this.___ property.
|
| Scalability |
The management loop works identically whether there are 2 sparks or
2,000 sparks. Change 150 to 500 in
mousePressed() for a bigger explosion — not a single
other line of code needs to change.
|
💬 “A class is the right tool when you have multiple things of the same type that each need their own independent data and behavior.” That sentence describes this explosion perfectly.
✨ The S.P.A.R.K. of Every Class
Use S.P.A.R.K. as a mental checklist whenever you design a new class:
| ✨ | Letter | Key Question | Particle Answer |
|---|---|---|---|
| S | State | What data does each instance need to remember? | pos, vel, acc, alpha, size, color |
| P | Plan (constructor) | What are the starting values, and what is passed in? | Starting position (x, y) passed in; all else randomized |
| A | Actions (methods) | What can each instance do? | update() advances physics; show() draws; finished() reports lifespan |
| R | Reuse | How many instances will I need? | 150 per click — a class is the only practical choice |
| K | Key this |
Does every property reference use this.? |
Yes — this.pos, this.vel, this.alpha… every one |
✅ Comprehension Checklist
| ✨ | I can explain… | Where to find it |
|---|---|---|
| ⬜ | The difference between a class and an instance | Part 1, Step 1 |
| ⬜ | Why 150 sparks without a class would be unworkable | Part 1, Step 2 |
| ⬜ | What constructor, property, and method mean |
Part 2, Step 3 |
| ⬜ | What this refers to inside a method |
Part 2, Step 3 (golden rule) |
| ⬜ | All 6 properties the Particle constructor sets up |
Part 3, Step 4 |
| ⬜ | How air drag is simulated with one multiplication | Part 3, Step 5 |
| ⬜ | How gravity is simulated with one addition | Part 3, Step 5 |
| ⬜ | Why we iterate the particle array backwards when removing | Part 3, Step 7 |
| ⬜ | The full lifecycle of one particle (birth → living → fading → death) | Part 4, Step 8 |
| ⬜ | What encapsulation means and why the Particle uses it | Part 5 |
| ⬜ | The S.P.A.R.K. checklist for designing a new class | S.P.A.R.K. section |
🚀 Next Steps
-
Clean up the magic numbers.
The
Particleconstructor still contains bare literals (5, 20, 0.15, 0.95, 4, 4, 120…). The Stage 2 Laundry List shows exactly how to name them all. - Design your own class. Pick something in your own sketch that you have multiple copies of (stars, bubbles, raindrops, cars). Apply S.P.A.R.K. to design a class for it. If you get stuck, use this Particle class as your starting template.
-
Add a new method.
What if each
Particlealso knew how toshrink()— reducingthis.sizeeach frame as well as fading? Try adding that method. Call it inside the draw loop afterupdate(). -
Explore the SketchWave shape classes.
The shapes in
shapeClasses/(likeswCircle.js,swPolygon.js) are professional examples of the same class design principles you just learned. Read one with fresh eyes — you’ll recognize the pattern. -
See the professional version of this exact idea:
SWParticle. Everything you learned in this tutorial — constructor,update(),draw(), an array of instances — was the direct inspiration forSWParticle, a class now built in to the SketchWave JS library. It supports multiple shapes (circle, square, star, mix), HSB color, gravity, drag, auto-emit, and even a spin speed for square and star particles.
• SWParticle Live Demo — play with every parameter in real time.
• SWParticle Reference Page — full constructor options, properties, methods, and source code.