弹跳墙壁

这是 游戏开发 Canvas 教程 的 10 个步骤中的 **第 3 步**。您可以在 Gamedev-Canvas-workshop/lesson3.html 中找到完成本课后的源代码。

看到我们的球在移动真是太好了,但它很快就会从屏幕上消失,限制了我们可以从中获得的乐趣!为了克服这个问题,我们将实现一些碰撞检测(将在 后面 更详细地解释),让球弹离画布的四条边。

简单碰撞检测

为了检测碰撞,我们将检查球是否接触(碰撞)墙壁,如果是,我们将相应地改变它的运动方向。

为了简化计算,让我们定义一个名为 ballRadius 的变量,它将保存绘制圆的半径,并用于计算。将此添加到您的代码中,位于现有变量声明的下方

js
const ballRadius = 10;

现在更新 drawBall() 函数中绘制球的代码行到下面这样

js
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);

从顶部和底部弹起

有四面墙可以让球弹起——让我们先关注顶部的那面。我们需要在每一帧都检查球是否接触画布的顶边——如果是,我们将反转球的运动,使其开始向相反方向移动,并保持在可见边界内。记住坐标系从左上角开始,我们可以得到这样的代码

js
if (y + dy < 0) {
  dy = -dy;
}

如果球的位置的 y 值小于零,则通过将它设置为自身的反向来改变 y 轴上的运动方向。如果球以每帧 2 个像素的速度向上移动,现在它将以 -2 个像素的速度“向上”移动,这实际上等同于以每帧 2 个像素的速度向下移动。

上面的代码将处理球从顶边弹起,现在让我们考虑一下底边

js
if (y + dy > canvas.height) {
  dy = -dy;
}

如果球的 y 位置大于画布的高度(记住我们从左上角计算 y 值,因此顶边从 0 开始,底边在 320 像素处,即画布的高度),则让它从底边弹起,如前所述反转 y 轴运动。

我们可以将这两个语句合并为一个,以节省代码冗余

js
if (y + dy > canvas.height || y + dy < 0) {
  dy = -dy;
}

如果两个语句中的任何一个为 true,则反转球的运动。

从左侧和右侧弹起

我们已经覆盖了顶部和底部边缘,所以让我们考虑一下左侧和右侧。实际上非常相似,您只需要重复 x 而不是 y 的语句即可

js
if (x + dx > canvas.width || x + dx < 0) {
  dx = -dx;
}

if (y + dy > canvas.height || y + dy < 0) {
  dy = -dy;
}

此时,您应该将上面的代码块插入 draw() 函数中,位于闭合花括号之前。

球仍然会消失到墙里!

在此时测试您的代码,您将会印象深刻——现在我们有一个球可以弹离画布的四条边!但是,我们还有另一个问题——当球撞到每面墙时,它会稍微陷进去,然后再改变方向

skyblue ball disappearing into the top of the white wall.

这是因为我们正在计算墙的碰撞点和球的中心,而我们应该对球的圆周进行计算。球应该在接触墙后立即弹起,而不是已经深入墙中一半时才弹起,所以让我们稍微调整一下语句来包含这一点。将您添加的最后一段代码更新为以下内容

js
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
  dx = -dx;
}
if (y + dy > canvas.height - ballRadius || y + dy < ballRadius) {
  dy = -dy;
}

当球的中心与墙边的距离正好等于球的半径时,它将改变运动方向。从一个边的宽度减去半径,并在另一个边上加上半径,会给我们一种正确的碰撞检测的印象——球按应有的方式弹离墙壁。

比较您的代码

让我们再次检查一下这部分的完成代码与您得到的结果,并玩一玩

js
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
const ballRadius = 10;
let x = canvas.width / 2;
let y = canvas.height - 30;
let dx = 2;
let dy = -2;

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

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawBall();

  if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
    dx = -dx;
  }
  if (y + dy > canvas.height - ballRadius || y + dy < ballRadius) {
    dy = -dy;
  }

  x += dx;
  y += dy;
}

function startGame() {
  setInterval(draw, 10);
}

document.getElementById("runButton").addEventListener("click", function () {
  startGame();
  this.disabled = true;
});

注意:尝试在球每次撞到墙时将它的颜色更改为随机颜色。

后续步骤

现在我们的球已经能够移动并停留在游戏区域内。在第四章,我们将研究如何实现可控制的挡板——参见 挡板和键盘控制