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. ๐Ÿš€ ๐ŸŽฎ