๐ŸŸจ My First Browser Game ยท Episode 3 of 6 ยท See All Episodes
๐Ÿงฑ Episode 3 ยท Improver ยท Canvas Physics

Smash the Wall!
Browser Breakout

The canvas classic: mouse-driven paddle, spin physics, a glowing brick wall, lives and levels, plus canvas gradients and particle effects that make the browser version the prettiest one yet.

๐Ÿ‘ถ Ages 10+ โฑ๏ธ ~1.5 Hours ๐ŸŸจ JavaScript โœ“ Free
๐Ÿ–ฑ๏ธ Mouse control โšช Bounce physics ๐Ÿงฑ Brick objects โœจ Particles ๐ŸŽจ Canvas gradients โค๏ธ Lives & levels
โญ
0 XP
Level 1
๐Ÿ”ฅ0
Your Progress 0 / 6 steps
1
๐Ÿ–ฑ๏ธ
Canvas, Loop & Mouse Paddle
Setup plus mousemove-driven paddle
Active
๐ŸŽฏ
Goal for this step

Standard canvas setup with a paddle glued to the mouse.

  • 1New breakout.html: 700ร—520 canvas, requestAnimationFrame loop (Episode 1 muscle memory).
  • 2Track the mouse: a mousemove listener stores the x relative to the canvas (subtract canvas.getBoundingClientRect().left).
  • 3Paddle x = mouseX โˆ’ 55, clamped 0-590.
  • 4Draw the paddle with ctx.roundRect, canvas has rounded rectangles built in now!
breakout.html
let mouseX = 350;
canvas.addEventListener("mousemove", function(e) {
    const rect = canvas.getBoundingClientRect();
    mouseX = e.clientX - rect.left;
});

// in the loop:
const padX = Math.max(0, Math.min(590, mouseX - 55));
ctx.fillStyle = "#ff7700";
ctx.beginPath();
ctx.roundRect(padX, 480, 110, 14, 7);
ctx.fill();
โœ๏ธ
Fill in the Blanks
+15 XP
Mouse position must subtract the canvas offset from (). The paddle centre sits at the cursor by subtracting (half its width).
๐Ÿง 
Knowledge Check
+15 XP
Why subtract getBoundingClientRect().left from e.clientX?
ATo slow the mouse
BclientX is relative to the PAGE, subtracting the canvas position converts it to canvas coordinates
CTo keep the number positive
2
โšช
Ball & Wall Physics
Velocity object, three-wall bounce
Locked
๐ŸŽฏ
Goal for this step

A ball object bouncing off the walls and ceiling.

  • 1Ball object: { x, y, vx: 4, vy: -4, r: 7 }, a circle this time, drawn with arc().
  • 2Move, then bounce: x edges flip vx, top flips vy, accounting for the radius on each side.
  • 3Floor = miss (lives later).
  • 4Draw with a radial gradient for a glossy sphere, canvas can do shading!
breakout.html
const ball = { x: 350, y: 300, vx: 4, vy: -4, r: 7 };

ball.x += ball.vx;
ball.y += ball.vy;
if (ball.x - ball.r <= 0 || ball.x + ball.r >= 700) ball.vx = -ball.vx;
if (ball.y - ball.r <= 0) ball.vy = -ball.vy;

const g = ctx.createRadialGradient(ball.x-2, ball.y-2, 1, ball.x, ball.y, ball.r);
g.addColorStop(0, "#fff");
g.addColorStop(1, "#bbb");
ctx.fillStyle = g;
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
ctx.fill();
โŒจ๏ธ
Code Challenge
+20 XP
Bounce off the side walls, respecting the radius:
breakout.html
if (ball.x - ball. <= 0 || ball.x + ball.r >= )
    ball.vx = ;
๐Ÿ’ก Hint: A circle touches the wall when its centre is one radius away; then the horizontal velocity flips.
๐Ÿง 
Knowledge Check
+15 XP
ctx.arc(x, y, r, 0, Math.PI * 2) drawsโ€ฆ
AA square
BA full circle, the angles run from 0 to 2ฯ€ radians (360ยฐ)
CHalf a circle
3
๐Ÿ“
Paddle Rebound with Spin
The aim-with-your-catch mechanic, browser edition
Locked
๐ŸŽฏ
Goal for this step

Rebound with player-controlled angle off the paddle.

  • 1Circle-rectangle collision, simplified: ball bottom inside the paddle strip while falling.
  • 2Flip vy, then spin: (ball.x โˆ’ paddleCentre) / 12 becomes vx (min magnitude 1).
  • 3Third language writing this, you could do it in your sleep. THAT is transferable skill.
  • 4Cap |vx| at 7 to keep the game playable.
breakout.html
if (ball.vy > 0 &&
    ball.y + ball.r >= 480 && ball.y + ball.r <= 494 &&
    ball.x >= padX && ball.x <= padX + 110) {
    ball.vy = -ball.vy;
    let spin = (ball.x - (padX + 55)) / 12;
    spin = Math.max(-7, Math.min(7, spin));
    ball.vx = spin === 0 ? 1 : spin;
}
โœ๏ธ
Fill in the Blanks
+15 XP
The rebound only happens while ball.vy > (falling). The spin is the distance from the paddle divided by 12, capped at ยฑ.
๐Ÿง 
Knowledge Check
+15 XP
You have now written paddle spin in Java, Python/C++ and JavaScript. What ACTUALLY differed?
AThe physics idea
BOnly syntax, the game design logic was identical every time
CThe direction of gravity
4
๐Ÿงฑ
Bricks & Neon Glow
The wall as objects, drawn with shadowBlur
Locked
๐ŸŽฏ
Goal for this step

Build the brick wall and give it a neon glow with canvas shadows.

  • 1Bricks: array of objects from nested loops, x, y, colour by row, alive.
  • 2Canvas glow is one property: ctx.shadowBlur = 12; ctx.shadowColor = brickColour; before fillRect.
  • 3IMPORTANT: reset shadowBlur to 0 after the bricks or EVERYTHING glows.
  • 4Collision + destruction: same alive-flag pattern as C++ Episode 3, flip vy, score by row.
breakout.html
const COLOURS = ["#f87171","#fb923c","#facc15","#4ade80","#60a5fa"];
let bricks = [];
for (let r = 0; r < 5; r++)
    for (let c = 0; c < 10; c++)
        bricks.push({ x: c*68+12, y: r*26+50, row: r, alive: true });

// drawing:
for (const b of bricks) {
    if (!b.alive) continue;
    ctx.shadowBlur = 12;
    ctx.shadowColor = COLOURS[b.row];
    ctx.fillStyle = COLOURS[b.row];
    ctx.fillRect(b.x, b.y, 64, 22);
}
ctx.shadowBlur = 0;      // stop the glow leaking!
โŒจ๏ธ
Code Challenge
+20 XP
Give bricks a neon glow, then turn it off:
breakout.html
ctx. = 12;
ctx.shadowColor = COLOURS[b.row];
ctx.fillRect(b.x, b.y, 64, 22);
// after the brick loop:
ctx.shadowBlur = ;
๐Ÿ’ก Hint: The blur property creates the glow; setting it back to zero stops it applying to the paddle and ball.
๐Ÿง 
Knowledge Check
+15 XP
Canvas state (fillStyle, shadowBlurโ€ฆ) persists between draw calls. That meansโ€ฆ
AYou set it once ever
BWhatever you set STAYS until changed, forget to reset and later drawings inherit it. Classic canvas bug
CEach shape resets it
5
โœจ
Particle Explosions
Every brick bursts into flying sparks
Locked
๐ŸŽฏ
Goal for this step

Spawn a burst of physics particles when a brick dies.

  • 1A particle: { x, y, vx, vy, life }, spawned 8 per broken brick with random velocities.
  • 2Each frame: move, add slight gravity, life โˆ’= 1, remove dead ones (filter).
  • 3Draw as 3px squares in the brickโ€™s colour with alpha = life/30 (fade out via ctx.globalAlpha, reset it after!).
  • 4This is THE effect that makes players say "juicy". 15 lines.
breakout.html
let particles = [];

function burst(x, y, colour) {
    for (let i = 0; i < 8; i++)
        particles.push({
            x: x, y: y,
            vx: (Math.random() - 0.5) * 6,
            vy: (Math.random() - 0.5) * 6 - 1,
            life: 30, colour: colour
        });
}

// each frame:
for (const p of particles) {
    p.x += p.vx;  p.y += p.vy;
    p.vy += 0.15;                 // particle gravity
    p.life--;
}
particles = particles.filter(p => p.life > 0);

for (const p of particles) {
    ctx.globalAlpha = p.life / 30;
    ctx.fillStyle = p.colour;
    ctx.fillRect(p.x, p.y, 3, 3);
}
ctx.globalAlpha = 1;
โœ๏ธ
Fill in the Blanks
+15 XP
Each burst spawns particles with random velocities. Fading uses ctx. scaled by remaining life, reset to afterwards.
๐Ÿง 
Knowledge Check
+15 XP
Particles are pure decoration, no gameplay effect. Why bother?
AThey increase score
BFeedback: the brain reads bursts as impact. Games FEEL better with consequence made visible
CBrowsers require them
6
โค๏ธ
Lives, Levels & Ship It
The complete game loop with speed-up levels
Locked
๐ŸŽฏ
Goal for this step

Finish with lives, escalating levels and the end screens.

  • 1Lives 3; floor miss โ†’ recentre and serve; 0 โ†’ GAME OVER overlay, click to restart.
  • 2Wall cleared โ†’ level++, rebuild bricks, ball speed ร— 1.15, a proper difficulty ladder.
  • 3HUD: score left, level centre, hearts right.
  • 4Zip through the checklist: mouse paddle โœ“ spin โœ“ glow โœ“ particles โœ“ levels โœ“. Send the file to a friend, it just WORKS in their browser. That is the JS advantage.
breakout.html
let lives = 3, level = 1;

// wall cleared:
if (bricks.every(b => !b.alive)) {
    level++;
    bricks = buildWall();
    ball.x = 350; ball.y = 300;
    ball.vx *= 1.15;
    ball.vy = -Math.abs(ball.vy) * 1.15;
}
โŒจ๏ธ
Code Challenge
+20 XP
Detect the cleared wall with an array method:
breakout.html
if (bricks.(b => !)) {
    level++;
    bricks = buildWall();
}
๐Ÿ’ก Hint: The method asks "is this true for ALL items?", and the condition is that each brick is not alive.
๐Ÿง 
Knowledge Check
+15 XP
bricks.every(b => !b.alive) reads asโ€ฆ
ADelete every brick
B"Is every brick dead?", true only when the whole wall is destroyed
CRevive all bricks
๐ŸŽ‰๐Ÿ†๐ŸŽฎโœจ๐ŸŽ‰
Workshop Complete!
Breakout with particles and gradients, the prettiest wall-smasher of the series. Episode 4: Snake, the grid returns!
0
Total XP
1
Level
0
Best Streak
0%
Accuracy
โ–ถ Episode 4: Snake โ†’
โญ View My Progress & Certificates

This workshop was free and took many hours to build. If it helped you learn something new, consider supporting the project.

☕ Support on Ko-fi