The Concept
When I started reading through what matter.js actually does well I kept coming back to the same thing: it’s a rigid body physics engine, and the most satisfying thing you can do with one is watch things fall and bounce. That sounds simple but I think there’s something genuinely compelling about getting physical simulation right visually. The randomness of a ball path through a peg field is the kind of thing you can watch for a long time.
The reference that stuck in my head was the Galton board, those wooden statistical demonstration devices where you drop balls through rows of pegs and they collect into a bell curve at the bottom. What I really like about it is that the pattern emerging at the bottom is a direct consequence of physics, not something you program explicitly. The bell curve isn’t in the code, it falls out of the geometry. That kind of emergent result is exactly what I was interested in building toward.
The sketch is a pachinko-style peg board: balls spawn at the top, fall through seven alternating rows of pegs, and collect into nine buckets at the bottom. Wind force can be pushed left or right with the arrow keys so the distribution shifts over time. The pegs change color the more times they get hit, starting at magenta and shifting toward lime green, which ends up acting as a live heatmap of ball traffic. Clicking anywhere drops a burst of twelve balls at once.
How the Matter.js Side Works
Forces are applied every frame in applyForces(). Every live ball gets a horizontal nudge via Body.applyForce() using the current wind value. Gravity is live-editable with the up and down arrow keys and writes directly to engine.gravity.y each draw cycle, so you can feel the difference between 0.1 and 3.0 .
Collision events are set up with Events.on(engine, 'collisionStart', ...). Two things happen on every collision: pegs get a glowTimer value set to 14 which drives the flash animation, and their hitCount increments so the color can shift over time. Balls also get a tiny random horizontal force on peg impact, which adds unpredictability to each bounce so paths never feel mechanical.
Events.on(engine, 'collisionStart', function(event) {
for (let pair of event.pairs) {
let a = pair.bodyA;
let b = pair.bodyB;
if (a.label === 'peg') { a.glowTimer = 14; a.hitCount++; }
if (b.label === 'peg') { b.glowTimer = 14; b.hitCount++; }
if (a.label === 'ball' && b.label === 'peg') {
Body.applyForce(a, a.position, { x: random(-0.0008, 0.0008), y: 0 });
}
if (b.label === 'ball' && a.label === 'peg') {
Body.applyForce(b, b.position, { x: random(-0.0008, 0.0008), y: 0 });
}
}
});
I like this block because it handles three completely different concerns from the same event listener: visual feedback, statistical tracking, and physics perturbation. The hitCount accumulates over the whole run so the center pegs that get hit most go lime green first while the outer ones stay magenta, which tells you the actual distribution without needing any chart.
Building It Up: Milestones & Challenges
Milestone 1: Pegs, Balls, Physics
The first step was getting the basic setup running: engine, world, static peg bodies, dynamic ball bodies, and the p5 draw loop calling Engine.update(). No collision events, no forces, no visual polish. Just confirming that matter.js and p5 play nicely together and that ball paths through the peg rows look physically believable.
Getting the staggered peg layout right was the first real problem to solve. The rows need to alternate so that every gap in one row has a peg directly below it in the next, which is what forces balls to deflect at every level rather than falling cleanly through. I spent time getting the startX calculation right so the grid stays centered regardless of column count, and tuning the spacing so the balls are large enough to look satisfying but not so large they jam between pegs.
Restitution also matters a lot more than I expected. The default is 0, so balls just thud through the pegs with no bounce and pile up directly below the spawn point. Setting it to 0.7 gave enough bounce to spread the paths out properly.
Milestone 2: Collision Events and Wind
With the base working I added the collision event listener and the wind force. The collision detection in matter.js was straightforward, but making the peg flash readable took some iteration. A single-frame color change was too subtle to notice. Storing a glowTimer counter that counts down from 14 and drives both the color and a slight radius increase made it much more visible.
Wind was the trickiest thing to tune in the whole sketch. Initial values around 0.03 sound small but in matter.js force units they were enormous and balls would fly sideways off the canvas immediately. Getting down to the 0.0005 per-keypress step size took a few rounds of testing before it read as a gentle nudge rather than a gale.
Challenge: Getting Balls to Actually Collect
The bucket and floor setup took more work to get right than I expected. Matter.js bodies collide based on their center position plus radius, so a ground body that looks visually correct can still let fast-moving balls tunnel through it if the physics body isn’t thick enough. I made the ground body significantly taller than it visually appears and positioned its center well below the canvas edge so fast balls always hit it. The bucket dividers needed their Y position calculated precisely to sit flush against the floor without a gap balls could slip through.
There was also a collisionFilter I had on the ball bodies that was incorrectly masking out collisions with all static bodies, meaning balls were passing through both pegs and the ground without interacting. Removing it fixed everything at once.
The Final Result
Balls spawn continuously at the top, fall through seven alternating rows of pegs, and collect in nine buckets at the bottom. The peg color shifts from magenta to lime green based on cumulative hit count. Arrow keys control wind and gravity. Click anywhere to burst twelve balls at the cursor.
Controls:
- Arrow keys — left/right adjusts wind, up/down adjusts gravity
- Click — burst of 12 balls at cursor
Reflection & Future Work
The thing I kept watching was the bucket distribution. When wind is near zero it builds into a rough bell curve exactly the way Galton described, but as wind increases the whole distribution slides sideways and the center buckets start emptying. When the wind reverses there’s a brief moment where the distribution goes almost flat across all nine buckets before the new bell curve forms on the other side. None of that is programmed, it just comes out of the physics.
The hitCount heatmap on the pegs is my favorite visual element in the whole sketch. It always ends up looking the same: the center columns go lime first, then it falls off toward the edges. The physics is confirming the probability distribution in real time and you can see it happening across the peg grid.
What I’d add next:
- Different ball sizes: mixing radii would create more varied paths since larger balls interact with the peg geometry differently
- Timed wind gusts: sharp short bursts instead of a manual input would create more dramatic distribution swings automatically
- Oscillating pegs: if pegs moved slowly on the horizontal axis, the board would never settle into a predictable pattern