彈跳
這是 Gamedev Canvas 教程 的 第 3 步,共 10 步。您可以在 Gamedev-Canvas-workshop/lesson3.html 找到完成本課後程式碼應有的樣子。
看到我們的球在移動很不錯,但它很快就從螢幕上消失了,限制了我們的樂趣!為了克服這一點,我們將實現一些碰撞檢測(稍後將在 更詳細地解釋),讓球從 Canvas 的四條邊反彈。
簡單的碰撞檢測
要檢測碰撞,我們將檢查球是否碰到(碰撞到)牆壁,如果碰到,我們將相應地改變其移動方向。
為了簡化計算,讓我們定義一個名為 ballRadius 的變數,它將儲存繪製圓的半徑並用於計算。將此新增到您的程式碼中,位於現有變數宣告的某個位置下方。
const ballRadius = 10;
現在,將 drawBall() 函式中繪製球的程式碼更新為如下所示:
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
彈跳頂部和底部
球有四面牆可以反彈 — 讓我們先關注頂部。我們需要在每一幀都檢查球是否碰到 Canvas 的頂部邊緣 — 如果是,我們將反轉球的移動方向,使其開始向相反方向移動並保持在可見邊界內。記住座標系從左上角開始,我們可以得出這樣的結論:
if (y + dy < 0) {
dy = -dy;
}
如果球位置的 y 值小於零,則透過將其設定為自身的反值來改變 y 軸上的移動方向。如果球以每幀 2 畫素的速度向上移動,現在它將以 -2 畫素的速度“向上”移動,這實際上等於以每幀 2 畫素的速度向下移動。
上面的程式碼處理了球從頂部邊緣反彈的情況,現在讓我們考慮底部邊緣:
if (y + dy > canvas.height) {
dy = -dy;
}
如果球的 y 位置大於 Canvas 的高度(記住我們從左上角開始計算 y 值,所以頂部邊緣從 0 開始,底部邊緣是 320 畫素,即 Canvas 的高度),然後透過像以前一樣反轉 y 軸移動來使其從底部邊緣反彈。
我們可以將這兩個語句合併為一個,以節省程式碼冗餘。
if (y + dy > canvas.height || y + dy < 0) {
dy = -dy;
}
如果兩個語句中的任何一個為 true,則反轉球的移動。
彈跳左側和右側
我們已經處理了頂部和底部邊緣,所以讓我們考慮左側和右側。實際上非常相似,您所要做的就是為 x 重複 y 的語句。
if (x + dx > canvas.width || x + dx < 0) {
dx = -dx;
}
if (y + dy > canvas.height || y + dy < 0) {
dy = -dy;
}
此時,您應該將上面的程式碼塊插入到 draw() 函式中,就在閉合花括號之前。
球仍然會消失到牆裡!
此時測試您的程式碼,您會感到驚訝 — 現在我們有了一個能從畫布四邊反彈的球!然而,我們遇到了另一個問題 — 當球碰到每面牆時,它會在改變方向之前稍微沉入牆裡。

這是因為我們正在計算牆壁和球心之間的碰撞點,而我們應該為球的周長進行計算。球應該在碰到牆壁後立即反彈,而不是在已經半個球體都在牆裡之後,所以讓我們稍微調整一下我們的語句以包含這一點。將您最後新增的程式碼更新為如下所示:
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
dx = -dx;
}
if (y + dy > canvas.height - ballRadius || y + dy < ballRadius) {
dy = -dy;
}
當球心與牆壁邊緣之間的距離等於球的半徑時,它將改變移動方向。從一個邊緣的寬度中減去半徑,並在另一個邊緣加上半徑,讓我們對正確的碰撞檢測有印象 — 球應該像預期的那樣從牆壁上反彈。
Compare your code
讓我們再次檢查此部分的程式碼是否與您的程式碼相符,並進行一些嘗試。
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
const ballRadius = 10;
let x = canvas.width / 2;
let y = canvas.height - 30;
let dx = 2;
let dy = -2;
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
dx = -dx;
}
if (y + dy > canvas.height - ballRadius || y + dy < ballRadius) {
dy = -dy;
}
x += dx;
y += dy;
}
function startGame() {
setInterval(draw, 10);
}
const runButton = document.getElementById("runButton");
runButton.addEventListener("click", () => {
startGame();
runButton.disabled = true;
});
注意: 嘗試在球碰到牆壁時將其顏色更改為隨機顏色。
後續步驟
現在我們已經到了球既能移動又能停留在遊戲板上的階段。在第四章中,我們將實現一個可控制的擋板 — 請參閱 擋板和鍵盤控制。