移动球体

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

您已经知道如何绘制一个球体,这来自之前文章的学习,所以现在让我们让它移动。从技术上讲,我们将把球体绘制在屏幕上,然后清除它,并在每一帧稍微不同的位置重新绘制它,从而产生移动的印象 - 就像电影中的移动一样。

定义绘制循环

为了持续更新每一帧的画布绘制,我们需要定义一个绘制函数,该函数会不断重复执行,每次使用不同的变量值来改变精灵位置等。您可以使用 JavaScript 定时函数来重复执行一个函数。在本教程的后面,我们将看到 requestAnimationFrame() 如何帮助绘制,但我们首先从 setInterval() 开始,以创建一些循环逻辑。

删除您当前 HTML 文件中所有 JavaScript 代码,除了前两行,并在它们下方添加以下代码。draw() 函数将在 setInterval 中每 10 毫秒执行一次

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) 位置,而是使用名为 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;
}

比较您的代码

您可以在下面的实时演示中查看本文的完成代码,并与它交互,以更好地了解它是如何工作的。

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

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

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

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

下一步

我们已经绘制了我们的球体并让它移动,但它一直消失在画布的边缘。在第三章中,我们将探索如何让它 弹回墙壁