碰撞检测

这是 Gamedev Canvas 教程 的 10 个步骤中的 **第 7 步**。您可以在 Gamedev-Canvas-workshop/lesson7.html 中找到完成本课后代码应有的样子。

我们已经让砖块出现在屏幕上了,但是游戏仍然不够有趣,因为球会穿过它们。我们需要考虑添加碰撞检测,以便球可以反弹并击碎砖块。

当然,我们可以选择如何实现这一点,但是计算球是否接触到矩形可能很困难,因为 Canvas 中没有为此提供任何辅助函数。在本教程中,我们将采用最简单的方法。我们将检查球的中心是否与任何给定的砖块发生碰撞。这种方法并非每次都能得到完美的结果,并且还有更复杂的方法来进行碰撞检测,但对于教授基本概念来说,这已经足够了。

碰撞检测函数

为了开始这一切,我们想要创建一个碰撞检测函数,该函数将在所有砖块中循环,并在绘制每一帧时将每个砖块的位置与球的坐标进行比较。为了提高代码的可读性,我们将在碰撞检测的每个循环中定义变量 b 来存储砖块对象

js
function collisionDetection() {
  for (let c = 0; c < brickColumnCount; c++) {
    for (let r = 0; r < brickRowCount; r++) {
      const b = bricks[c][r];
      // calculations
    }
  }
}

如果球的中心位于我们其中一块砖块的坐标内,我们将改变球的方向。为了使球的中心位于砖块内部,以下四个语句都需要为真

  • 球的 x 坐标大于砖块的 x 坐标。
  • 球的 x 坐标小于砖块的 x 坐标加上其宽度。
  • 球的 y 坐标大于砖块的 y 坐标。
  • 球的 y 坐标小于砖块的 y 坐标加上其高度。

让我们将其写成代码

js
function collisionDetection() {
  for (let c = 0; c < brickColumnCount; c++) {
    for (let r = 0; r < brickRowCount; r++) {
      const b = bricks[c][r];
      if (x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
        dy = -dy;
      }
    }
  }
}

将上述代码块添加到您的代码中,位于 keyUpHandler() 函数下方。

被击中后使砖块消失

上述代码将按预期工作,并且球会改变方向。问题是砖块仍然停留在原来的位置。我们必须找到一种方法来消除已经被球击中的砖块。我们可以通过添加一个额外的参数来指示我们是否要将每个砖块绘制到屏幕上。在初始化砖块的代码部分,让我们为每个砖块对象添加一个 status 属性。根据突出显示的行更新代码的以下部分

js
let 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 };
  }
}

接下来,在绘制砖块之前,我们将在 drawBricks() 函数中检查每个砖块的 status 属性的值——如果 status1,则绘制它,但如果为 0,则表示它被球击中了,我们不再希望它出现在屏幕上。请按如下所示更新您的 drawBricks() 函数

js
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;
        ctx.beginPath();
        ctx.rect(brickX, brickY, brickWidth, brickHeight);
        ctx.fillStyle = "#0095DD";
        ctx.fill();
        ctx.closePath();
      }
    }
  }
}

在碰撞检测函数中跟踪和更新状态

现在我们需要在 collisionDetection() 函数中包含砖块的 status 属性:如果砖块处于活动状态(其状态为 1),我们将检查是否发生了碰撞;如果发生了碰撞,我们将把该砖块的状态设置为 0,这样它就不会绘制到屏幕上了。请按如下所示更新您的 collisionDetection() 函数

js
function collisionDetection() {
  for (let c = 0; c < brickColumnCount; c++) {
    for (let r = 0; r < brickRowCount; r++) {
      const b = bricks[c][r];
      if (b.status === 1) {
        if (
          x > b.x &&
          x < b.x + brickWidth &&
          y > b.y &&
          y < b.y + brickHeight
        ) {
          dy = -dy;
          b.status = 0;
        }
      }
    }
  }
}

激活碰撞检测

最后要做的事情是将对 collisionDetection() 函数的调用添加到我们的主要 draw() 函数中。将以下行添加到 draw() 函数中,紧跟在 drawPaddle() 调用之后

js
collisionDetection();

比较您的代码

现在,每一帧都会对每个砖块检查球的碰撞检测。现在我们可以摧毁砖块了!:-)

后续步骤

我们现在肯定越来越接近了;让我们继续!在第八章中,我们将了解如何 跟踪分数并获胜