移动球
这是Gamedev Canvas 教程的第 2 步,共 10 步。您可以在 Gamedev-Canvas-workshop/lesson2.html 找到完成本课后应有的源代码。
您已经知道如何从上一篇文章中绘制一个球,现在让我们让它动起来。从技术上讲,我们将在每一帧中清除屏幕上的球,然后在略有不同的位置重新绘制它,以产生移动的错觉——就像电影中的移动一样。
定义绘制循环
为了在每一帧中不断更新画布绘制,我们需要定义一个绘制函数,它会一遍又一遍地运行,每次使用不同的变量值来改变精灵位置等。您可以使用 JavaScript 定时函数一遍又一遍地运行一个函数。稍后在教程中,我们将看到 requestAnimationFrame() 如何帮助绘制,但我们一开始将使用 setInterval() 来创建一些循环逻辑。
删除您 HTML 文件中除前两行之外的所有 JavaScript 代码,并在它们下方添加以下内容。draw() 函数将在 10 毫秒后由 setInterval 执行
function draw() {
// drawing code
}
setInterval(draw, 10);
由于 setInterval 的无限性质,draw() 函数将每 10 毫秒永远调用一次,或者直到我们停止它。现在,让我们绘制球——在您的 draw() 函数中添加以下内容
ctx.beginPath();
ctx.arc(50, 50, 10, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
立即尝试更新后的代码——球应该在每一帧中被重绘。
让它动起来
目前您不会注意到球在不断重绘,因为它没有移动。让我们改变这一点。首先,我们将不再使用硬编码的位置 (50,50),而是将初始位置定义在 Canvas 的底部中心区域,并用变量 x 和 y 来存储,然后使用这些变量来定义绘制圆的位置。
首先,在 draw() 函数上方添加以下两行,以定义 x 和 y
let x = canvas.width / 2;
let y = canvas.height - 30;
接下来,更新 draw() 函数,以便在 arc() 方法中使用 x 和 y 变量,如下面的高亮行所示
function draw() {
ctx.beginPath();
ctx.arc(x, y, 10, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
现在是重要部分:我们希望在绘制完每一帧后给 x 和 y 添加一个很小的数值,使其看起来球在移动。我们将这些小数值定义为 dx 和 dy,并将它们的值分别设置为 2 和 -2。在 x 和 y 变量定义下方添加以下内容
let dx = 2;
let dy = -2;
最后一件事是更新每一帧中的 x 和 y 与我们的 dx 和 dy 变量,以便球将在每次更新时在新的位置绘制。在您的 draw() 函数中添加以下两个新行,如下所示
function draw() {
ctx.beginPath();
ctx.arc(x, y, 10, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
x += dx;
y += dy;
}
再次保存您的代码并在浏览器中尝试。这工作正常,尽管看起来球在后面留下了拖尾

在每一帧之前清除画布
球留下拖尾是因为我们在每一帧中都绘制了一个新圆,而没有移除前一个。不用担心,因为有一个清除画布内容的方法:clearRect()。此方法接受四个参数:矩形左上角的 x 和 y 坐标,以及矩形右下角的 x 和 y 坐标。此矩形覆盖的整个区域将被清除之前绘制的所有内容。
将以下高亮的新行添加到 draw() 函数
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(x, y, 10, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
x += dx;
y += dy;
}
保存代码并再次尝试,这次您将看到球移动而没有拖尾。每 10 毫秒,画布就会被清除,蓝色的圆(我们的球)将在给定的位置绘制,并且 x 和 y 的值将为下一帧更新。
清理我们的代码
我们将在接下来的几篇文章中向 draw() 函数添加越来越多的命令,因此最好保持其尽可能简洁和干净。让我们开始将球绘制代码移到一个单独的函数中。
用以下两个函数替换现有的 draw() 函数
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, 10, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
x += dx;
y += dy;
}
Compare your code
您可以在下面的实时演示中查看本文档的完成代码,并进行尝试以更好地理解其工作原理。
注意: 实时示例会在这些页面上自动运行,因此我们添加了一个“开始游戏”按钮。这有助于避免游戏自动开始并过于频繁地触发警报或其他事件。
<canvas id="myCanvas" width="480" height="320"></canvas>
<button id="runButton">Start game</button>
canvas {
background: #eeeeee;
}
button {
display: block;
}
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
let x = canvas.width / 2;
let y = canvas.height - 30;
const dx = 2;
const dy = -2;
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, 10, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
x += dx;
y += dy;
}
function startGame() {
setInterval(draw, 10);
}
const runButton = document.getElementById("runButton");
runButton.addEventListener("click", () => {
startGame();
runButton.disabled = true;
});
注意: 尝试更改移动球的速度或移动方向。
后续步骤
我们已经绘制了我们的球并使其动起来,但它一直在画布边缘消失。在第三章中,我们将探讨如何让它 从墙壁上反弹。