The particle object pool
Creating and deleting objects constantly is slow. A pool pre-allocates a fixed number of particles and reuses them, when one dies, the next spawn revives it instead of allocating memory.
// Reuse the same array slot.
if (!p.active) { // free slot, reuse it
Object.assign(p, { active: true, x, y, vx: rand(-120,120), ... });
return p;
}
Benefits:
- No garbage collection stalls. No churn of new/dead objects for the browser to clean up.
- Bounded cost. The
spawnloop checks at mostmaxparticles, predictable performance. - Reuse is cheap.
spawnjust rewrites a few fields.
The pool is the reason the game can show 100+ sparkles without dropping frames. It's the same idea as reusing a single <canvas>: keep the expensive resource (memory, context) stable, and only change the data inside it.
Without a pool, a 200-particle burst per coin pickup, times dozens of coins, would thrash memory. The pool keeps it smooth.
When to add juice
Juice is not decoration, it's feedback the player reads as information:
- Coin pickup → burst of yellow sparkles + small shake. "You got a coin."
- Hazard hit → big shake + red burst. "You died."
- Camera ease → smooth follow. "The world keeps moving."
The rule: feedback should scale with the action. A coin is a small shake; a hit is a big one. If every action shakes the same, the feedback means nothing.
// Coin: small, bright.
this.camera.addShake(3);
for (let i = 0; i < 12; i++) particles.spawn(coin.x, coin.y, COLORS.coin);
// Hit: large, red.
this.camera.addShake(18);
for (let i = 0; i < 40; i++) particles.spawn(player.x, player.y, COLORS.hazard);
Without this feedback, a coin pickup is silent and invisible, the player won't know they succeeded. Juice closes the loop between action and response.