14 KiB
建立太空遊戲第六部分:結束與重新開始
每個偉大的遊戲都需要明確的結束條件和流暢的重新開始機制。你已經建立了一個令人印象深刻的太空遊戲,擁有移動、戰鬥和得分功能——現在是時候添加最後的部分,讓它感覺更加完整。
目前你的遊戲可以無限運行,就像 NASA 在 1977 年發射的旅行者探測器一樣,幾十年後仍在太空中旅行。雖然這對於太空探索來說是可以接受的,但遊戲需要明確的結束點來創造令人滿意的體驗。
今天,我們將實現適當的勝負條件以及重新開始系統。在本課程結束時,你將擁有一個精緻的遊戲,玩家可以完成並重新遊玩,就像那些定義了遊戲媒介的經典街機遊戲一樣。
課前測驗
理解遊戲結束條件
你的遊戲應該在什麼時候結束?這個基本問題自早期街機時代以來就影響了遊戲設計。吃豆人(Pac-Man)在你被鬼抓住或清除所有豆子時結束,而太空侵略者(Space Invaders)則在外星人到達底部或你摧毀它們時結束。
作為遊戲創作者,你可以定義勝利和失敗的條件。對於我們的太空遊戲,以下是一些經過驗證的方式,可以創造出引人入勝的遊戲體驗:
- 摧毀
N敵方飛船:如果你將遊戲分成不同的關卡,通常需要摧毀N敵方飛船才能完成一個關卡。 - 你的飛船被摧毀:有些遊戲中,如果你的飛船被摧毀,你就會輸掉遊戲。另一種常見的方法是引入生命值的概念。每次你的飛船被摧毀,生命值就會減少。一旦所有生命值耗盡,你就輸掉遊戲。
- 收集
N分數:另一種常見的結束條件是收集分數。如何獲得分數取決於你,但通常會將分數分配給各種活動,例如摧毀敵方飛船或收集敵方飛船被摧毀後掉落的物品。 - 完成一個關卡:這可能涉及多種條件,例如摧毀
X敵方飛船、收集Y分數,或者收集特定物品。
實現遊戲重新開始功能
好的遊戲通過流暢的重新開始機制鼓勵玩家反覆遊玩。當玩家完成遊戲(或遭遇失敗)時,他們通常希望立即再次嘗試——無論是為了打破自己的得分記錄還是提高表現。
俄羅斯方塊(Tetris)完美地展示了這一點:當你的方塊到達頂部時,你可以立即開始新遊戲,而不需要導航複雜的菜單。我們將建立一個類似的重新開始系統,乾淨地重置遊戲狀態,讓玩家迅速回到遊戲中。
✅ 反思:想想你玩過的遊戲。它們在什麼條件下結束?如何提示你重新開始?什麼樣的重新開始體驗感覺流暢而不令人沮喪?
你將建立什麼
你將實現最終的功能,將你的項目轉變為完整的遊戲體驗。這些元素使精緻的遊戲與基本原型區分開來。
今天我們要添加的內容:
- 勝利條件:摧毀所有敵人並享受應得的慶祝!
- 失敗條件:生命值耗盡,面對失敗畫面
- 重新開始機制:按下 Enter 鍵立即重新開始——因為一局遊戲永遠不夠
- 狀態管理:每次都清空狀態——不會有上次遊戲留下的敵人或奇怪的故障
開始準備
讓我們準備好你的開發環境。你應該已經準備好前幾課的太空遊戲文件。
你的項目應該看起來像這樣:
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| life.png
-| index.html
-| app.js
-| package.json
啟動你的開發伺服器:
cd your-work
npm start
此命令:
- 在
http://localhost:5000上運行本地伺服器 - 正確地提供你的文件
- 當你進行更改時自動刷新
在瀏覽器中打開 http://localhost:5000,確認你的遊戲正在運行。你應該能夠移動、射擊並與敵人互動。一旦確認,我們就可以進行實現了。
💡 專業提示:為了避免在 Visual Studio Code 中出現警告,請在文件的頂部聲明
gameLoopId為let gameLoopId;,而不是在window.onload函數內聲明。這符合現代 JavaScript 變數聲明的最佳實踐。
實現步驟
步驟 1:創建追蹤結束條件的函數
我們需要函數來監控遊戲應該何時結束。就像國際空間站上的傳感器不斷監控關鍵系統一樣,這些函數將持續檢查遊戲狀態。
function isHeroDead() {
return hero.life <= 0;
}
function isEnemiesDead() {
const enemies = gameObjects.filter((go) => go.type === "Enemy" && !go.dead);
return enemies.length === 0;
}
以下是內部發生的事情:
- 檢查我們的英雄是否失去所有生命值(糟糕!)
- 計算還有多少敵人活著並且活躍
- 返回當戰場上沒有敵人時的
true - 使用簡單的 true/false 邏輯保持直觀
- 篩選所有遊戲物件以找到倖存者
步驟 2:更新事件處理器以檢查結束條件
現在我們將這些條件檢查與遊戲的事件系統連接起來。每次發生碰撞時,遊戲都會評估是否觸發結束條件。這為關鍵遊戲事件創造了即時反饋。
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
hero.incrementPoints();
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
enemy.dead = true;
hero.decrementLife();
if (isHeroDead()) {
eventEmitter.emit(Messages.GAME_END_LOSS);
return; // loss before victory
}
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.GAME_END_WIN, () => {
endGame(true);
});
eventEmitter.on(Messages.GAME_END_LOSS, () => {
endGame(false);
});
這裡發生了什麼:
- 雷射擊中敵人:兩者消失,你獲得分數,並檢查是否獲勝
- 敵人擊中你:你失去一條生命,並檢查你是否還活著
- 智能排序:我們先檢查失敗(沒人想要同時贏和輸!)
- 即時反應:一旦發生重要事件,遊戲就會知道
步驟 3:添加新的消息常量
你需要在 Messages 常量對象中添加新的消息類型。這些常量有助於保持一致性並防止事件系統中的拼寫錯誤。
GAME_END_LOSS: "GAME_END_LOSS",
GAME_END_WIN: "GAME_END_WIN",
在上面,我們:
- 添加了遊戲結束事件的常量以保持一致性
- 使用描述性名稱清楚地指示事件目的
- 遵循現有的消息類型命名規範
步驟 4:實現重新開始控制
現在你將添加鍵盤控制,允許玩家重新開始遊戲。Enter 鍵是一個自然的選擇,因為它通常與確認操作和開始新遊戲相關聯。
將 Enter 鍵檢測添加到現有的 keydown 事件監聽器:
else if(evt.key === "Enter") {
eventEmitter.emit(Messages.KEY_EVENT_ENTER);
}
添加新的消息常量:
KEY_EVENT_ENTER: "KEY_EVENT_ENTER",
你需要知道的:
- 擴展現有的鍵盤事件處理系統
- 使用 Enter 鍵作為重新開始觸發器,提供直觀的用戶體驗
- 發送自定義事件,遊戲的其他部分可以監聽
- 保持與其他鍵盤控制相同的模式
步驟 5:創建消息顯示系統
你的遊戲需要清楚地向玩家傳達結果。我們將創建一個消息系統,使用彩色文字顯示勝利和失敗狀態,類似於早期電腦系統的終端界面,其中綠色表示成功,紅色表示錯誤。
創建 displayMessage() 函數:
function displayMessage(message, color = "red") {
ctx.font = "30px Arial";
ctx.fillStyle = color;
ctx.textAlign = "center";
ctx.fillText(message, canvas.width / 2, canvas.height / 2);
}
逐步解析,以下是發生的事情:
- 設置字體大小和字體家族,確保文字清晰可讀
- 應用顏色參數,默認為 "red" 用於警告
- 水平和垂直居中文字於畫布上
- 使用現代 JavaScript 的默認參數提供靈活的顏色選項
- 利用畫布 2D 上下文直接渲染文字
創建 endGame() 函數:
function endGame(win) {
clearInterval(gameLoopId);
// Set a delay to ensure any pending renders complete
setTimeout(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (win) {
displayMessage(
"Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew",
"green"
);
} else {
displayMessage(
"You died !!! Press [Enter] to start a new game Captain Pew Pew"
);
}
}, 200)
}
此函數的作用:
- 凍結所有物件——不再有移動的飛船或雷射
- 稍作停頓(200 毫秒),讓最後一幀完成繪製
- 清空屏幕並塗黑,營造戲劇效果
- 顯示不同的消息給勝利者和失敗者
- 用顏色區分消息——綠色代表好消息,紅色代表...嗯,不太好
- 告訴玩家如何重新進入遊戲
步驟 6:實現遊戲重置功能
重置系統需要完全清理當前的遊戲狀態並初始化新的遊戲會話。這確保玩家能夠在沒有任何前一局遊戲遺留數據的情況下重新開始。
創建 resetGame() 函數:
function resetGame() {
if (gameLoopId) {
clearInterval(gameLoopId);
eventEmitter.clear();
initGame();
gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawPoints();
drawLife();
updateGameObjects();
drawGameObjects(ctx);
}, 100);
}
}
讓我們理解每個部分:
- 檢查是否有遊戲循環正在運行,然後再重置
- 清除現有的遊戲循環以停止所有當前遊戲活動
- 移除所有事件監聽器以防止記憶體洩漏
- 重新初始化遊戲狀態,創建新的物件和變數
- 啟動新的遊戲循環,包含所有必要的遊戲功能
- 保持相同的 100 毫秒間隔,確保遊戲性能一致
將 Enter 鍵事件處理器添加到你的 initGame() 函數:
eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
resetGame();
});
將 clear() 方法添加到你的 EventEmitter 類:
clear() {
this.listeners = {};
}
需要記住的要點:
- 連接 Enter 鍵按下與重置遊戲功能
- 在遊戲初始化期間註冊此事件監聽器
- 提供清除所有事件監聽器的方式,避免記憶體洩漏
- 重置監聽器對象為空狀態,便於重新初始化
恭喜!🎉
👽 💥 🚀 你已成功從零開始建立了一個完整的遊戲。就像 1970 年代創造第一批電子遊戲的程式員一樣,你已將代碼行轉化為具有完整遊戲機制和用戶反饋的互動體驗。🚀 💥 👽
你已完成:
- 實現完整的勝負條件並提供用戶反饋
- 創建流暢的重新開始系統,支持連續遊玩
- 設計清晰的視覺通信,傳達遊戲狀態
- 管理複雜的遊戲狀態轉換和清理
- 組裝所有組件,形成一個連貫且可玩的遊戲
GitHub Copilot Agent 挑戰 🚀
使用 Agent 模式完成以下挑戰:
描述: 增強太空遊戲,實現具有逐漸增加難度和額外功能的關卡進程系統。
提示: 創建一個多關卡太空遊戲系統,每個關卡都有更多速度和生命值增加的敵方飛船。添加隨著每個關卡增加的得分倍數,並實現當敵人被摧毀時隨機出現的強化道具(如快速射擊或護盾)。包括關卡完成獎勵,並在屏幕上顯示當前關卡,與現有的得分和生命值一起。
了解更多關於 Agent 模式 的信息。
🚀 可選增強挑戰
為你的遊戲添加音效:通過實現音效來增強你的遊戲體驗!考慮添加以下音效:
- 雷射射擊:當玩家開火時
- 敵人摧毀:當飛船被擊中時
- 英雄受傷:當玩家受到攻擊時
- 勝利音樂:當遊戲獲勝時
- 失敗音效:當遊戲失敗時
音效實現範例:
// Create audio objects
const laserSound = new Audio('assets/laser.wav');
const explosionSound = new Audio('assets/explosion.wav');
// Play sounds during game events
function playLaserSound() {
laserSound.currentTime = 0; // Reset to beginning
laserSound.play();
}
你需要知道的:
- 創建不同音效的 Audio 對象
- 重置
currentTime以允許快速觸發音效 - 處理瀏覽器自動播放政策,通過用戶交互觸發音效
- 管理音效音量和時間,提升遊戲體驗
💡 學習資源:探索這個 音效沙盒,了解更多在 JavaScript 遊戲中實現音效的方法。
課後測驗
回顧與自學
你的作業是創建一個新的樣本遊戲,因此探索一些有趣的遊戲,看看你可能會建立什麼類型的遊戲。
作業
免責聲明:
本文件已使用 AI 翻譯服務 Co-op Translator 進行翻譯。雖然我們致力於提供準確的翻譯,但請注意,自動翻譯可能包含錯誤或不準確之處。原始文件的母語版本應被視為權威來源。對於關鍵信息,建議使用專業人工翻譯。我們對因使用此翻譯而引起的任何誤解或誤釋不承擔責任。