|
|
<!--
|
|
|
CO_OP_TRANSLATOR_METADATA:
|
|
|
{
|
|
|
"original_hash": "2e83e38c35dc003f046d7cc0bbfd4920",
|
|
|
"translation_date": "2025-08-25T22:22:14+00:00",
|
|
|
"source_file": "6-space-game/4-collision-detection/README.md",
|
|
|
"language_code": "mo"
|
|
|
}
|
|
|
-->
|
|
|
# 建立太空遊戲第 4 部分:新增雷射與碰撞檢測
|
|
|
|
|
|
## 課前測驗
|
|
|
|
|
|
[課前測驗](https://ff-quizzes.netlify.app/web/quiz/35)
|
|
|
|
|
|
在這節課中,你將學習如何用 JavaScript 發射雷射!我們將為遊戲新增兩個功能:
|
|
|
|
|
|
- **雷射**:從英雄的飛船發射,垂直向上移動
|
|
|
- **碰撞檢測**:作為實現射擊功能的一部分,我們還會加入一些有趣的遊戲規則:
|
|
|
- **雷射擊中敵人**:敵人被雷射擊中後會死亡
|
|
|
- **雷射擊中螢幕頂部**:雷射擊中螢幕頂部後會被銷毀
|
|
|
- **敵人與英雄碰撞**:敵人與英雄相撞後雙方都會被銷毀
|
|
|
- **敵人到達螢幕底部**:敵人到達螢幕底部後,敵人和英雄都會被銷毀
|
|
|
|
|
|
簡而言之,你——*英雄*——需要在敵人到達螢幕底部之前,用雷射擊中所有敵人。
|
|
|
|
|
|
✅ 做一些研究,了解第一款電腦遊戲的功能是什麼?
|
|
|
|
|
|
讓我們一起成為英雄吧!
|
|
|
|
|
|
## 碰撞檢測
|
|
|
|
|
|
我們該如何進行碰撞檢測?我們需要將遊戲物件視為移動的矩形。為什麼呢?因為用來繪製遊戲物件的圖像本身就是一個矩形:它有 `x`、`y`、`width` 和 `height`。
|
|
|
|
|
|
如果兩個矩形(例如英雄和敵人)*相交*,就發生了碰撞。碰撞後應該發生什麼,取決於遊戲規則。要實現碰撞檢測,你需要以下幾步:
|
|
|
|
|
|
1. 一種方法來獲取遊戲物件的矩形表示,例如:
|
|
|
|
|
|
```javascript
|
|
|
rectFromGameObject() {
|
|
|
return {
|
|
|
top: this.y,
|
|
|
left: this.x,
|
|
|
bottom: this.y + this.height,
|
|
|
right: this.x + this.width
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
2. 一個比較函數,該函數可以像這樣:
|
|
|
|
|
|
```javascript
|
|
|
function intersectRect(r1, r2) {
|
|
|
return !(r2.left > r1.right ||
|
|
|
r2.right < r1.left ||
|
|
|
r2.top > r1.bottom ||
|
|
|
r2.bottom < r1.top);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
## 我們如何銷毀物件
|
|
|
|
|
|
在遊戲中銷毀物件需要讓遊戲知道不再需要在遊戲迴圈中繪製該物件。實現方法是當某些事件發生時,將遊戲物件標記為*死亡*,如下所示:
|
|
|
|
|
|
```javascript
|
|
|
// collision happened
|
|
|
enemy.dead = true
|
|
|
```
|
|
|
|
|
|
接著,你可以在重新繪製螢幕之前,將*死亡*的物件過濾掉,如下所示:
|
|
|
|
|
|
```javascript
|
|
|
gameObjects = gameObject.filter(go => !go.dead);
|
|
|
```
|
|
|
|
|
|
## 我們如何發射雷射
|
|
|
|
|
|
發射雷射的過程是響應按鍵事件並創建一個向某個方向移動的物件。因此,我們需要執行以下步驟:
|
|
|
|
|
|
1. **創建雷射物件**:從英雄飛船的頂部發射,並在創建後立即開始向螢幕頂部移動。
|
|
|
2. **綁定按鍵事件**:選擇鍵盤上的某個按鍵來代表玩家發射雷射。
|
|
|
3. **當按鍵被按下時,創建一個看起來像雷射的遊戲物件**。
|
|
|
|
|
|
## 雷射的冷卻時間
|
|
|
|
|
|
雷射需要在每次按下按鍵(例如 *空格鍵*)時發射。為了防止遊戲在短時間內生成過多雷射,我們需要解決這個問題。解決方法是實現所謂的*冷卻時間*,一個計時器,確保雷射只能在一定時間間隔內發射一次。你可以這樣實現:
|
|
|
|
|
|
```javascript
|
|
|
class Cooldown {
|
|
|
constructor(time) {
|
|
|
this.cool = false;
|
|
|
setTimeout(() => {
|
|
|
this.cool = true;
|
|
|
}, time)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
class Weapon {
|
|
|
constructor {
|
|
|
}
|
|
|
fire() {
|
|
|
if (!this.cooldown || this.cooldown.cool) {
|
|
|
// produce a laser
|
|
|
this.cooldown = new Cooldown(500);
|
|
|
} else {
|
|
|
// do nothing - it hasn't cooled down yet.
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
✅ 回顧太空遊戲系列的第 1 節課,重新了解*冷卻時間*的概念。
|
|
|
|
|
|
## 要建構的內容
|
|
|
|
|
|
你將使用上一節課的現有程式碼(應該已經清理和重構過),並進行擴展。可以從第 II 部分的程式碼開始,或者使用 [第 III 部分的起始程式碼](../../../../../../../../../your-work)。
|
|
|
|
|
|
> 提示:你將使用的雷射已經在資產資料夾中,並且已被程式碼引用。
|
|
|
|
|
|
- **新增碰撞檢測**,當雷射與某物件碰撞時,應遵循以下規則:
|
|
|
1. **雷射擊中敵人**:敵人被雷射擊中後會死亡
|
|
|
2. **雷射擊中螢幕頂部**:雷射擊中螢幕頂部後會被銷毀
|
|
|
3. **敵人與英雄碰撞**:敵人與英雄相撞後雙方都會被銷毀
|
|
|
4. **敵人到達螢幕底部**:敵人到達螢幕底部後,敵人和英雄都會被銷毀
|
|
|
|
|
|
## 建議步驟
|
|
|
|
|
|
找到在 `your-work` 子資料夾中為你創建的檔案。它應包含以下內容:
|
|
|
|
|
|
```bash
|
|
|
-| assets
|
|
|
-| enemyShip.png
|
|
|
-| player.png
|
|
|
-| laserRed.png
|
|
|
-| index.html
|
|
|
-| app.js
|
|
|
-| package.json
|
|
|
```
|
|
|
|
|
|
在 `your_work` 資料夾中輸入以下指令來啟動專案:
|
|
|
|
|
|
```bash
|
|
|
cd your-work
|
|
|
npm start
|
|
|
```
|
|
|
|
|
|
上述指令會在位址 `http://localhost:5000` 啟動一個 HTTP 伺服器。打開瀏覽器並輸入該位址,目前應該可以看到英雄和所有敵人,但它們尚未移動。
|
|
|
|
|
|
### 新增程式碼
|
|
|
|
|
|
1. **為遊戲物件設置矩形表示以處理碰撞** 以下程式碼允許你獲取 `GameObject` 的矩形表示。編輯你的 GameObject 類別以擴展它:
|
|
|
|
|
|
```javascript
|
|
|
rectFromGameObject() {
|
|
|
return {
|
|
|
top: this.y,
|
|
|
left: this.x,
|
|
|
bottom: this.y + this.height,
|
|
|
right: this.x + this.width,
|
|
|
};
|
|
|
}
|
|
|
```
|
|
|
|
|
|
2. **新增檢查碰撞的程式碼** 這將是一個新函數,用於測試兩個矩形是否相交:
|
|
|
|
|
|
```javascript
|
|
|
function intersectRect(r1, r2) {
|
|
|
return !(
|
|
|
r2.left > r1.right ||
|
|
|
r2.right < r1.left ||
|
|
|
r2.top > r1.bottom ||
|
|
|
r2.bottom < r1.top
|
|
|
);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
3. **新增雷射發射功能**
|
|
|
1. **新增按鍵事件訊息**。*空格鍵*應在英雄飛船上方創建一個雷射。在 Messages 物件中新增三個常數:
|
|
|
|
|
|
```javascript
|
|
|
KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
|
|
|
COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
|
|
|
COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
|
|
|
```
|
|
|
|
|
|
1. **處理空格鍵**。編輯 `window.addEventListener` 的 keyup 函數以處理空格鍵:
|
|
|
|
|
|
```javascript
|
|
|
} else if(evt.keyCode === 32) {
|
|
|
eventEmitter.emit(Messages.KEY_EVENT_SPACE);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
1. **新增監聽器**。編輯 `initGame()` 函數,確保當按下空格鍵時英雄可以發射雷射:
|
|
|
|
|
|
```javascript
|
|
|
eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
|
|
|
if (hero.canFire()) {
|
|
|
hero.fire();
|
|
|
}
|
|
|
```
|
|
|
|
|
|
並新增一個新的 `eventEmitter.on()` 函數,以確保當敵人與雷射碰撞時的行為:
|
|
|
|
|
|
```javascript
|
|
|
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
|
|
|
first.dead = true;
|
|
|
second.dead = true;
|
|
|
})
|
|
|
```
|
|
|
|
|
|
1. **移動物件**,確保雷射逐漸移動到螢幕頂部。你將創建一個新的 Laser 類別,繼承自 `GameObject`,如之前所做:
|
|
|
|
|
|
```javascript
|
|
|
class Laser extends GameObject {
|
|
|
constructor(x, y) {
|
|
|
super(x,y);
|
|
|
(this.width = 9), (this.height = 33);
|
|
|
this.type = 'Laser';
|
|
|
this.img = laserImg;
|
|
|
let id = setInterval(() => {
|
|
|
if (this.y > 0) {
|
|
|
this.y -= 15;
|
|
|
} else {
|
|
|
this.dead = true;
|
|
|
clearInterval(id);
|
|
|
}
|
|
|
}, 100)
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
1. **處理碰撞**,為雷射實現碰撞規則。新增一個 `updateGameObjects()` 函數,用於測試碰撞物件是否命中:
|
|
|
|
|
|
```javascript
|
|
|
function updateGameObjects() {
|
|
|
const enemies = gameObjects.filter(go => go.type === 'Enemy');
|
|
|
const lasers = gameObjects.filter((go) => go.type === "Laser");
|
|
|
// laser hit something
|
|
|
lasers.forEach((l) => {
|
|
|
enemies.forEach((m) => {
|
|
|
if (intersectRect(l.rectFromGameObject(), m.rectFromGameObject())) {
|
|
|
eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
|
|
|
first: l,
|
|
|
second: m,
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
gameObjects = gameObjects.filter(go => !go.dead);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
確保將 `updateGameObjects()` 新增到 `window.onload` 的遊戲迴圈中。
|
|
|
|
|
|
4. **實現雷射的冷卻時間**,確保雷射只能在一定時間間隔內發射。
|
|
|
|
|
|
最後,編輯 Hero 類別以實現冷卻功能:
|
|
|
|
|
|
```javascript
|
|
|
class Hero extends GameObject {
|
|
|
constructor(x, y) {
|
|
|
super(x, y);
|
|
|
(this.width = 99), (this.height = 75);
|
|
|
this.type = "Hero";
|
|
|
this.speed = { x: 0, y: 0 };
|
|
|
this.cooldown = 0;
|
|
|
}
|
|
|
fire() {
|
|
|
gameObjects.push(new Laser(this.x + 45, this.y - 10));
|
|
|
this.cooldown = 500;
|
|
|
|
|
|
let id = setInterval(() => {
|
|
|
if (this.cooldown > 0) {
|
|
|
this.cooldown -= 100;
|
|
|
} else {
|
|
|
clearInterval(id);
|
|
|
}
|
|
|
}, 200);
|
|
|
}
|
|
|
canFire() {
|
|
|
return this.cooldown === 0;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
到這裡為止,你的遊戲已經具備了一些功能!你可以用方向鍵移動,用空格鍵發射雷射,並且當雷射擊中敵人時,敵人會消失。做得好!
|
|
|
|
|
|
---
|
|
|
|
|
|
## 🚀 挑戰
|
|
|
|
|
|
新增爆炸效果!查看 [Space Art 資源庫](../../../../6-space-game/solution/spaceArt/readme.txt) 中的遊戲資產,嘗試在雷射擊中外星人時新增爆炸效果。
|
|
|
|
|
|
## 課後測驗
|
|
|
|
|
|
[課後測驗](https://ff-quizzes.netlify.app/web/quiz/36)
|
|
|
|
|
|
## 回顧與自學
|
|
|
|
|
|
試著調整遊戲中的時間間隔,觀察會發生什麼變化?閱讀更多關於 [JavaScript 計時事件](https://www.freecodecamp.org/news/javascript-timing-events-settimeout-and-setinterval/) 的內容。
|
|
|
|
|
|
## 作業
|
|
|
|
|
|
[探索碰撞](assignment.md)
|
|
|
|
|
|
**免責聲明**:
|
|
|
本文檔使用 AI 翻譯服務 [Co-op Translator](https://github.com/Azure/co-op-translator) 進行翻譯。儘管我們努力確保準確性,但請注意,自動翻譯可能包含錯誤或不準確之處。原始語言的文件應被視為權威來源。對於關鍵信息,建議使用專業人工翻譯。我們對因使用此翻譯而引起的任何誤解或錯誤解釋不承擔責任。 |