You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/mo/6-space-game/6-end-condition
softchris 9837770ac1
🌐 Update translations via Co-op Translator
1 month ago
..
solution 🌐 Update translations via Co-op Translator 3 months ago
your-work 🌐 Update translations via Co-op Translator 3 months ago
README.md 🌐 Update translations via Co-op Translator 1 month ago
assignment.md 🌐 Update translations via Co-op Translator 1 month ago

README.md

建立太空遊戲第六部分:結束與重新開始

每個偉大的遊戲都需要明確的結束條件和流暢的重新開始機制。你已經建立了一個令人印象深刻的太空遊戲,擁有移動、戰鬥和得分功能——現在是時候添加最後的部分,讓它感覺更加完整。

目前你的遊戲可以無限運行,就像 NASA 在 1977 年發射的旅行者探測器一樣,幾十年後仍在太空中旅行。雖然這對於太空探索來說是可以接受的,但遊戲需要明確的結束點來創造令人滿意的體驗。

今天,我們將實現適當的勝負條件以及重新開始系統。在本課程結束時,你將擁有一個精緻的遊戲,玩家可以完成並重新遊玩,就像那些定義了遊戲媒介的經典街機遊戲一樣。

課前測驗

課前測驗

理解遊戲結束條件

你的遊戲應該在什麼時候結束這個基本問題自早期街機時代以來就影響了遊戲設計。吃豆人Pac-Man在你被鬼抓住或清除所有豆子時結束而太空侵略者Space Invaders則在外星人到達底部或你摧毀它們時結束。

作為遊戲創作者,你可以定義勝利和失敗的條件。對於我們的太空遊戲,以下是一些經過驗證的方式,可以創造出引人入勝的遊戲體驗:

  • 摧毀 N 敵方飛船:如果你將遊戲分成不同的關卡,通常需要摧毀 N 敵方飛船才能完成一個關卡。
  • 你的飛船被摧毀:有些遊戲中,如果你的飛船被摧毀,你就會輸掉遊戲。另一種常見的方法是引入生命值的概念。每次你的飛船被摧毀,生命值就會減少。一旦所有生命值耗盡,你就輸掉遊戲。
  • 收集 N 分數:另一種常見的結束條件是收集分數。如何獲得分數取決於你,但通常會將分數分配給各種活動,例如摧毀敵方飛船或收集敵方飛船被摧毀後掉落的物品。
  • 完成一個關卡:這可能涉及多種條件,例如摧毀 X 敵方飛船、收集 Y 分數,或者收集特定物品。

實現遊戲重新開始功能

好的遊戲通過流暢的重新開始機制鼓勵玩家反覆遊玩。當玩家完成遊戲(或遭遇失敗)時,他們通常希望立即再次嘗試——無論是為了打破自己的得分記錄還是提高表現。

俄羅斯方塊Tetris完美地展示了這一點當你的方塊到達頂部時你可以立即開始新遊戲而不需要導航複雜的菜單。我們將建立一個類似的重新開始系統乾淨地重置遊戲狀態讓玩家迅速回到遊戲中。

反思:想想你玩過的遊戲。它們在什麼條件下結束?如何提示你重新開始?什麼樣的重新開始體驗感覺流暢而不令人沮喪?

你將建立什麼

你將實現最終的功能,將你的項目轉變為完整的遊戲體驗。這些元素使精緻的遊戲與基本原型區分開來。

今天我們要添加的內容:

  1. 勝利條件:摧毀所有敵人並享受應得的慶祝!
  2. 失敗條件:生命值耗盡,面對失敗畫面
  3. 重新開始機制:按下 Enter 鍵立即重新開始——因為一局遊戲永遠不夠
  4. 狀態管理:每次都清空狀態——不會有上次遊戲留下的敵人或奇怪的故障

開始準備

讓我們準備好你的開發環境。你應該已經準備好前幾課的太空遊戲文件。

你的項目應該看起來像這樣:

-| 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 中出現警告,請在文件的頂部聲明 gameLoopIdlet 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 進行翻譯。雖然我們致力於提供準確的翻譯,但請注意,自動翻譯可能包含錯誤或不準確之處。原始文件的母語版本應被視為權威來源。對於關鍵信息,建議使用專業人工翻譯。我們對因使用此翻譯而引起的任何誤解或誤釋不承擔責任。