TECH DETAILS

Brick Breaker - Day 1

Advanced Level

Table of Contents

01 / Tech Stack

A classic arcade game built entirely with native browser APIs - no libraries needed.

// Core APIs used const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); // 2D rendering context requestAnimationFrame(gameLoop); // ~60 FPS game loop
Why Canvas 2D?

Canvas 2D is perfect for simple 2D games: no setup overhead, immediate mode rendering, and works on every browser including mobile Safari.

02 / The Game Loop

Every game needs a loop that runs continuously: update state, then render. requestAnimationFrame syncs with the display refresh rate.

Clear Canvas -> Update Positions -> Check Collisions -> Draw Everything -> Request Next Frame
function gameLoop() { // 1. Clear the entire canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // 2. Update ball position ball.x += ball.dx; ball.y += ball.dy; // 3. Check all collisions checkWallCollision(); checkPaddleCollision(); checkBrickCollisions(); // 4. Draw everything drawBricks(); drawPaddle(); drawBall(); drawScore(); // 5. Schedule next frame (~16.67ms later for 60fps) requestAnimationFrame(gameLoop); }
Frame Rate Independence

This simple loop assumes 60fps. For variable frame rates, multiply velocities by deltaTime (time since last frame).

03 / AABB Collision Detection

AABB (Axis-Aligned Bounding Box) is the simplest collision method: check if two rectangles overlap.

The Rectangle Overlap Test

Two rectangles overlap if and only if they overlap on BOTH the X and Y axes simultaneously.

function checkCollision(rect1, rect2) { // Check if rect1 and rect2 overlap return rect1.x < rect2.x + rect2.width && // rect1 left < rect2 right rect1.x + rect1.width > rect2.x && // rect1 right > rect2 left rect1.y < rect2.y + rect2.height && // rect1 top < rect2 bottom rect1.y + rect1.height > rect2.y; // rect1 bottom > rect2 top } // For circle-vs-rect (ball vs brick), we approximate the ball as a rect const ballRect = { x: ball.x - ball.radius, y: ball.y - ball.radius, width: ball.radius * 2, height: ball.radius * 2 };

Brick Collision with Response

function checkBrickCollisions() { for (const brick of bricks) { if (brick.destroyed) continue; if (checkCollision(ballRect, brick)) { brick.destroyed = true; score += 10; // Determine bounce direction based on approach angle const fromLeft = ball.dx > 0; const fromTop = ball.dy > 0; // Calculate overlap to determine which side was hit const overlapX = fromLeft ? (ball.x + ball.radius) - brick.x : (brick.x + brick.width) - (ball.x - ball.radius); const overlapY = fromTop ? (ball.y + ball.radius) - brick.y : (brick.y + brick.height) - (ball.y - ball.radius); // Bounce based on which axis had less penetration if (overlapX < overlapY) { ball.dx *= -1; // Hit side - reverse X } else { ball.dy *= -1; // Hit top/bottom - reverse Y } break; // Only hit one brick per frame } } }

04 / Paddle Bounce Physics

The paddle isn't just a wall - where the ball hits affects its bounce angle. This gives players control over the ball's trajectory.

function checkPaddleCollision() { if (ball.y + ball.radius >= paddle.y && ball.x > paddle.x && ball.x < paddle.x + paddle.width && ball.dy > 0) { // Only if moving downward // Calculate hit position: -1 (left edge) to 1 (right edge) const hitPos = (ball.x - paddle.x) / paddle.width; const normalized = (hitPos - 0.5) * 2; // -1 to 1 // Convert to angle: -60deg to +60deg const maxAngle = Math.PI / 3; // 60 degrees const angle = normalized * maxAngle; // Calculate new velocity (preserve speed) const speed = Math.sqrt(ball.dx**2 + ball.dy**2); ball.dx = speed * Math.sin(angle); ball.dy = -speed * Math.cos(angle); // Always upward } }
Game Feel Tip

Limiting the max angle (60deg here) prevents the ball from bouncing too horizontally, which would make gameplay frustrating.

05 / Brick Grid Generation

Bricks are generated procedurally in a grid pattern with padding and margins.

function createBricks() { const bricks = []; const config = { rows: 5, cols: 8, width: 60, height: 20, padding: 10, offsetTop: 60, offsetLeft: 35 }; // Row colors from top to bottom const colors = ['#ff6b6b', '#ffa502', '#ffd93d', '#6bcb77', '#4d96ff']; for (let row = 0; row < config.rows; row++) { for (let col = 0; col < config.cols; col++) { bricks.push({ x: config.offsetLeft + col * (config.width + config.padding), y: config.offsetTop + row * (config.height + config.padding), width: config.width, height: config.height, color: colors[row], destroyed: false, points: (config.rows - row) * 10 // Top rows worth more }); } } return bricks; }

06 / Touch & Mouse Controls

Supporting both mouse and touch is essential for mobile play.

// Mouse control canvas.addEventListener('mousemove', (e) => { const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const mouseX = (e.clientX - rect.left) * scaleX; // Center paddle on mouse, clamped to canvas paddle.x = Math.max(0, Math.min(canvas.width - paddle.width, mouseX - paddle.width/2) ); }); // Touch control (mobile) canvas.addEventListener('touchmove', (e) => { e.preventDefault(); // Prevent scrolling const touch = e.touches[0]; const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const touchX = (touch.clientX - rect.left) * scaleX; paddle.x = Math.max(0, Math.min(canvas.width - paddle.width, touchX - paddle.width/2) ); }, { passive: false });
Canvas Scaling

When CSS scales the canvas, coordinates don't match. Use getBoundingClientRect() and scale factors to convert screen coords to canvas coords.

07 / Game State Machine

Track the game's current state to control what happens in the game loop.

const GameState = { START: 'start', // Waiting for player to begin PLAYING: 'playing', // Active gameplay PAUSED: 'paused', // Game paused WIN: 'win', // All bricks destroyed LOSE: 'lose' // Out of lives }; let currentState = GameState.START; function gameLoop() { switch (currentState) { case GameState.START: drawStartScreen(); break; case GameState.PLAYING: update(); draw(); // Check win/lose conditions if (bricks.every(b => b.destroyed)) { currentState = GameState.WIN; } if (lives <= 0) { currentState = GameState.LOSE; } break; case GameState.WIN: case GameState.LOSE: drawGameOver(); break; } requestAnimationFrame(gameLoop); }