碰撞检测
这是 Gamedev Canvas 教程 的 第 7 步,共 10 步。您可以在 Gamedev-Canvas-workshop/lesson7.html 找到完成此课程后代码应有的样子。
我们已经让砖块出现在屏幕上了,但由于球可以穿过它们,所以游戏仍然不是那么有趣。我们需要考虑添加碰撞检测,以便球能够从砖块上弹开并打破它们。
当然,如何实现它由我们自己决定,但计算球是否碰到矩形会很棘手,因为 Canvas 没有为此提供辅助函数。在本教程中,我们将采用最简单的方法。我们将检查球的中心是否与任何给定的砖块发生碰撞。这并不总是能给出完美的结果,而且有更复杂的碰撞检测方法,但对于教授基本概念来说,这已经足够了。
一个碰撞检测函数
为了开始这一切,我们要创建一个碰撞检测函数,该函数将在绘制每一帧时循环遍历所有砖块,并将每个砖块的位置与球的坐标进行比较。为了提高代码的可读性,我们将在碰撞检测的每次循环中定义 b 变量来存储砖块对象。
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 坐标加上其高度。
让我们用代码来实现这一点
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 属性。根据高亮显示的行更新以下代码部分:
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 属性的值,然后再绘制它——如果 status 是 1,则绘制它,但如果它是 0,则表示它被球击中,我们不再希望它出现在屏幕上。按如下方式更新您的 drawBricks() 函数:
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();
}
}
}
}
在碰撞检测函数中跟踪和更新状态
现在我们需要将砖块的 status 属性包含在 collisionDetection() 函数中:如果砖块是活动的(其状态为 1),我们将检查是否发生碰撞;如果发生碰撞,我们将给定砖块的状态设置为 0,这样它就不会被绘制在屏幕上。按如下方式更新您的 collisionDetection() 函数:
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;
}
}
}
}
}
激活我们的碰撞检测
最后要做的是在我们的主 draw() 函数中添加一个对 collisionDetection() 函数的调用。将以下行添加到 draw() 函数中,就在 drawPaddle() 调用下方:
collisionDetection();
Compare your code
现在,每一帧都会与每一个砖块检查球的碰撞检测。现在我们可以摧毁砖块了! :-)
后续步骤
我们确实越来越接近了;让我们继续前进!在第八章中,我们将学习如何跟踪得分和获胜。