移動球

這是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;
});

注意: 嘗試更改移動球的速度或移動方向。

後續步驟

我們已經繪製了我們的球並使其動起來,但它一直在畫布邊緣消失。在第三章中,我們將探討如何讓它 從牆壁上反彈