25 KiB
建造太空遊戲第六部分:結束與重新開始
journey
title Your Game Completion Journey
section End Conditions
Define win/lose states: 3: Student
Implement condition checking: 4: Student
Handle state transitions: 4: Student
section Player Experience
Design feedback systems: 4: Student
Create restart mechanics: 5: Student
Polish user interface: 5: Student
section System Integration
Manage game lifecycle: 5: Student
Handle memory cleanup: 5: Student
Create complete experience: 5: Student
每個偉大的遊戲都需要明確的結束條件和流暢的重新開始機制。你已經打造了一款令人印象深刻的太空遊戲,擁有移動、戰鬥和得分功能——現在是時候加入最後的元素,讓它感覺更加完整。
目前你的遊戲可以無限運行,就像 NASA 在 1977 年發射的旅行者探測器一樣,幾十年後仍在太空中旅行。雖然這對於太空探索來說是可以接受的,但遊戲需要明確的結束點才能創造令人滿意的體驗。
今天,我們將實現適當的勝負條件和重新開始系統。在本課程結束時,你將擁有一款精緻的遊戲,玩家可以完成並重玩,就像那些定義遊戲媒介的經典街機遊戲一樣。
mindmap
root((Game Completion))
End Conditions
Victory States
Defeat Conditions
Progress Tracking
State Validation
Player Feedback
Visual Messages
Color Psychology
Clear Communication
Emotional Response
State Management
Game Loop Control
Memory Cleanup
Object Lifecycle
Event Handling
Restart Systems
Input Handling
State Reset
Fresh Initialization
User Experience
Polish Elements
Message Display
Smooth Transitions
Error Prevention
Accessibility
課前測驗
理解遊戲結束條件
你的遊戲應該在什麼時候結束?這個基本問題自早期街機時代以來就影響了遊戲設計。吃豆人會在你被鬼抓住或清除所有豆子時結束,而太空侵略者則在外星人到達底部或你摧毀所有外星人時結束。
作為遊戲創作者,你需要定義勝利和失敗的條件。對於我們的太空遊戲,以下是一些經過驗證的方式,可以創造出引人入勝的遊戲體驗:
flowchart TD
A["🎮 Game Start"] --> B{"Check Conditions"}
B --> C["Enemy Count"]
B --> D["Hero Lives"]
B --> E["Score Threshold"]
B --> F["Level Progress"]
C --> C1{"Enemies = 0?"}
D --> D1{"Lives = 0?"}
E --> E1{"Score ≥ Target?"}
F --> F1{"Objectives Complete?"}
C1 -->|Yes| G["🏆 Victory"]
D1 -->|Yes| H["💀 Defeat"]
E1 -->|Yes| G
F1 -->|Yes| G
C1 -->|No| B
D1 -->|No| B
E1 -->|No| B
F1 -->|No| B
G --> I["🔄 Restart Option"]
H --> I
style G fill:#e8f5e8
style H fill:#ffebee
style I fill:#e3f2fd
- 摧毀
N艘敵方飛船:如果你將遊戲分成不同的關卡,通常需要摧毀N艘敵方飛船才能完成一個關卡。 - 你的飛船被摧毀:有些遊戲會在你的飛船被摧毀時結束。另一種常見的方式是引入生命的概念。每次你的飛船被摧毀時會扣除一條生命,當所有生命耗盡時,你就輸了。
- 收集
N分:另一種常見的結束條件是收集一定的分數。如何獲得分數取決於你,但通常會為各種活動分配分數,比如摧毀敵方飛船或收集敵方飛船被摧毀後掉落的物品。 - 完成一個關卡:這可能涉及多種條件,例如摧毀
X艘敵方飛船、收集Y分數,或者收集特定物品。
實現遊戲重新開始功能
好的遊戲通過流暢的重新開始機制鼓勵玩家反覆遊玩。當玩家完成遊戲(或遭遇失敗)時,他們通常希望立即再次嘗試——無論是為了打破自己的得分記錄還是提高表現。
stateDiagram-v2
[*] --> Playing: Game Start
Playing --> Victory: All enemies destroyed
Playing --> Defeat: Lives = 0
Victory --> MessageDisplay: Show win message
Defeat --> MessageDisplay: Show lose message
MessageDisplay --> WaitingRestart: Press Enter prompt
WaitingRestart --> Resetting: Enter key pressed
Resetting --> CleanupMemory: Clear intervals
CleanupMemory --> ClearEvents: Remove listeners
ClearEvents --> InitializeGame: Fresh start
InitializeGame --> Playing: New game begins
note right of MessageDisplay
Color-coded feedback:
Green = Victory
Red = Defeat
end note
note right of Resetting
Complete state reset
prevents memory leaks
end note
俄羅斯方塊完美地展示了這一點:當你的方塊堆到頂部時,你可以立即開始新遊戲,而不需要導航複雜的菜單。我們將構建一個類似的重新開始系統,乾淨地重置遊戲狀態,讓玩家迅速回到遊戲中。
✅ 反思:想想你玩過的遊戲。它們在什麼條件下結束?如何提示你重新開始?什麼樣的重新開始體驗感覺流暢而非令人沮喪?
你將構建的內容
你將實現最終的功能,將你的項目轉變為完整的遊戲體驗。這些元素使精緻的遊戲與基本的原型區分開來。
今天我們要添加的內容:
- 勝利條件:摧毀所有敵人並獲得應有的慶祝(你值得擁有!)
- 失敗條件:生命耗盡並面對失敗畫面
- 重新開始機制:按下 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 變數聲明的最佳實踐。
flowchart TD
A["1. Condition Tracking"] --> B["2. Event Handlers"]
B --> C["3. Message Constants"]
C --> D["4. Restart Controls"]
D --> E["5. Message Display"]
E --> F["6. Reset System"]
G["isHeroDead()\nisEnemiesDead()"] --> A
H["Collision Events\nEnd Game Events"] --> B
I["GAME_END_WIN\nGAME_END_LOSS"] --> C
J["Enter Key\nRestart Trigger"] --> D
K["Victory/Defeat\nColor-coded Text"] --> E
L["State Cleanup\nFresh Initialization"] --> F
F --> M["🎮 Complete Game"]
style A fill:#e3f2fd
style B fill:#e8f5e8
style C fill:#fff3e0
style D fill:#f3e5f5
style E fill:#e0f2f1
style F fill:#fce4ec
style M fill:#e1f5fe
實現步驟
步驟 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:更新事件處理器以支持結束條件
現在我們將把這些條件檢查與遊戲的事件系統連接起來。每次發生碰撞時,遊戲都會評估是否觸發結束條件。這為關鍵遊戲事件創造了即時反饋。
sequenceDiagram
participant Collision
participant GameLogic
participant Conditions
participant EventSystem
participant Display
Collision->>GameLogic: Laser hits enemy
GameLogic->>GameLogic: Destroy objects
GameLogic->>Conditions: Check isEnemiesDead()
alt All enemies defeated
Conditions->>EventSystem: Emit GAME_END_WIN
EventSystem->>Display: Show victory message
else Enemies remain
Conditions->>GameLogic: Continue game
end
Collision->>GameLogic: Enemy hits hero
GameLogic->>GameLogic: Decrease lives
GameLogic->>Conditions: Check isHeroDead()
alt Lives = 0
Conditions->>EventSystem: Emit GAME_END_LOSS
EventSystem->>Display: Show defeat message
else Lives remain
GameLogic->>Conditions: Check isEnemiesDead()
alt All enemies defeated
Conditions->>EventSystem: Emit GAME_END_WIN
end
end
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);
}
逐步解析,以下是發生的事情:
- 設置字體大小和字體家族以確保文字清晰可讀
- 應用顏色參數,默認為紅色以顯示警告
- 將文字水平和垂直居中顯示在畫布上
- 使用現代 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 年代創造第一批視頻遊戲的程序員一樣,你已將代碼行轉化為具有適當遊戲機制和用戶反饋的互動體驗。🚀 💥 👽
你已完成:
- 實現完整的勝負條件並提供用戶反饋
- 創建流暢的重新開始系統以支持持續遊玩
- 設計清晰的遊戲狀態視覺傳達
- 管理複雜的遊戲狀態轉換和清理
- 組裝所有組件成為一款完整可玩的遊戲
🔄 教學檢查點
完整的遊戲開發系統:慶祝你掌握了完整的遊戲開發週期:
- ✅ 結束條件如何創造令人滿意的玩家體驗?
- ✅ 為什麼適當的狀態管理對遊戲穩定性至關重要?
- ✅ 視覺反饋如何增強玩家的理解?
- ✅ 重新開始系統在玩家留存中扮演什麼角色?
系統掌握:你的完整遊戲展示了:
- 全棧遊戲開發:從圖形到輸入到狀態管理
- 專業架構:事件驅動系統,具有適當的清理
- 用戶體驗設計:清晰的反饋和直觀的控制
- 性能優化:高效的渲染和內存管理
- 精緻與完整性:使遊戲感覺完成的所有細節
行業準備技能:你已實現:
- 遊戲循環架構:具有一致性能的實時系統
- 事件驅動編程:可擴展的解耦系統
- 狀態管理:複雜的數據處理和生命周期管理
- 用戶界面設計:清晰的溝通和響應式控制
- 測試與調試:迭代開發和問題解決
⚡ 你可以在接下來的 5 分鐘內完成的事情
- 玩你的完整遊戲並測試所有勝利和失敗條件
- 嘗試不同的結束條件參數
- 嘗試添加 console.log 語句來追蹤遊戲狀態變化
- 與朋友分享你的遊戲並收集反饋
🎯 你可以在接下來的一小時內完成的事情
- 完成課後測驗並反思你的遊戲開發旅程
- 為勝利和失敗狀態添加音效
- 實現額外的結束條件,例如時間限制或額外目標
- 創建不同的難度級別,增加敵人數量
- 使用更好的字體和顏色來完善視覺呈現
📅 你的週長遊戲開發精通計劃
- 完成具有多個關卡和進度的增強版太空遊戲
- 添加高級功能,例如能量提升、不同類型的敵人和特殊武器
- 創建具有持久存儲的高分系統
- 設計菜單、設置和遊戲選項的用戶界面
- 優化性能以適應不同的設備和瀏覽器
- 將你的遊戲部署到線上並與社群分享
🌟 你的月度遊戲開發職業生涯
- 建立多款完整遊戲,探索不同的類型和機制
- 學習高級遊戲開發框架,例如 Phaser 或 Three.js
- 參與開源遊戲開發項目
- 研究遊戲設計原則和玩家心理
- 創建展示你遊戲開發技能的作品集
- 與遊戲開發社群建立聯繫並持續學習
🎯 你的完整遊戲開發精通時間表
timeline
title Complete Game Development Learning Progression
section Foundation (Lessons 1-2)
Game Architecture: Project structure
: Asset management
: Canvas basics
: Event systems
section Interaction Systems (Lessons 3-4)
Player Control: Input handling
: Movement mechanics
: Collision detection
: Physics simulation
section Game Mechanics (Lesson 5)
Feedback Systems: Scoring mechanisms
: Life management
: Visual communication
: Player motivation
section Game Completion (Lesson 6)
Polish & Flow: End conditions
: State management
: Restart systems
: User experience
section Advanced Features (1 week)
Enhancement Skills: Audio integration
: Visual effects
: Level progression
: Performance optimization
section Professional Development (1 month)
Industry Readiness: Framework mastery
: Team collaboration
: Portfolio development
: Community engagement
section Career Advancement (3 months)
Specialization: Advanced game engines
: Platform deployment
: Monetization strategies
: Industry networking
🛠️ 你的完整遊戲開發工具包摘要
完成整個太空遊戲系列後,你已經掌握了:
- 遊戲架構:事件驅動系統、遊戲循環和狀態管理
- 圖形編程:Canvas API、精靈渲染和視覺效果
- 輸入系統:鍵盤操作、碰撞檢測和響應式控制
- 遊戲設計:玩家反饋、進度系統和吸引力機制
- 性能優化:高效渲染、內存管理和幀率控制
- 用戶體驗:清晰的溝通、直觀的控制和細節打磨
- 專業模式:乾淨的代碼、調試技術和項目組織
實際應用:你的遊戲開發技能可以直接應用於:
- 互動式網頁應用:動態界面和實時系統
- 數據可視化:動畫圖表和互動式圖形
- 教育技術:遊戲化和吸引人的學習體驗
- 移動開發:觸控交互和性能優化
- 模擬軟件:物理引擎和實時建模
- 創意產業:互動藝術、娛樂和數字體驗
獲得的專業技能:你現在可以:
- 架構 從零開始設計複雜的互動系統
- 調試 使用系統化方法解決實時應用問題
- 優化 性能以提供流暢的用戶體驗
- 設計 吸引人的用戶界面和交互模式
- 協作 在技術項目中有效地進行代碼組織
掌握的遊戲開發概念:
- 實時系統:遊戲循環、幀率管理和性能優化
- 事件驅動架構:解耦系統和消息傳遞
- 狀態管理:複雜數據處理和生命周期管理
- 用戶界面編程:Canvas 圖形和響應式設計
- 遊戲設計理論:玩家心理和吸引力機制
下一步:你已準備好探索高級遊戲框架、3D 圖形、多玩家系統,或轉向專業遊戲開發角色!
🌟 成就解鎖:你已完成完整的遊戲開發旅程,並從零開始建立了一個專業品質的互動體驗!
歡迎加入遊戲開發社群! 🎮✨
GitHub Copilot Agent 挑戰 🚀
使用 Agent 模式完成以下挑戰:
描述:通過實現一個關卡進程系統,提升太空遊戲的難度並添加額外功能。
提示:創建一個多關卡的太空遊戲系統,每個關卡都有更多的敵方飛船,速度和生命值逐漸增加。添加一個隨關卡提升的得分倍數,並實現隨機出現的強化道具(例如快速射擊或護盾),當敵人被摧毀時出現。包括關卡完成獎勵,並在屏幕上顯示當前關卡以及現有的得分和生命值。
了解更多 agent mode 的信息。
🚀 可選增強挑戰
為你的遊戲添加音效:通過實現音效來提升遊戲體驗!考慮添加以下音效:
- 激光射擊 當玩家開火時
- 敵人摧毀 當飛船被擊中時
- 英雄受傷 當玩家受到攻擊時
- 勝利音樂 當遊戲獲勝時
- 失敗音效 當遊戲失敗時
音效實現範例:
// 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 進行翻譯。儘管我們努力確保準確性,但請注意,自動翻譯可能包含錯誤或不準確之處。原始文件的母語版本應被視為權威來源。對於關鍵信息,建議使用專業人工翻譯。我們對因使用此翻譯而產生的任何誤解或誤釋不承擔責任。