Building Progressive Web Games with JavaScript and HTML5 Canvas


In recent years, the web platform has evolved significantly, enabling developers to create more powerful and interactive applications. With the introduction of HTML5 and JavaScript, developers now have the tools to build not just websites but also games that can run directly in the browser.

In this article, we will explore the process of building a Progressive Web Game using JavaScript and HTML5 Canvas, with a practical example of a "Brick Breaker" game.

What are Progressive Web Games?

Progressive Web Games are web-based games that leverage modern web technologies to provide a rich and immersive gaming experience. They are built using standard web technologies like HTML, CSS, and JavaScript, making them accessible across different platforms and devices. One of the key features of Progressive Web Games is their ability to work offline and provide an app-like experience to users.

Setting up the Game

To get started, we need a canvas element where we can render our game graphics. The canvas element provides a drawing surface on which we can create dynamic and interactive graphics using JavaScript. Here's the HTML markup for our game canvas −

<canvas id="gameCanvas" width="480" height="320"></canvas>

In the above code, we define a canvas element with an id of "gameCanvas" and specify the width and height of the canvas. Feel free to adjust the dimensions based on your preference.

Drawing the Game Elements

Now that we have our canvas set up, let's move on to drawing the game elements. In our "Brick Breaker" game example, we have three main elements: the ball, the paddle, and the bricks. We will use JavaScript and the HTML5 Canvas API to draw these elements on the canvas.

The Ball

The ball will be represented as a circle on the canvas. We define its position (x, y), radius, and movement (dx, dy). We use the context.arc() method to draw the ball on the canvas. Here's the code for drawing the ball:

function drawBall() {
   context.beginPath();
   context.arc(x, y, ballRadius, 0, Math.PI * 2);
   context.fillStyle = "#0095DD";
   context.fill();
   context.closePath();
}

The Paddle

The paddle is a rectangular shape that moves horizontally. We define its position (paddleX), width, and height. We use the context.rect() method to draw the paddle on the canvas. Here's the code for drawing the paddle:

function drawPaddle() {
   context.beginPath();
   context.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
   context.fillStyle = "#0095DD";
   context.fill();
   context.closePath();
}

The Bricks

The bricks are represented as rectangles arranged in rows and columns. We define their positions, width, height, and status (whether they are active or destroyed). We use nested loops to iterate through the bricks array and draw the active bricks on the canvas. Here's the code for drawing the bricks:

function drawBricks() {
   for (let c = 0; c < brickColumnCount; c++) {
      for (let r = 0; r < brickRowCount; r++) {
         if (bricks[c][r].status === 1) {
            const brickX = c * (brickWidth + brickPadding) + brickOffsetLeft;
            const brickY = r * (brickHeight + brickPadding) + brickOffsetTop;
            bricks[c][r].x = brickX;
            bricks[c][r].y = brickY;
            context.beginPath();
            context.rect(brickX, brickY, brickWidth, brickHeight);
            context.fillStyle = "#0095DD";
            context.fill();
            context.closePath();
         }
      }
   }
}

Below is the complete code for the Game.

Example

index.html

<!DOCTYPE html>
<html>
<head>
   <title>Brick Breaker Game</title>
   <style>
      canvas {
         border: 1px solid #000;
         display: block;
         margin: 0 auto;
      }
   </style>
</head>
<body>
   <canvas id="gameCanvas" width="480" height="320"></canvas>
   <script>
      const canvas = document.getElementById("gameCanvas");
      const context = canvas.getContext("2d");

      const ballRadius = 10;
      let x = canvas.width / 2;
      let y = canvas.height - 30;
      let dx = 2;
      let dy = -2;

      const paddleHeight = 10;
      const paddleWidth = 75;
      let paddleX = (canvas.width - paddleWidth) / 2;
      let rightPressed = false;
      let leftPressed = false;

      const brickRowCount = 3;
      const brickColumnCount = 5;
      const brickWidth = 75;
      const brickHeight = 20;
      const brickPadding = 10;
      const brickOffsetTop = 30;
      const brickOffsetLeft = 30;
      const bricks = [];
      for (let c = 0; c < brickColumnCount; c++) {
         bricks[c] = [];
         for (let r = 0; r < brickRowCount; r++) {
            bricks[c][r] = { x: 0, y: 0, status: 1 };
         }
      }

      let score = 0;

      function drawBall() {
         context.beginPath();
         context.arc(x, y, ballRadius, 0, Math.PI * 2);
         context.fillStyle = "#0095DD";
         context.fill();
         context.closePath();
      }

      function drawPaddle() {
         context.beginPath();
         context.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
         context.fillStyle = "#0095DD";
         context.fill();
         context.closePath();
      }

      function drawBricks() {
         for (let c = 0; c < brickColumnCount; c++) {
            for (let r = 0; r < brickRowCount; r++) {
               if (bricks[c][r].status === 1) {
                  const brickX = c * (brickWidth + brickPadding) + brickOffsetLeft;
                  const brickY = r * (brickHeight + brickPadding) + brickOffsetTop;
                  bricks[c][r].x = brickX;
                  bricks[c][r].y = brickY;
                  context.beginPath();
                  context.rect(brickX, brickY, brickWidth, brickHeight);
                  context.fillStyle = "#0095DD";
                  context.fill();
                  context.closePath();
               }
            }
         }
      }

      function drawScore() {
         context.font = "16px Arial";
         context.fillStyle = "#0095DD";
         context.fillText("Score: " + score, 8, 20);
      }

      function collisionDetection() {
         for (let c = 0; c < brickColumnCount; c++) {
            for (let r = 0; r < brickRowCount; r++) {
               const brick = bricks[c][r];
               if (brick.status === 1) {
                  if (
                     x > brick.x &&
                     x < brick.x + brickWidth &&
                     y > brick.y &&
                     y < brick.y + brickHeight
                  ) {
                     dy = -dy;
                     brick.status = 0;
                     score++;
                     if (score === brickRowCount * brickColumnCount) {
                        alert("Congratulations! You win!");
                        document.location.reload();
                     }
                  }
               }
            }
         }
      }

      function draw() {
         context.clearRect(0, 0, canvas.width, canvas.height);
         drawBricks();
         drawBall();
         drawPaddle();
         drawScore();
         collisionDetection();

         if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
            dx = -dx;
         }
         if (y + dy < ballRadius) {
            dy = -dy;
         } else if (y + dy > canvas.height - ballRadius) {
            if (x > paddleX && x < paddleX + paddleWidth) {
               dy = -dy;
            } else {
               alert("Game Over");
               document.location.reload();
            }
         }

         if (rightPressed && paddleX < canvas.width - paddleWidth) {
            paddleX += 7;
         } else if (leftPressed && paddleX > 0) {
            paddleX -= 7;
         }

         x += dx;
         y += dy;

         requestAnimationFrame(draw);
      }

      function keyDownHandler(e) {
         if (e.key === "Right" || e.key === "ArrowRight") {
            rightPressed = true;
         } else if (e.key === "Left" || e.key === "ArrowLeft") {
            leftPressed = true;
         }
      }

      function keyUpHandler(e) {
         if (e.key === "Right" || e.key === "ArrowRight") {
            rightPressed = false;
         } else if (e.key === "Left" || e.key === "ArrowLeft") {
            leftPressed = false;
         }
      }

      document.addEventListener("keydown", keyDownHandler, false);
      document.addEventListener("keyup", keyUpHandler, false);

      draw();
   </script>
</body>
</html>

When you open the above code in the browser, you can expect an output similar to the one shown below.

When you miss the contact with the ball with your paddle, you should see an output similar to the one shown below.

Conclusion

Building Progressive Web Games with JavaScript and HTML5 Canvas opens up a world of possibilities for developers. By harnessing the power of web technologies, we can create engaging and interactive games that can be accessed by users across different platforms and devices. In this article, we explored the process of building a "Brick Breaker" game as an example, covering the setup, drawing game elements, implementing game logic, and user interaction.

Updated on: 25-Jul-2023

298 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements