Building Breakout: A Tiny Offline Breakout Game With Emoji Chaos
Breakout started as a plain Breakout clone: a paddle, a ball, and rows of bricks. Over a few sessions it grew into a small offline-ready PWA with power-ups, animations, generated sound, and a few surprises. Here is what we built and how it works.
The Game
- Shaped levels. Nine hand-made brick patterns (pyramid, heart, 0, 8, 6, A, W, M) drawn from tiny string maps. After level 9, symmetric random layouts take over.
- Emoji bricks. Every brick carries an emoji, and a few of them are special.
- Fully offline. It runs as an installable PWA and works in airplane mode.
- No audio files. All sound is generated live with the Web Audio API.
The Special Bricks
| Emoji | What it does |
|---|---|
| ⚽️ | Piercing ball for 10 seconds. It smashes through bricks without bouncing. |
| 🌈 | Rainbow splash. It destroys every brick in a small radius. |
| 🍎 | A falling apple. Catch it and it grows, goes BOOM, and turns the ball big for 8 seconds while giving it a speed burst. |
| 🍇 | A falling grape. Catch it and it grows, goes BOOM, and fires two fast bullet-balls upward in a fan. |
| 💎 | Bubble shield. Every brick gets a glowing shield for 30 seconds and needs two hits. |
| 🦖 | A dino stomps across the screen while the whole screen shakes. Big levels get two dinos. |
| ❤️ | Extra life. It appears on every 5th level, or when a bomb leaves you at 1 life. |
| 💣 | Two hidden bomb carriers per level. Only one bomb falls at a time, and catching it on the paddle costs a life (never the last one). |
The Polish
- Grow, then boom. Caught fruit swells up with a rising charge sound, then explodes into particles and a shockwave ring.
- Bullet speed. Shot balls launch at about twice the speed, with a yellow trail, and ease back to normal over about a second and a half.
- Heart drama. When a bomb costs you a life, the ❤️ in the HUD shakes, cracks into 💔, sheds pieces, and heals back.
- Smooth big ball. The ball scales up and down instead of snapping.
Bugs We Hunted
- The freeze. One error inside the animation frame killed the whole loop. The fix was a
try/catcharound each frame, a check for removed bricks during a rainbow splash, and a fallback for browsers withoutroundRect. - Splice while iterating. Removing items from an array during
forEachskips the next one. All loops now run backwards. - Tunneling. Fast balls could skip through thin bricks, so each frame is split into sub-steps of 8px or less.
- Stacking timers. Power-up timers now count down in game time, so they can’t stack, can’t fire late, and pause with the game.
- Phone shake lag. Moving the whole HTML container every frame was heavy on phones. The shake now happens inside the canvas.
Tiny Code Tour
Levels are strings. "01100110" is one row, and 1 means a brick:
js
["01100110","11111111","11111111","01111110","00111100"] // heart
Sound without files. One oscillator and a fading gain gives a beep:
js
osc.frequency.setValueAtTime(freq, t);
gain.gain.exponentialRampToValueAtTime(0.0001, t + dur);
Speed burst that fades. Each ball has a boost multiplier that decays every frame:
js
this.boost = this.boost > 1.01 ? 1 + (this.boost - 1) * 0.965 : 1;
Tunneling fix. Move in small steps and check bricks after each one:
js
const n = Math.max(1, Math.ceil(ball.v / 8));
for (let s = 0; s < n; s++) { ball.advance(n); hitBricks(ball); }
Offline in a few lines. The service worker caches the page and images. It uses network-first for the page and cache-first for assets, and the cache name includes the file’s modified time, so uploading a new index.php refreshes everything automatically:
php
$VERSION = filemtime(__FILE__);
echo "const CACHE = 'breakout-v{$VERSION}';";
One-bomb rule. New bombs wait in a queue until the last one is gone:
js
if (fruits.some(f => f.type === 'bomb')) bombQueue.push({x, y});
else fruits.push(new PowerUp(x, y, '💣', 'bomb'));
Stack
One index.php file, plain Canvas 2D, Web Audio, a service worker and a manifest, plus four images (ball, paddle, block, fav). There are no frameworks and no build step.
What’s Next
- A separate maskable icon and 192px icon for Android
- High score saving
- Pause button and a smarter touch offset so a finger doesn’t cover the paddle
- More brick types with different hit points
Building it was mostly a loop of “add one silly idea, break something, fix it.” Each fix made the game a little tighter, and each silly idea made it a little more fun.
