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/zh-HK/6-space-game/3-moving-elements-around
localizeflow[bot] a5481015bd
chore(i18n): sync translations with latest source changes (chunk 2/2, 261 changes)
7 months ago
..
README.md chore(i18n): sync translations with latest source changes (chunk 2/2, 261 changes) 7 months ago
assignment.md chore(i18n): sync translations with latest source changes (chunk 2/2, 261 changes) 7 months ago

README.md

Build a Space Game Part 3: Adding Motion

journey
    title 你的遊戲動畫旅程
    section 動作基礎
      了解動作原理: 3: Student
      學習座標更新: 4: Student
      實現基本移動: 4: Student
    section 玩家控制
      處理鍵盤事件: 4: Student
      防止預設行為: 5: Student
      創建互動控制: 5: Student
    section 遊戲系統
      建立遊戲迴圈: 5: Student
      管理物件生命週期: 5: Student
      實現發布/訂閱模式: 5: Student

想想你最喜歡的遊戲 — 讓它們吸引人的不只是漂亮的圖像,而是所有東西隨著你的操作而移動和反應的方式。現在,你的太空遊戲就像一幅美麗的畫,但我們正要加入讓它活起來的動作。

當 NASA 的工程師為阿波羅任務編寫導航電腦程式時,他們面臨著類似的挑戰:如何讓太空船對飛行員輸入做出反應,同時自動維持航向修正?今天我們要學的原理正呼應那些概念 — 同時管理玩家操控的動作與系統自動行為。

在本課中,你將學習如何讓太空船在螢幕上滑動,回應玩家指令,並創造平滑的移動模式。我們會將所有內容拆解成可管理的概念,自然地一層層建立。

到最後,你將能讓玩家駕駛英雄飛船在螢幕上飛行,敵機則在上方巡邏。更重要的是,你將理解驅動遊戲移動系統的核心原理。

mindmap
  root((遊戲動畫))
    Movement Types
      Player Controlled
      Automatic Motion
      Physics Based
      Scripted Paths
    Event Handling
      Keyboard Input
      Mouse Events
      Touch Controls
      Default Prevention
    Game Loop
      Update Logic
      Render Frame
      Clear Canvas
      Frame Rate Control
    Object Management
      Position Updates
      Collision Detection
      Lifecycle Management
      State Tracking
    Communication
      Pub/Sub Pattern
      Event Emitters
      Message Passing
      Loose Coupling

課前小測驗

課前小測驗

理解遊戲移動

當遊戲開始有東西移動起來,遊戲世界才會活過來,而這基本上有兩種方式:

  • 由玩家控制的移動:當你按下一個按鍵或點擊滑鼠,有物體移動。這是你與遊戲世界的直接連結。
  • 自動移動:遊戲本身決定移動東西 — 就像那些無論你是否在操作都需要巡邏螢幕的敵機。

讓物體在電腦螢幕上移動其實比你想像中簡單。還記得數學課上的 x 和 y 座標嗎?這正是我們現在使用的東西。早在 1610 年,伽利略追蹤木星的衛星時,基本上也是在做類似的事 — 隨時間繪製位置來理解運動模式。

在螢幕上移動東西就像製作翻頁動畫一樣 — 你需要遵循以下三個簡單步驟:

flowchart LR
    A["第 N 幀"] --> B["更新位置"]
    B --> C["清除畫布"]
    C --> D["繪製物件"]
    D --> E["第 N+1 幀"]
    E --> F{繼續?}
    F -->|是| B
    F -->|否| G["遊戲結束"]
    
    subgraph "動畫循環"
        H["1. 計算新位置"]
        I["2. 擦除前一幀"]
        J["3. 渲染新幀"]
    end
    
    style B fill:#e1f5fe
    style C fill:#ffebee
    style D fill:#e8f5e8
  1. 更新位置 — 改變物件應該在的位置(比如往右移 5 個像素)
  2. 清除舊的畫面 — 清空螢幕,避免出現鬼影拖尾
  3. 繪製新的畫面 — 把物件放在新的位置

做得夠快,叭!你就有了流暢、自然的移動感。

以下是程式碼範例:

// 設定英雄的位置
hero.x += 5;
// 清除承載英雄的矩形範圍
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 重繪遊戲背景和英雄
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);

這段代碼做了什麼:

  • 更新英雄在 x 軸的位置向右移 5 個像素
  • 清空整個畫布去除上一張畫面
  • 填滿黑色背景
  • 重新繪製英雄圖像在新位置

你能想出為什麼每秒重新繪製多幀英雄會造成效能問題嗎?請閱讀關於這種模式的替代方案

處理鍵盤事件

這就是把玩家輸入連接到遊戲行動的地方。當有人按下空白鍵發射雷射,或是用方向鍵閃避小行星時,遊戲需要偵測並回應這些輸入。

鍵盤事件在視窗級別發生,換句話說,整個瀏覽器視窗都在監聽這些鍵擊。滑鼠點擊則可以綁定到特定元素(像按鈕)。在我們的太空遊戲中,我們會專注於鍵盤控制,因為這給玩家經典的街機操作感。

這讓我想到 1800 年代電報操作員如何將摩斯密碼輸入轉換為有意義的訊息 — 我們在做的事情也類似,將按鍵轉換為遊戲指令。

要處理事件,你需要使用 window 的 addEventListener() 方法,並給它兩個參數。第一個是事件名稱,例如 keyup。第二個是當事件發生時要呼叫的函數。

範例如下:

window.addEventListener('keyup', (evt) => {
  // evt.key = 鍵的字串表示
  if (evt.key === 'ArrowUp') {
    // 做某事
  }
});

分解這裡發生的事:

  • 監聽整個視窗的鍵盤事件
  • 捕捉事件物件,其中包含按下的按鍵資訊
  • 檢查是否被按下的鍵匹配特定按鍵(此處為向上箭頭)
  • 當條件符合時執行程式碼

在鍵盤事件中,你可以使用事件物件的兩個屬性來查看按了哪個鍵:

  • key — 按鍵的字串表示,例如 'ArrowUp'
  • keyCode — 按鍵的數字表示,例如 37 對應 ArrowLeft

操作鍵盤事件不只在遊戲開發有用,你還能想到這項技術的其他用途嗎?

sequenceDiagram
    participant User
    participant Browser
    participant EventSystem
    participant GameLogic
    participant Hero
    
    User->>Browser: 按下方向鍵上
    Browser->>EventSystem: keydown 事件
    EventSystem->>EventSystem: preventDefault()
    EventSystem->>GameLogic: emit('KEY_EVENT_UP')
    GameLogic->>Hero: hero.y 減少 5
    Hero->>Hero: 更新位置
    
    Note over Browser,GameLogic: 事件流程防止瀏覽器預設行為
    Note over GameLogic,Hero: 發佈/訂閱模式促進清晰通訊

特殊鍵:注意!

某些鍵有內建的瀏覽器行為,可能會干擾你的遊戲。箭頭鍵會滾動頁面,空白鍵會跳頁 — 這些行為本來是不希望玩家操作太空船時發生的。

我們可以阻止這些預設行為,讓遊戲自己處理輸入。這就像早期程式員必須覆寫系統中斷以實現自訂行為 — 我們現在是在瀏覽器層面做同樣的事。方式如下:

const onKeyDown = function (e) {
  console.log(e.keyCode);
  switch (e.keyCode) {
    case 37:
    case 39:
    case 38:
    case 40: // 箭咀鍵
    case 32:
      e.preventDefault();
      break; // 空格鍵
    default:
      break; // 不要阻擋其他按鍵
  }
};

window.addEventListener('keydown', onKeyDown);

解讀這段阻止預設行為的程式碼:

  • 檢查可能會導致不想要瀏覽器行為的特定按鍵碼
  • 阻止箭頭鍵及空白鍵的預設瀏覽器行為
  • 允許其他按鍵正常運作
  • 使用 e.preventDefault() 停止瀏覽器內建的行為

🔄 教學檢視點

事件 Handling 理解:在進入自動移動前,請確認你能:

  • 解釋 keydownkeyup 事件差異
  • 理解為何要阻止瀏覽器的預設行為
  • 描述事件監聽器如何將用戶輸入連結到遊戲邏輯
  • 識別哪些鍵可能會干擾遊戲控制

快速自我測試:如果你不阻止箭頭鍵的預設行為會怎樣? 回答:瀏覽器會滾動頁面,干擾遊戲移動

事件系統架構:你現在知道了:

  • 視窗層監聽:在瀏覽器層捕捉事件
  • 事件物件屬性:字串 key 與數字 keyCode
  • 阻止預設行為:阻止不想要的瀏覽器動作
  • 條件邏輯:針對特定按鍵組合回應行為

遊戲主導移動

現在講講沒有玩家輸入時物件自動移動。想像敵機在螢幕上巡航,子彈直線飛行,或者背景雲朵漂浮。這種自主移動讓遊戲世界有生命感,即使沒人操作。

我們使用 JavaScript 內建的計時器,在固定時間間隔更新位置。這概念和擺鐘運作相似 — 一個規律機制觸發一致定時動作。程式簡單長這樣:

const id = setInterval(() => {
  // 在 y 軸上移動敵人
  enemy.y += 10;
}, 100);

這段移動程式碼做了什麼:

  • 建立每 100 毫秒執行一次的計時器
  • 每次更新敵機的 y 座標往下移 10 像素
  • 儲存計時器 ID方便後續停止
  • 讓敵機自動向下移動

遊戲迴圈

這是整合所有東西的概念 — 遊戲迴圈。如果遊戲是一部電影,遊戲迴圈就是放映機,一幀一幀快速播放讓畫面看起來流暢移動。

每個遊戲背後都有這樣的迴圈。它是一個函式,負責更新所有遊戲物件、重繪畫面並連續重複。它追蹤英雄、所有敵人和各種雷射 — 整個遊戲狀態。

此概念讓我想到早期動畫師如沃爾特·迪士尼,需要逐格重畫角色創造運動幻覺。我們做的是同樣事情,只是用程式碼取代鉛筆。

典型的遊戲迴圈程式碼長這樣:

flowchart TD
    A["開始遊戲循環"] --> B["清除畫布"]
    B --> C["填充背景"]
    C --> D["更新遊戲物件"]
    D --> E["繪製主角"]
    E --> F["繪製敵人"]
    F --> G["繪製介面元素"]
    G --> H["等待下一幀"]
    H --> I{遊戲正在運行?}
    I -->|是| B
    I -->|否| J["結束遊戲"]
    
    subgraph "幀率控制"
        K["60 幀 = 16.67 毫秒"]
        L["30 幀 = 33.33 毫秒"]
        M["10 幀 = 100 毫秒"]
    end
    
    style B fill:#ffebee
    style D fill:#e1f5fe
    style E fill:#e8f5e8
    style F fill:#e8f5e8
const gameLoopId = setInterval(() => {
  function gameLoop() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "black";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    drawHero();
    drawEnemies();
    drawStaticObjects();
  }
  gameLoop();
}, 200);

理解遊戲迴圈結構:

  • 清空整個畫布以移除上一幀畫面
  • 填充背景色
  • 繪製所有遊戲物件在其當前位置
  • 每 200 毫秒重複此程序以創造流暢動畫
  • 控制畫面更新速率透過設定間隔時間

繼續太空遊戲

現在我們將為之前建立的靜態場景加入動作。我們要將它從靜態截圖轉成互動體驗。每一步都會詳細操作,確保彼此相連。

從上一課的程式碼基礎開始(如果想重新開始,請用 Part II- starter 資料夾的程式碼)。

今天我們要做的是:

  • 英雄控制:使用方向鍵駕駛太空船在螢幕上移動
  • 敵機移動:外星船開始推進

讓我們開始實作這些功能。

建議步驟

找到 your-work 子資料夾中已為你建立的檔案。它們應該包括:

-| assets
  -| enemyShip.png
  -| player.png
-| index.html
-| app.js
-| package.json

你從 your-work 資料夾啟動專案,指令如下:

cd your-work
npm start

指令作用:

  • 切換到你的專案目錄
  • 啟動一個位於 http://localhost:5000 的 HTTP 伺服器
  • 提供遊戲檔案,方便你在瀏覽器中測試

啟動後在瀏覽器輸入 http://localhost:5000,你將看到英雄和所有敵人出現,但還沒有動作!

新增程式碼

  1. 新增專門物件heroenemygame object,它們應該擁有 xy 屬性。(記得之前關於繼承與組合的部分)

    提示 game object 應包含 xy 並能將自己繪製到畫布。

    技巧:先新增一個 GameObject 類別,建構函數如下,並畫出到畫布:

    class GameObject {
      constructor(x, y) {
        this.x = x;
        this.y = y;
        this.dead = false;
        this.type = "";
        this.width = 0;
        this.height = 0;
        this.img = undefined;
      }
    
      draw(ctx) {
        ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
      }
    }
    

    理解此基類:

    • 定義所有遊戲物件共用的屬性(位置、尺寸、影像)
    • 包含一個 dead 標記,用來追蹤物件是否應該被移除
    • 提供一個 draw() 方法,在畫布上渲染物件
    • 設定所有屬性的預設值,供子類別覆寫
classDiagram
    class GameObject {
        +x: number
        +y: number
        +dead: boolean
        +type: string
        +width: number
        +height: number
        +img: Image
        +draw(ctx)
    }
    
    class Hero {
        +speed: number
        +type: "英雄"
        +width: 98
        +height: 75
    }
    
    class Enemy {
        +type: "敵人"
        +width: 98
        +height: 50
        +setInterval()
    }
    
    GameObject <|-- Hero
    GameObject <|-- Enemy
    
    class EventEmitter {
        +listeners: object
        +on(message, listener)
        +emit(message, payload)
    }
接著,擴展這個 `GameObject` 來創建 `Hero` 與 `Enemy`

```javascript
class Hero extends GameObject {
  constructor(x, y) {
    super(x, y);
    this.width = 98;
    this.height = 75;
    this.type = "Hero";
    this.speed = 5;
  }
}
```

```javascript
class Enemy extends GameObject {
  constructor(x, y) {
    super(x, y);
    this.width = 98;
    this.height = 50;
    this.type = "Enemy";
    const id = setInterval(() => {
      if (this.y < canvas.height - this.height) {
        this.y += 5;
      } else {
        console.log('Stopped at', this.y);
        clearInterval(id);
      }
    }, 300);
  }
}
```

**這些類別的主要概念:**
- **繼承**自 `GameObject`,使用 `extends` 關鍵字
- **呼叫**父類別建構函數 `super(x, y)`
- **設定**每種物件的特定尺寸與屬性
- **利用**`setInterval()` 實現敵人的自動移動
  1. 新增鍵盤事件處理器以處理方向鍵導航(上下左右移動英雄)

    記得 座標系統是直角座標系,左上是 0,0。也別忘了加程式碼阻止預設行為

    技巧:創建 onKeyDown 函式並附加到 window

    const onKeyDown = function (e) {
      console.log(e.keyCode);
      // 加入以上課程中的代碼以阻止默認行為
      switch (e.keyCode) {
        case 37:
        case 39:
        case 38:
        case 40: // 方向鍵
        case 32:
          e.preventDefault();
          break; // 空格鍵
        default:
          break; // 不阻擋其他按鍵
      }
    };
    
    window.addEventListener("keydown", onKeyDown);
    

    這個事件處理器的作用:

    • 監聽整個視窗的 keydown 事件
    • 紀錄按鍵代碼方便你除錯按了哪些鍵
    • 阻止箭頭鍵及空白鍵的預設瀏覽器行為
    • 允許其他按鍵正常運作

    此時查看瀏覽器主控台,觀察按鍵記錄。

  2. 實作Pub sub pattern ,維持程式碼整潔並方便接下來的部分。

    發佈-訂閱模式能幫助你組織程式碼,分離事件偵測與事件處理,使程式更模組化且易維護。

    為了最後這步,你可以:

    1. 在視窗新增事件監聽器

      window.addEventListener("keyup", (evt) => {
        if (evt.key === "ArrowUp") {
          eventEmitter.emit(Messages.KEY_EVENT_UP);
        } else if (evt.key === "ArrowDown") {
          eventEmitter.emit(Messages.KEY_EVENT_DOWN);
        } else if (evt.key === "ArrowLeft") {
          eventEmitter.emit(Messages.KEY_EVENT_LEFT);
        } else if (evt.key === "ArrowRight") {
          eventEmitter.emit(Messages.KEY_EVENT_RIGHT);
        }
      });
      

    這個事件系統的功能:

    • 偵測鍵盤輸入並轉換為自定義遊戲事件
    • 分離輸入偵測與遊戲邏輯
    • 方便未來更改控制綁定而不影響遊戲程式碼
    • 允許多個系統回應相同輸入
flowchart TD
    A["鍵盤輸入"] --> B["視窗事件監聽器"]
    B --> C["事件發射器"]
    C --> D["鍵盤事件_↑"]
    C --> E["鍵盤事件_↓"]
    C --> F["鍵盤事件_←"]
    C --> G["鍵盤事件_→"]
    
    D --> H["英雄移動"]
    D --> I["音效系統"]
    D --> J["視覺特效"]
    
    E --> H
    F --> H
    G --> H
    
    style A fill:#e1f5fe
    style C fill:#e8f5e8
    style H fill:#fff3e0
  1. 建立 EventEmitter 類別來發布與訂閱訊息:

    class EventEmitter {
      constructor() {
        this.listeners = {};
      }
    
      on(message, listener) {
        if (!this.listeners[message]) {
          this.listeners[message] = [];
        }
        this.listeners[message].push(listener);
      }
    
    
  2. 新增常數並設置 EventEmitter

    const Messages = {
      KEY_EVENT_UP: "KEY_EVENT_UP",
      KEY_EVENT_DOWN: "KEY_EVENT_DOWN",
      KEY_EVENT_LEFT: "KEY_EVENT_LEFT",
      KEY_EVENT_RIGHT: "KEY_EVENT_RIGHT",
    };
    
    let heroImg, 
        enemyImg, 
        laserImg,
        canvas, ctx, 
        gameObjects = [], 
        hero, 
        eventEmitter = new EventEmitter();
    

理解這個設置:

  • 定義訊息常數以避免拼寫錯誤並方便重構
  • 宣告圖片、畫布上下文和遊戲狀態等變數
  • 建立一個全局事件發射器用於發布訂閱系統
  • 初始化 一個陣列以保存所有遊戲物件
  1. 初始化遊戲

    function initGame() {
      gameObjects = [];
      createEnemies();
      createHero();
    
      eventEmitter.on(Messages.KEY_EVENT_UP, () => {
        hero.y -= 5;
      });
    
      eventEmitter.on(Messages.KEY_EVENT_DOWN, () => {
        hero.y += 5;
      });
    
      eventEmitter.on(Messages.KEY_EVENT_LEFT, () => {
        hero.x -= 5;
      });
    
    
  2. 設定遊戲循環

    重構 window.onload 函式以初始化遊戲並設置以適當間隔運行的遊戲循環。你還會新增雷射光束:

    window.onload = async () => {
      canvas = document.getElementById("canvas");
      ctx = canvas.getContext("2d");
      heroImg = await loadTexture("assets/player.png");
      enemyImg = await loadTexture("assets/enemyShip.png");
      laserImg = await loadTexture("assets/laserRed.png");
    
      initGame();
      const gameLoopId = setInterval(() => {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "black";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        drawGameObjects(ctx);
      }, 100);
    };
    

    了解遊戲設定:

    • 等待 頁面完全載入後才開始
    • 取得 canvas 元素及其 2D 繪圖上下文
    • 非同步載入 所有圖片資產,使用 await
    • 啟動 遊戲循環,每 100 毫秒執行一次10 FPS
    • 清除 並重繪每一幀的整個畫面
  3. 新增代碼 以在特定間隔移動敵人

    重構 createEnemies() 函式,用來建立敵人並將它們推入新的 gameObjects 類別:

    function createEnemies() {
      const MONSTER_TOTAL = 5;
      const MONSTER_WIDTH = MONSTER_TOTAL * 98;
      const START_X = (canvas.width - MONSTER_WIDTH) / 2;
      const STOP_X = START_X + MONSTER_WIDTH;
    
      for (let x = START_X; x < STOP_X; x += 98) {
        for (let y = 0; y < 50 * 5; y += 50) {
          const enemy = new Enemy(x, y);
          enemy.img = enemyImg;
          gameObjects.push(enemy);
        }
      }
    }
    

    敵人建立做了什麼:

    • 計算 位置使敵人置中於畫面
    • 用巢狀迴圈 建立敵人網格
    • 設定 敵人影像給每個敵人物件
    • 加入 每個敵人到全域的遊戲物件陣列中

    並新增一個 createHero() 函式,用類似的程序為英雄建立物件。

    function createHero() {
      hero = new Hero(
        canvas.width / 2 - 45,
        canvas.height - canvas.height / 4
      );
      hero.img = heroImg;
      gameObjects.push(hero);
    }
    

    英雄建立做了什麼:

    • 定位 英雄於畫面底部中央
    • 指定 英雄影像給英雄物件
    • 加入 英雄到遊戲物件陣列以便渲染

    最後,新增一個 drawGameObjects() 函式開始繪製:

    function drawGameObjects(ctx) {
      gameObjects.forEach(go => go.draw(ctx));
    }
    

    了解繪圖函式:

    • 對陣列中所有遊戲物件迭代
    • 呼叫 每個物件的 draw() 方法
    • 傳入 canvas 上下文讓物件自行渲染

    🔄 教學檢視點

    完整遊戲系統理解:確認你對整個架構的掌握:

    • 繼承如何使 Hero 與 Enemy 共享共同的 GameObject 屬性?
    • 為什麼 pub/sub 模式使你的程式碼更易維護?
    • 遊戲循環在創造流暢動畫中扮演什麼角色?
    • 事件監聽器如何連結使用者輸入與遊戲物件行為?

    系統整合:你的遊戲現在展現了:

    • 物件導向設計:基底類別與專門的繼承
    • 事件驅動架構pub/sub 模式實現鬆耦合
    • 動畫框架:遊戲循環帶來一致的畫面更新
    • 輸入處理:鍵盤事件並防止預設行為
    • 資源管理:圖片載入與精靈繪製

    專業設計模式:你已實現:

    • 關注點分離:輸入、邏輯及渲染分開
    • 多型:所有遊戲物件共享相同繪製介面
    • 訊息傳遞:模組間的乾淨溝通
    • 資源管理:有效的精靈及動畫處理

    你的敵人應該會開始向你的英雄太空船推進! } }

    
    and add a `createHero()` function to do a similar process for the hero.
    
    ```javascript
    function createHero() {
      hero = new Hero(
        canvas.width / 2 - 45,
        canvas.height - canvas.height / 4
      );
      hero.img = heroImg;
      gameObjects.push(hero);
    }
    

    最後,新增一個 drawGameObjects() 函式開始繪製:

    function drawGameObjects(ctx) {
      gameObjects.forEach(go => go.draw(ctx));
    }
    

    你的敵人應該會開始向你的英雄太空船推進!


GitHub Copilot Agent 挑戰 🚀

這是一項能提升你遊戲質感的挑戰:加入邊界限制和平滑控制。目前你的英雄可以飛出畫面,而移動看起來有點卡頓。

你的任務: 讓你的太空船感覺更真實,實現畫面邊界與流暢移動。這類似 NASA 飛行控制系統防止太空船超出安全操作參數的方式。

你要建立什麼: 建立系統以保持英雄太空船不會飛出畫面,並讓操作觸感流暢。當玩家長按方向鍵時,飛船應持續滑行,而非一步步移動。考慮在船接近畫面邊界時給予視覺反饋 —— 例如微妙暗示已達遊戲區域邊緣的效果。

更多關於 agent mode 的資訊。

🚀 挑戰

隨著專案成長,程式碼組織變得越來越重要。你可能會發現檔案裡函式、變數、類別混在一起,非常混亂。這讓我想到阿波羅任務工程師必須建立清楚、可維護系統,讓多個團隊可以同時協作。

你的任務: 扮演軟體架構師的思維。你會如何組織程式碼,讓六個月後你(或隊友)還能輕易理解?即使目前全部都在同一個檔案裡,也可以做更好的組織:

  • 將相關函式分組,並用清楚的註解標頭
  • 分離關注點 - 保持遊戲邏輯與渲染分開
  • 使用一致命名 規範,變數與函式易於識別
  • 創建模組或命名空間,用來整理遊戲不同面向
  • 添加文件說明,解釋每個主要部分的功能

反思問題:

  • 哪些部分的程式碼在回來時最難理解?
  • 你如何組織程式碼使其他人更容易參與?
  • 如果想新增像強化道具或不同敵人種類,該怎麼做?

課後測驗

課後測驗

複習與自學

我們一直從零開始構建,這對學習很棒,但這裡有個小秘密 —— 有些驚人的 JavaScript 框架可以幫你完成很多繁重工作。一旦你熟悉我們教的基本概念,可以考慮探索可用框架

把框架想像成一個工具箱,不用自己手工打造每把工具,它們可以解決許多程式碼組織的難題,還提供你需要花好幾週才能做出的功能。

值得探究的事項:

  • 遊戲引擎如何組織程式碼 — 你會驚訝它們使用的巧妙模式
  • 性能技巧,讓 canvas 遊戲流暢運行
  • 現代 JavaScript 功能,讓程式碼更清晰易維護
  • 管理遊戲物件關係的不同方法

🎯 你的遊戲動畫精通路線圖

timeline
    title 遊戲動畫與互動學習進程
    
    section 動作基礎 (20 分鐘)
        動畫原理: 幀動畫
                : 位置更新
                : 坐標系統
                : 平滑移動
        
    section 事件系統 (25 分鐘)
        用戶輸入: 鍵盤事件處理
                : 預設行為阻止
                : 事件物件屬性
                : 視窗層級監聽
        
    section 遊戲架構 (30 分鐘)
        物件設計: 繼承模式
                : 基底類別創建
                : 專門化行為
                : 多態介面
        
    section 通訊模式 (35 分鐘)
        發佈/訂閱實作: 事件發射器
                      : 訊息常數
                      : 鬆散耦合
                      : 系統整合
        
    section 遊戲迴圈精通 (40 分鐘)
        即時系統: 幀率控制
                 : 更新/渲染循環
                 : 狀態管理
                 : 效能優化
        
    section 進階技巧 (45 分鐘)
        專業功能: 碰撞檢測
                 : 物理模擬
                 : 狀態機
                 : 元件系統
        
    section 遊戲引擎概念 (1 週)
        框架理解: 實體-元件系統
                 : 場景圖譜
                 : 資產管線
                 : 效能分析
        
    section 製作技能 (1 個月)
        專業發展: 代碼組織
                 : 團隊協作
                 : 測試策略
                 : 部署優化

🛠️ 你的遊戲開發工具摘要

完成本課程後,你已經掌握:

  • 動畫原理:基於幀的移動與平滑過渡
  • 事件驅動程式設計:鍵盤輸入與事件管理
  • 物件導向設計:繼承階層與多型介面
  • 溝通模式pub/sub 架構提升碼維護性
  • 遊戲循環架構:即時更新與渲染循環
  • 輸入系統:用戶控制對映與預設行為阻止
  • 資源管理:精靈載入與高效渲染技巧

你接下來 5 分鐘能做什麼

  • 打開瀏覽器控制台,嘗試 addEventListener('keydown', console.log) 觀察鍵盤事件
  • 建立簡單的 div 元素並用方向鍵移動它
  • 試試 setInterval 來創造連續移動效果
  • 嘗試使用 event.preventDefault() 阻止預設行為

🎯 你這一小時能達成的事

  • 完成課後測驗並理解事件驅動程式設計
  • 建置流暢鍵盤控制的移動英雄太空船
  • 實作平滑的敵人移動模式
  • 新增邊界防止遊戲物件飛出畫面
  • 建立基本的遊戲物件碰撞偵測

📅 你這周的動畫旅程

  • 完成完整太空遊戲,具有流暢移動與互動
  • 加入進階移動模式,如曲線、加速度、物理效果
  • 實作平滑過渡與緩動函式
  • 創建粒子效果與視覺反饋系統
  • 優化遊戲效能達成流暢 60fps
  • 加入手機觸控控制與響應式設計

🌟 你這月的互動開發

  • 建立復雜的互動應用與進階動畫系統
  • 學習如 GSAP 的動畫函式庫或自製動畫引擎
  • 參與開源遊戲與動畫開發專案
  • 精通圖形密集應用的性能優化
  • 創作遊戲開發與動畫的教育內容
  • 建立展示高階互動程式設計技術的作品集

實務應用:你的遊戲動畫技能適用於:

  • 互動式網頁應用:動態儀表板與即時介面
  • 資料視覺化:動畫圖表與互動圖形
  • 教育軟體:互動模擬與學習工具
  • 行動開發:觸控遊戲與手勢處理
  • 桌面應用程式Electron 應用平滑動畫
  • 網頁動畫CSS 和 JavaScript 動畫函式庫

專業技能獲得:你現在能夠:

  • 設計 可擴展的事件驅動系統
  • 實作 使用數學原理的平滑動畫
  • 除錯 複雜互動系統利用瀏覽器開發工具
  • 優化 不同裝置與瀏覽器的遊戲效能
  • 設計 使用成熟模式的可維護程式碼結構

遊戲開發核心概念掌握

  • 幀率管理:理解 FPS 與時間控制
  • 輸入處理:跨平台鍵盤及事件系統
  • 物件生命週期:建立、更新與銷毀模式
  • 狀態同步:保持遊戲狀態於各幀一致
  • 事件架構:遊戲系統間鬆耦合通訊

下一階段:你已準備好加入碰撞偵測、計分系統、音效,或探索 Phaser、Three.js 等現代遊戲框架!

🌟 成就解鎖:你已建立完整互動遊戲系統並運用專業架構模式!

作業

註解你的程式碼


免責聲明
本文件由 AI 翻譯服務 Co-op Translator 所翻譯。雖然我們致力於提供準確的翻譯,但請注意,自動翻譯可能包含錯誤或不準確之處。原始文件的母語版本應視為權威來源。對於重要資訊,建議使用專業人類翻譯。我們不對因使用此翻譯而引起的任何誤解或誤釋承擔責任。