JavaScript Game Tutorial
How to Make a Breakout Game in JavaScript (Brick Breaker Tutorial)
Breakout is the classic second game after Pong: one ball, one paddle and a wall of bricks. It teaches collision detection, angle control and input handling in under 150 lines of JavaScript.
Production context
This guide studies Brick Breaker, a published Supagames game. Repository source: games/lvl01/06-bricks.html.
Play Brick Breaker for a minute and notice how hitting the ball with the edge of the paddle sends it off at a sharp angle. That single rule is what makes the game skillful, and the code below shows how it works.
The game runs on a 480 by 360 canvas with eight columns and five rows of bricks. It uses the mouse, the arrow keys or a finger to move the paddle.
1. Set Up the Canvas and Game State
All sizes are constants so the layout is easy to tweak: canvas size, paddle size, ball size and brick dimensions. The ball has a position and a velocity in pixels per update, and the paddle only needs an x position because it stays at the bottom.
The Supagames version advances the game every 16 milliseconds with setInterval, which is close to 60 updates per second and keeps the ball speed the same on any screen.
const W = 480, H = 360;
const paddleW = 80, paddleH = 12, ballSize = 8;
const brickRows = 5, brickCols = 8;
const brickW = 50, brickH = 18, brickPad = 6, brickTop = 40;
let paddleX = W / 2 - paddleW / 2;
let ballX = W / 2, ballY = H - 50;
let ballDX = 3, ballDY = -3;
let score = 0, gameRunning = true, bricks = [];
gameLoop = setInterval(update, 16);
2. Build the Brick Wall
Bricks are plain objects in an array: position, size, colour and an alive flag. Positions come from the row and column with padding between bricks, and each row gets its own colour.
Marking a brick as dead instead of removing it keeps the loop simple and makes the win check a single every() call.
const colors = ["#ff6b6b", "#ff9ff3", "#ffd93d", "#6bcb77", "#4d96ff"];
function buildBricks() {
bricks = [];
for (let r = 0; r < brickRows; r++) {
for (let c = 0; c < brickCols; c++) {
bricks.push({
x: c * (brickW + brickPad) + brickPad,
y: r * (brickH + brickPad) + brickTop,
w: brickW, h: brickH, alive: true, color: colors[r],
});
}
}
}
3. Move the Ball and Bounce Off Walls
Each update adds the velocity to the position. Hitting the left or right wall flips the horizontal velocity, and hitting the ceiling flips the vertical one. There is no bottom wall: a ball that falls past the paddle ends the round.
Keeping velocity as two separate numbers, ballDX and ballDY, makes every bounce a simple sign change.
function update() {
if (!gameRunning) return;
ballX += ballDX;
ballY += ballDY;
if (ballX <= 0 || ballX >= W - ballSize) ballDX = -ballDX; // side walls
if (ballY <= 0) ballDY = -ballDY; // ceiling
if (ballY > H) gameRunning = false; // missed the ball
bouncePaddle();
hitBricks();
draw();
}
4. Aim With the Paddle: Angle From the Hit Position
If the paddle only reversed the ball, the player could never aim. Instead, the horizontal speed depends on where the ball lands: the centre sends it straight up, the edges send it out at a steep angle.
hit is 0 at the left edge and 1 at the right edge, so (hit - 0.5) * 6 gives a horizontal speed between -3 and 3. Forcing ballDY negative and placing the ball on top of the paddle stops it from getting stuck inside.
function bouncePaddle() {
const onPaddle = ballY + ballSize >= H - paddleH &&
ballX + ballSize > paddleX && ballX < paddleX + paddleW;
if (!onPaddle) return;
ballDY = -Math.abs(ballDY); // always bounce upwards
ballY = H - paddleH - ballSize; // move out of the paddle
const hit = (ballX - paddleX) / paddleW;
ballDX = (hit - 0.5) * 6; // -3 at the left edge, +3 at the right
}
5. Break Bricks With Box Collisions
A brick is hit when the ball's bounding box overlaps the brick's box on both axes. The brick dies, the score goes up by 10 and the ball bounces vertically. Breaking out of the loop after one hit prevents the ball from destroying two bricks in a single update and flipping direction twice.
When every brick is dead, the player wins. The draw function reads gameRunning and the bricks array to show either the win or the game over screen.
function hitBricks() {
for (const b of bricks) {
if (!b.alive) continue;
const overlap = ballX + ballSize > b.x && ballX < b.x + b.w &&
ballY + ballSize > b.y && ballY < b.y + b.h;
if (!overlap) continue;
b.alive = false;
ballDY = -ballDY;
score += 10;
scoreEl.textContent = score;
if (bricks.every((br) => !br.alive)) gameRunning = false; // cleared the wall
break; // one brick per update
}
}
6. Bounce Off the Correct Side of a Brick
Always flipping the vertical speed looks wrong when the ball clips the side of a brick. A better rule compares how deep the ball overlaps the brick on each axis: the smaller overlap is the side it came through.
This is an upgrade over the Supagames version and a good exercise once the basic game works.
function bounceFromBrick(b) {
const overlapX = Math.min(ballX + ballSize - b.x, b.x + b.w - ballX);
const overlapY = Math.min(ballY + ballSize - b.y, b.y + b.h - ballY);
if (overlapX < overlapY) ballDX = -ballDX; // hit the left or right side
else ballDY = -ballDY; // hit the top or bottom
}
7. Mouse, Keyboard and Touch Input
The paddle follows the mouse or a finger, and the arrow keys move it in steps. Pointer positions are in screen pixels, but on a phone the canvas is scaled down by CSS, so the position must be converted into canvas units before use.
Subtracting half the paddle width centres the paddle under the pointer, which feels much more precise than lining up its left edge.
document.addEventListener("mousemove", (e) => { mouseX = e.clientX; });
canvas.addEventListener("touchmove", (e) => { mouseX = e.touches[0].clientX; }, { passive: true });
canvas.style.touchAction = "none";
// inside update():
if (mouseX !== null) {
const rect = canvas.getBoundingClientRect();
const x = (mouseX - rect.left) * (W / rect.width); // screen pixels -> canvas units
paddleX = Math.max(0, Math.min(W - paddleW, x - paddleW / 2));
}
8. Build checklist
- Keep sizes in constants and the ball velocity as two numbers.
- Store bricks as objects with an alive flag.
- Bounce off walls and the ceiling; end the round below the paddle.
- Set the ball angle from where it hits the paddle.
- Break at most one brick per update.
- Convert pointer positions into canvas units on scaled screens.
9. FAQ
Who made the original Breakout?
Atari released Breakout in 1976. Steve Wozniak built the prototype hardware, and the game went on to inspire Arkanoid and countless brick breaker clones.
Why does my ball get stuck inside the paddle?
If you only flip the vertical speed, the ball can still overlap the paddle on the next update and flip back. Always force the speed upwards and move the ball to the top edge of the paddle when it bounces.
How do I add more levels?
Store brick layouts as arrays of strings or numbers, build the bricks from the current layout, and load the next one when every brick is dead. Increase the ball speed slightly on each level.
Should I use setInterval or requestAnimationFrame?
requestAnimationFrame gives smoother drawing, but the update must then use delta time or a fixed step so the ball speed does not depend on the screen refresh rate. setInterval with a fixed step is simpler for beginners.