使用 JavaScript 和 HTML5 Canvas 构建渐进式 Web 游戏

javascripthtml5web developmentfront end technology

近年来,Web 平台发生了重大变化,使开发人员能够创建功能更强大、互动性更强的应用程序。随着 HTML5 和 JavaScript 的推出,开发人员现在不仅拥有构建网站的工具,还有可以直接在浏览器中运行的游戏。

在本文中,我们将通过"打砖块"游戏的实际示例,探讨使用 JavaScript 和 HTML5 Canvas 构建渐进式 Web 游戏的过程。

什么是渐进式 Web 游戏?

渐进式 Web 游戏是基于 Web 的游戏,利用现代 Web 技术提供丰富而身临其境的游戏体验。它们使用 HTML、CSS 和 JavaScript 等标准 Web 技术构建,使其可以在不同的平台和设备上访问。渐进式 Web 游戏的主要功能之一是其能够离线工作并为用户提供类似应用程序的体验。

设置游戏

首先,我们需要一个画布元素,我们可以在其中渲染游戏图形。画布元素提供了一个绘图表面,我们可以在该表面上使用 JavaScript 创建动态和交互式图形。这是我们游戏画布的 HTML 标记 −

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

在上面的代码中,我们定义了一个 id 为"gameCanvas"的画布元素,并指定了画布的宽度和高度。您可以根据自己的喜好随意调整尺寸。

绘制游戏元素

现在我们已经设置好了画布,让我们继续绘制游戏元素。在我们的"打砖块"游戏示例中,我们有三个主要元素:球、桨和砖块。我们将使用 JavaScript 和 HTML5 Canvas API 在画布上绘制这些元素。

球将在画布上表示为一个圆圈。我们定义它的位置 (x, y)、半径和运动 (dx, dy)。我们使用 context.arc() 方法在画布上绘制球。以下是绘制球的代码:

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

桨是一个水平移动的矩形。我们定义它的位置(paddleX)、宽度和高度。我们使用 context.rect() 方法在画布上绘制桨。以下是绘制桨的代码:

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();
         }
      }
   }
}

以下是游戏的完整代码。

示例

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>

在浏览器中打开上述代码时,您可以看到类似于下面所示的输出。

当您用球拍未击中球时,您应该会看到类似于下面所示的输出。

结论

使用 JavaScript 和 HTML5 Canvas 构建渐进式 Web 游戏为开发人员打开了一个充满可能性的世界。通过利用 Web 技术的强大功能,我们可以创建引人入胜且互动性强的游戏,用户可以跨不同平台和设备访问这些游戏。在本文中,我们以"打砖块"游戏为例,探讨了构建过程,涵盖设置、绘制游戏元素、实现游戏逻辑和用户交互。


相关文章