起始點
要開始這個挑戰,請在本地計算機的新目錄中,複製上一篇文章中的 index-finished.html、style.css 和 main-finished.js 到本地。
或者,你可以使用線上編輯器,如 CodePen 或 JSFiddle。如果你使用的線上編輯器沒有獨立的 JavaScript 面板,你可以將 JavaScript 程式碼放在 HTML 頁面中的 <script> 元素內。
注意:如果你遇到困難,可以透過我們的 交流渠道 與我們聯絡。
提示和技巧
開始之前有幾點提示。
- 這個挑戰相當困難。在開始編碼之前請閱讀所有說明,並緩慢而仔細地執行每個步驟。
- 在每個階段完成後,最好儲存演示的單獨副本,以便將來遇到麻煩時可以參考。
專案簡介
我們的彈跳球演示很有趣,但現在我們想透過新增一個使用者控制的邪惡圓圈來使其更具互動性,如果它抓住球,它就會吃掉球。我們還想透過建立一個通用的 Shape() 物件來測試你的物件構建技能,我們的球和邪惡圓圈都可以繼承該物件。最後,我們想新增一個分數計數器來跟蹤剩餘的球的數量。
以下截圖讓你瞭解完成後的程式應該是什麼樣子。

為了讓你有更多的瞭解,請檢視 完成的示例 (不要偷看原始碼!)
完成步驟
以下部分描述了你需要做的事情。
建立一個 Shape 類
首先,建立一個新的 Shape 類。它只有一個建構函式。Shape 建構函式應該以與最初的 Ball() 建構函式相同的方式定義 x、y、velX 和 velY 屬性,但不定義 color 和 size 屬性。
Ball 類應該使用 extends 繼承自 Shape。Ball 的建構函式應該
- 接受與以前相同的引數:
x、y、velX、velY、size和color - 使用
super()呼叫Shape建構函式,並傳入x、y、velX和velY引數 - 根據給定的引數初始化其自身的
color和size屬性。
注意:確保在現有 Ball 類上方建立 Shape 類,否則你會收到類似“Uncaught ReferenceError: Cannot access 'Shape' before initialization”的錯誤。
Ball 建構函式應該定義一個名為 exists 的新屬性,用於跟蹤球是否存在於程式中(是否未被邪惡圓圈吃掉)。這應該是一個布林值(true/false),在建構函式中初始化為 true。
Ball 類的 collisionDetect() 方法需要進行小幅更新。只有當 exists 屬性為 true 時,才需要考慮球的碰撞檢測。因此,將現有的 collisionDetect() 程式碼替換為以下程式碼:
class Ball {
// …
collisionDetect() {
for (const ball of balls) {
if (!(this === ball) && ball.exists) {
const dx = this.x - ball.x;
const dy = this.y - ball.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < this.size + ball.size) {
ball.color = this.color = randomRGB();
}
}
}
}
// …
}
如上所述,唯一的新增是檢查球是否存在——透過在 if 條件中使用 ball.exists。
球的 draw() 和 update() 方法定義應該與以前完全相同。
此時,嘗試重新載入程式碼——它應該與以前一樣工作,只是我們的物件經過了重新設計。
定義 EvilCircle
現在是時候迎接壞人了——EvilCircle()!我們的遊戲只涉及一個邪惡圓圈,但我們仍然會使用繼承自 Shape() 的建構函式來定義它,以便給你一些練習。你可能希望稍後在應用程式中新增另一個可以由其他玩家控制的圓圈,或者有幾個由計算機控制的邪惡圓圈。你可能不會用一個邪惡圓圈征服世界,但它足以應對這個挑戰。
為 EvilCircle 類建立一個定義。它應該使用 extends 繼承自 Shape。
EvilCircle 建構函式
EvilCircle 的建構函式應該
- 只傳遞
x,y引數 - 將
x,y引數以及硬編碼為 20 的velX和velY值傳遞給Shape超類。你應該使用super(x, y, 20, 20);這樣的程式碼來完成。 - 將
color設定為white,將size設定為10。
最後,建構函式應該設定程式碼,使使用者能夠移動螢幕上的邪惡圓圈。
window.addEventListener("keydown", (e) => {
switch (e.key) {
case "a":
this.x -= this.velX;
break;
case "d":
this.x += this.velX;
break;
case "w":
this.y -= this.velY;
break;
case "s":
this.y += this.velY;
break;
}
});
這將向 window 物件新增一個 keydown 事件監聽器,以便當按下按鍵時,檢查事件物件的 key 屬性以檢視按下了哪個鍵。如果它是四個指定鍵之一,則邪惡圓圈將向左/右/上/下移動。
為 EvilCircle 定義方法
EvilCircle 類應該有三個方法,如下所述。
draw()
此方法與 Ball 的 draw() 方法具有相同的目的:它在畫布上繪製物件例項。EvilCircle 的 draw() 方法的工作方式將非常相似,因此你可以從複製 Ball 的 draw() 方法開始。然後你應該進行以下更改:
- 我們希望邪惡圓圈不被填充,而只有外線(描邊)。你可以透過將
fillStyle和fill()分別更新為strokeStyle和stroke()來實現這一點。 - 我們還希望使描邊更粗一些,這樣你可以更容易地看到邪惡圓圈。這可以透過在
beginPath()呼叫之後的某個位置設定lineWidth的值(3 即可)。
checkBounds()
此方法將執行與 Ball 的 update() 方法的第一部分相同的事情——檢視邪惡圓圈是否會超出螢幕邊緣,並阻止它這樣做。同樣,你基本上可以只複製 Ball 的 update() 方法,但你需要進行一些更改:
- 刪除最後兩行——我們不希望在每一幀自動更新邪惡圓圈的位置,因為我們將以其他方式移動它,如下所示。
- 在
if ()語句中,如果測試返回 true,我們不希望更新velX/velY;我們希望改為更改x/y的值,以便邪惡圓圈稍微反彈回螢幕上。適當地新增或減去邪惡圓圈的size屬性是有意義的。
collisionDetect()
此方法的行為方式將與 Ball 方法的 collisionDetect() 方法非常相似,因此你可以將其副本作為此新方法的基礎。但有幾個區別:
- 在外部
if語句中,你不再需要檢查迭代中的當前球是否與正在檢查的球相同——因為它不再是球,它是邪惡圓圈!相反,你需要進行測試以檢視正在檢查的球是否存在(你可以用哪個屬性來做到這一點?)。如果它不存在,它已經被邪惡圓圈吃掉了,因此無需再次檢查。 - 在內部
if語句中,你不再希望在檢測到碰撞時使物件改變顏色——相反,你希望將與邪惡圓圈碰撞的任何球設定為不再存在(再次,你認為你會怎麼做?)。
將邪惡圓圈引入程式
現在我們已經定義了邪惡圓圈,我們需要讓它出現在我們的場景中。為此,你需要對 loop() 函式進行一些更改。
- 首先,建立一個新的邪惡圓圈物件例項(指定必要的引數)。你只需執行一次,而不是在迴圈的每次迭代中都執行。
- 在你遍歷每個球併為每個球呼叫
draw()、update()和collisionDetect()函式時,確保只有在當前球存在時才呼叫這些函式。 - 在迴圈的每次迭代中呼叫邪惡圓圈例項的
draw()、checkBounds()和collisionDetect()方法。
實現分數計數器
要實現分數計數器,請遵循以下步驟:
-
在你的 CSS 檔案中,在底部新增以下規則:
cssp { position: absolute; margin: 0; top: 35px; right: 5px; color: #aaaaaa; } -
在你的 JavaScript 中,進行以下更新:
- 建立一個變數來儲存對段落的引用。
- 以某種方式記錄螢幕上球的數量。
- 每次向場景中新增一個球時,增加計數並顯示更新後的球的數量。
- 每次邪惡圓圈吃掉一個球(使其不再存在)時,減少計數並顯示更新後的球的數量。