移动球

这是Gamedev Canvas 教程的第 2 步,共 10 步。您可以在 Gamedev-Canvas-workshop/lesson2.html 找到完成本课后应有的源代码。

您已经知道如何从上一篇文章中绘制一个球,现在让我们让它动起来。从技术上讲,我们将在每一帧中清除屏幕上的球,然后在略有不同的位置重新绘制它,以产生移动的错觉——就像电影中的移动一样。

定义绘制循环

为了在每一帧中不断更新画布绘制,我们需要定义一个绘制函数,它会一遍又一遍地运行,每次使用不同的变量值来改变精灵位置等。您可以使用 JavaScript 定时函数一遍又一遍地运行一个函数。稍后在教程中,我们将看到 requestAnimationFrame() 如何帮助绘制,但我们一开始将使用 setInterval() 来创建一些循环逻辑。

删除您 HTML 文件中除前两行之外的所有 JavaScript 代码,并在它们下方添加以下内容。draw() 函数将在 10 毫秒后由 setInterval 执行

js
function draw() {
  // drawing code
}
setInterval(draw, 10);

由于 setInterval 的无限性质,draw() 函数将每 10 毫秒永远调用一次,或者直到我们停止它。现在,让我们绘制球——在您的 draw() 函数中添加以下内容

js
ctx.beginPath();
ctx.arc(50, 50, 10, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();

立即尝试更新后的代码——球应该在每一帧中被重绘。

让它动起来

目前您不会注意到球在不断重绘,因为它没有移动。让我们改变这一点。首先,我们将不再使用硬编码的位置 (50,50),而是将初始位置定义在 Canvas 的底部中心区域,并用变量 xy 来存储,然后使用这些变量来定义绘制圆的位置。

首先,在 draw() 函数上方添加以下两行,以定义 xy

js
let x = canvas.width / 2;
let y = canvas.height - 30;

接下来,更新 draw() 函数,以便在 arc() 方法中使用 x 和 y 变量,如下面的高亮行所示

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

现在是重要部分:我们希望在绘制完每一帧后给 xy 添加一个很小的数值,使其看起来球在移动。我们将这些小数值定义为 dxdy,并将它们的值分别设置为 2 和 -2。在 x 和 y 变量定义下方添加以下内容

js
let dx = 2;
let dy = -2;

最后一件事是更新每一帧中的 xy 与我们的 dxdy 变量,以便球将在每次更新时在新的位置绘制。在您的 draw() 函数中添加以下两个新行,如下所示

js
function draw() {
  ctx.beginPath();
  ctx.arc(x, y, 10, 0, Math.PI * 2);
  ctx.fillStyle = "#0095DD";
  ctx.fill();
  ctx.closePath();
  x += dx;
  y += dy;
}

再次保存您的代码并在浏览器中尝试。这工作正常,尽管看起来球在后面留下了拖尾

A blue line that indicates where the ball has been

在每一帧之前清除画布

球留下拖尾是因为我们在每一帧中都绘制了一个新圆,而没有移除前一个。不用担心,因为有一个清除画布内容的方法:clearRect()。此方法接受四个参数:矩形左上角的 x 和 y 坐标,以及矩形右下角的 x 和 y 坐标。此矩形覆盖的整个区域将被清除之前绘制的所有内容。

将以下高亮的新行添加到 draw() 函数

js
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 毫秒,画布就会被清除,蓝色的圆(我们的球)将在给定的位置绘制,并且 xy 的值将为下一帧更新。

清理我们的代码

我们将在接下来的几篇文章中向 draw() 函数添加越来越多的命令,因此最好保持其尽可能简洁和干净。让我们开始将球绘制代码移到一个单独的函数中。

用以下两个函数替换现有的 draw() 函数

js
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

您可以在下面的实时演示中查看本文档的完成代码,并进行尝试以更好地理解其工作原理。

注意: 实时示例会在这些页面上自动运行,因此我们添加了一个“开始游戏”按钮。这有助于避免游戏自动开始并过于频繁地触发警报或其他事件。

html
<canvas id="myCanvas" width="480" height="320"></canvas>
<button id="runButton">Start game</button>
css
canvas {
  background: #eeeeee;
}
button {
  display: block;
}
js
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;
});

注意: 尝试更改移动球的速度或移动方向。

后续步骤

我们已经绘制了我们的球并使其动起来,但它一直在画布边缘消失。在第三章中,我们将探讨如何让它 从墙壁上反弹