Building a Brick Breaker Game with JavaScript

🎮 Building a Brick Breaker Game with JavaScript

🏗 Game Structure

📌 HTML Layout

The game consists of a container (div.container) that holds the game elements:

  • Score & Lives Counter
  • Start Game Message
  • Ball (div.ball)
  • Paddle (div.paddle)
  • Bricks (div.brick)
<div>Score: <span class="score">0</span> | Lives: <span class="lives">3</span></div>
<div class="container">
<div class="game-message">Start Game</div>
<div class="ball"></div>
<div class="paddle"></div>
</div>

The game-message div appears initially, allowing players to start the game when they click it.


🎨 CSS Styling

The CSS ensures that:
✔ The paddle stays at the bottom
✔ The ball is round and positioned at the start
✔ The bricks are dynamically created
✔ The game container keeps everything inside the screen

.container {
height: 400px;
width: 80%;
margin: 20px auto;
overflow: hidden;
border: 2px solid white;
position: relative;
background-color: darkgrey;
}

.paddle {
position: absolute;
width: 100px;
height: 20px;
background-color: white;
border-radius: 10px;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
}

.ball {
position: absolute;
width: 20px;
height: 20px;
background-color: white;
border-radius: 50%;
top: 70%;
left: 50%;
display: none;
}

🎮 JavaScript Logic: Making the Game Work

📌 Game Variables

These variables track the game state:

const player = {
gameover: true,
score: 0,
lives: 3,
inPlay: false,
ballSpeedX: 3, // Ball X speed
ballSpeedY: -5, // Ball Y speed
paddleSpeed: 6, // Paddle movement speed
numBricks: 40 // Number of bricks
};
  • gameover → Indicates whether the game is running or ended
  • score → Tracks the number of bricks hit
  • lives → Reduces when the ball falls off
  • ballSpeedX, ballSpeedY → Controls ball movement
  • paddleSpeed → Sets paddle movement speed
  • numBricks → Determines how many bricks appear

🎯 Handling Player Input

The keyboard events move the paddle and start the game.

document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') paddle.movingLeft = true;
if (e.key === 'ArrowRight') paddle.movingRight = true;
if (e.key === 'ArrowUp' && !player.inPlay) player.inPlay = true;
});

document.addEventListener('keyup', (e) => {
if (e.key === 'ArrowLeft') paddle.movingLeft = false;
if (e.key === 'ArrowRight') paddle.movingRight = false;
});

✔ Left and Right arrow keys move the paddle
✔ Up arrow key launches the ball


🏁 Starting the Game

When the player clicks the start message, the game initializes:

function startGame() {
if (player.gameover) {
player.gameover = false;
gameMessage.style.display = "none";
player.score = 0;
player.lives = 3;
player.inPlay = false;
ball.style.display = "block";
resetBallPosition();
setupBricks();
updateScore();
player.animationFrame = requestAnimationFrame(updateGame);
}
}

✔ Hides the start message
✔ Resets the score & lives
✔ Creates new bricks
✔ Starts the animation loop (updateGame)


⚡ Moving the Paddle

Ensures the paddle stays inside the game container:

function movePaddle() {
let currentPos = paddle.offsetLeft;

// Prevent paddle from moving off the screen
if (paddle.movingLeft && currentPos > 0) {
currentPos -= player.paddleSpeed;
if (currentPos < 0) currentPos = 0;
}
if (paddle.movingRight && currentPos < (containerBounds.width - paddle.offsetWidth)) {
currentPos += player.paddleSpeed;
if (currentPos > containerBounds.width - paddle.offsetWidth) {
currentPos = containerBounds.width - paddle.offsetWidth;
}
}
paddle.style.left = `${currentPos}px`;
}

✔ Prevents the paddle from moving offscreen
✔ Moves left or right based on player input


🔵 Moving the Ball

Handles ball movement and collisions:

function moveBall() {
let posBall = { x: ball.offsetLeft, y: ball.offsetTop };

if (posBall.y > (containerBounds.height - 20)) {
player.lives--;
if (player.lives <= 0) endGame();
updateScore();
player.inPlay = false;
return;
}

if (posBall.y < 0) player.ballSpeedY *= -1;
if (posBall.x > (containerBounds.width - 20) || posBall.x < 0) player.ballSpeedX *= -1;

if (isCollide(paddle, ball)) {
let hitPos = (posBall.x - paddle.offsetLeft) / paddle.offsetWidth;
player.ballSpeedX = (hitPos - 0.5) * 6;
player.ballSpeedY = -Math.abs(player.ballSpeedY);
}

document.querySelectorAll('.brick').forEach(brick => {
if (isCollide(brick, ball)) {
player.ballSpeedY *= -1;
brick.remove();
player.score++;
updateScore();
}
});

ball.style.left = `${posBall.x + player.ballSpeedX}px`;
ball.style.top = `${posBall.y + player.ballSpeedY}px`;
}

✔ Ball bounces off walls, paddle, and bricks
✔ Brick disappears when hit
✔ Ball always bounces up after hitting the paddle


🏆 Winning & Losing

  • If player loses all lives, the game ends:
function endGame() {
gameMessage.style.display = "block";
gameMessage.innerHTML = `Game Over<br>Your score: ${player.score}`;
player.gameover = true;
ball.style.display = "none";
cancelAnimationFrame(player.animationFrame);
}

🚀 Conclusion: How It All Works

Game Flow:

1️⃣ Start the game → Click “Start Game”
2️⃣ Paddle moves → Controlled by left/right keys
3️⃣ Ball moves & bounces → Off paddle, walls, and bricks
4️⃣ Bricks disappear → When hit by the ball
5️⃣ Lose a life → If the ball falls below the paddle
6️⃣ Win or lose → Game ends if all bricks are cleared or lives reach 0

This Brick Breaker game demonstrates JavaScript’s ability to create interactive browser-based games with dynamic movement, collision detection, and event-driven controls.

🔥 Try modifying the game! Customize speed, brick layout, colors, or paddle size to make it your own. 🚀🎮

If you’ve been working on a Brick Breaker-style game, you may have encountered outdated JavaScript features. In this blog post, we’ll modernize the code by replacing deprecated features, improving readability, and making it more efficient.

🔧 Key Improvements

  1. Replace e.keyCode with e.key
  2. Remove PNG-based ball image and use CSS styles instead
  3. Use CSS classes instead of inline styles for dynamically created elements
  4. Improve readability and maintainability

🎮 Updated Brick Breaker Game Code

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Brick Breaker Game</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
background-color: #222;
color: white;
}

.container {
height: 400px;
width: 80%;
margin: 20px auto;
overflow: hidden;
border: 2px solid white;
position: relative;
background-color: darkgrey;
}

.score,
.lives {
font-size: 2em;
margin: 10px;
}

.brick {
position: absolute;
width: 80px;
height: 30px;
color: white;
border: 1px solid white;
font-size: 1.4em;
text-align: center;
line-height: 30px;
background-color: #ff5722;
}

.game-message {
position: absolute;
width: 100%;
height: 100px;
text-align: center;
font-size: 2em;
line-height: 100px;
background-color: red;
cursor: pointer;
top: 50%;
transform: translateY(-50%);
}

.ball {
position: absolute;
width: 20px;
height: 20px;
background-color: white;
border-radius: 50%;
top: 70%;
left: 50%;
display: none;
}

.paddle {
position: absolute;
width: 100px;
height: 20px;
background-color: white;
border-radius: 10px;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
}
</style>
</head>
<body>
<div>Score: <span class="score">0</span> | Lives: <span class="lives">3</span></div>
<div class="container">
<div class="game-message">Start Game</div>
<div class="ball"></div>
<div class="paddle"></div>
</div>

<script>
const container = document.querySelector('.container');
const gameMessage = document.querySelector('.game-message');
const ball = document.querySelector('.ball');
const paddle = document.querySelector('.paddle');
const scoreDisplay = document.querySelector('.score');
const livesDisplay = document.querySelector('.lives');

let containerBounds = container.getBoundingClientRect();

const player = {
gameover: true,
score: 0,
lives: 3,
inPlay: false,
ballSpeedX: 3,
ballSpeedY: -5,
paddleSpeed: 6,
numBricks: 40
};

gameMessage.addEventListener('click', startGame);

document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') paddle.movingLeft = true;
if (e.key === 'ArrowRight') paddle.movingRight = true;
if (e.key === 'ArrowUp' && !player.inPlay) player.inPlay = true;
});

document.addEventListener('keyup', (e) => {
if (e.key === 'ArrowLeft') paddle.movingLeft = false;
if (e.key === 'ArrowRight') paddle.movingRight = false;
});

function startGame() {
if (player.gameover) {
player.gameover = false;
gameMessage.style.display = "none";
player.score = 0;
player.lives = 3;
player.inPlay = false;
ball.style.display = "block";
resetBallPosition();
setupBricks();
updateScore();
player.animationFrame = requestAnimationFrame(updateGame);
}
}

function resetBallPosition() {
ball.style.left = `${paddle.offsetLeft + paddle.offsetWidth / 2 - 10}px`;
ball.style.top = `${paddle.offsetTop - 20}px`;
}

function setupBricks() {
document.querySelectorAll('.brick').forEach(brick => brick.remove());

let row = { x: 10, y: 50 };
let bricksPerRow = Math.floor(containerBounds.width / 90);
let totalRows = Math.ceil(player.numBricks / bricksPerRow);

for (let i = 0; i < player.numBricks; i++) {
if (i % bricksPerRow === 0 && i !== 0) {
row.y += 40;
row.x = 10;
}
createBrick(row, i);
row.x += 90;
}
}

function createBrick(pos, index) {
const brick = document.createElement('div');
brick.classList.add('brick');
brick.style.backgroundColor = getRandomColor();
brick.textContent = index + 1;
brick.style.left = `${pos.x}px`;
brick.style.top = `${pos.y}px`;
container.appendChild(brick);
}

function updateGame() {
if (!player.gameover) {
movePaddle();
player.inPlay ? moveBall() : resetBallPosition();
player.animationFrame = requestAnimationFrame(updateGame);
}
}

function movePaddle() {
let currentPos = paddle.offsetLeft;

// Prevent paddle from moving off the screen
if (paddle.movingLeft && currentPos > 0) {
currentPos -= player.paddleSpeed;
if (currentPos < 0) currentPos = 0;
}
if (paddle.movingRight && (currentPos < containerBounds.width - paddle.offsetWidth)) {
currentPos += player.paddleSpeed;
if (currentPos > containerBounds.width - paddle.offsetWidth) {
currentPos = containerBounds.width - paddle.offsetWidth;
}
}
paddle.style.left = `${currentPos}px`;
}

function moveBall() {
let posBall = { x: ball.offsetLeft, y: ball.offsetTop };

if (posBall.y > (containerBounds.height - 20)) {
player.lives--;
if (player.lives <= 0) endGame();
updateScore();
player.inPlay = false;
return;
}

if (posBall.y < 0) player.ballSpeedY *= -1;
if (posBall.x > (containerBounds.width - 20) || posBall.x < 0) player.ballSpeedX *= -1;

if (isCollide(paddle, ball)) {
let hitPos = (posBall.x - paddle.offsetLeft) / paddle.offsetWidth;
player.ballSpeedX = (hitPos - 0.5) * 6;
player.ballSpeedY = -Math.abs(player.ballSpeedY); // Ensure it always bounces upwards
}

document.querySelectorAll('.brick').forEach(brick => {
if (isCollide(brick, ball)) {
player.ballSpeedY *= -1;
brick.remove();
player.score++;
updateScore();
}
});

ball.style.left = `${posBall.x + player.ballSpeedX}px`;
ball.style.top = `${posBall.y + player.ballSpeedY}px`;
}

function isCollide(a, b) {
let aRect = a.getBoundingClientRect();
let bRect = b.getBoundingClientRect();
return !(
aRect.right < bRect.left ||
aRect.left > bRect.right ||
aRect.bottom < bRect.top ||
aRect.top > bRect.bottom
);
}

function updateScore() {
scoreDisplay.textContent = player.score;
livesDisplay.textContent = player.lives;
}

function endGame() {
gameMessage.style.display = "block";
gameMessage.innerHTML = `Game Over<br>Your score: ${player.score}`;
player.gameover = true;
ball.style.display = "none";
cancelAnimationFrame(player.animationFrame);
}

function getRandomColor() {
return `#${Math.floor(Math.random() * 16777215).toString(16)}`;
}
</script>
</body>
</html>

🔥 Improvements

✅ Uses e.key instead of deprecated e.keyCode
✅ Removes PNG ball (uses CSS for rendering)
✅ Uses CSS classes for styling elements
✅ Cleaner & More Readable Code

Now, your game is modernized, responsive, and efficient. 🚀 🎮