弹跳
这是 Gamedev Canvas 教程 的 第 3 步,共 10 步。您可以在 Gamedev-Canvas-workshop/lesson3.html 找到完成本课后代码应有的样子。
看到我们的球在移动很不错,但它很快就从屏幕上消失了,限制了我们的乐趣!为了克服这一点,我们将实现一些碰撞检测(稍后将在 更详细地解释),让球从 Canvas 的四条边反弹。
简单的碰撞检测
要检测碰撞,我们将检查球是否碰到(碰撞到)墙壁,如果碰到,我们将相应地改变其移动方向。
为了简化计算,让我们定义一个名为 ballRadius 的变量,它将保存绘制圆的半径并用于计算。将此添加到您的代码中,位于现有变量声明的某个位置下方。
const ballRadius = 10;
现在,将 drawBall() 函数中绘制球的代码更新为如下所示:
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
弹跳顶部和底部
球有四面墙可以反弹 — 让我们先关注顶部。我们需要在每一帧都检查球是否碰到 Canvas 的顶部边缘 — 如果是,我们将反转球的移动方向,使其开始向相反方向移动并保持在可见边界内。记住坐标系从左上角开始,我们可以得出这样的结论:
if (y + dy < 0) {
dy = -dy;
}
如果球位置的 y 值小于零,则通过将其设置为自身的反值来改变 y 轴上的移动方向。如果球以每帧 2 像素的速度向上移动,现在它将以 -2 像素的速度“向上”移动,这实际上等于以每帧 2 像素的速度向下移动。
上面的代码处理了球从顶部边缘反弹的情况,现在让我们考虑底部边缘:
if (y + dy > canvas.height) {
dy = -dy;
}
如果球的 y 位置大于 Canvas 的高度(记住我们从左上角开始计算 y 值,所以顶部边缘从 0 开始,底部边缘是 320 像素,即 Canvas 的高度),然后通过像以前一样反转 y 轴移动来使其从底部边缘反弹。
我们可以将这两个语句合并为一个,以节省代码冗余。
if (y + dy > canvas.height || y + dy < 0) {
dy = -dy;
}
如果两个语句中的任何一个为 true,则反转球的移动。
弹跳左侧和右侧
我们已经处理了顶部和底部边缘,所以让我们考虑左侧和右侧。实际上非常相似,您所要做的就是为 x 重复 y 的语句。
if (x + dx > canvas.width || x + dx < 0) {
dx = -dx;
}
if (y + dy > canvas.height || y + dy < 0) {
dy = -dy;
}
此时,您应该将上面的代码块插入到 draw() 函数中,就在闭合花括号之前。
球仍然会消失到墙里!
此时测试您的代码,您会感到惊讶 — 现在我们有了一个能从画布四边反弹的球!然而,我们遇到了另一个问题 — 当球碰到每面墙时,它会在改变方向之前稍微沉入墙里。

这是因为我们正在计算墙壁和球心之间的碰撞点,而我们应该为球的周长进行计算。球应该在碰到墙壁后立即反弹,而不是在已经半个球体都在墙里之后,所以让我们稍微调整一下我们的语句以包含这一点。将您最后添加的代码更新为如下所示:
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
dx = -dx;
}
if (y + dy > canvas.height - ballRadius || y + dy < ballRadius) {
dy = -dy;
}
当球心与墙壁边缘之间的距离等于球的半径时,它将改变移动方向。从一个边缘的宽度中减去半径,并在另一个边缘加上半径,让我们对正确的碰撞检测有印象 — 球应该像预期的那样从墙壁上反弹。
Compare your code
让我们再次检查此部分的代码是否与您的代码相符,并进行一些尝试。
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);
}
const runButton = document.getElementById("runButton");
runButton.addEventListener("click", () => {
startGame();
runButton.disabled = true;
});
注意: 尝试在球碰到墙壁时将其颜色更改为随机颜色。
后续步骤
现在我们已经到了球既能移动又能停留在游戏板上的阶段。在第四章中,我们将实现一个可控制的挡板 — 请参阅 挡板和键盘控制。