遊戲編程

**遊戲編程**（）係電子遊戲製作嘅一環，指為隻電子遊戲做軟件工程：一隻遊戲嘅製作組做完設計，度好晒隻遊戲嘅規則應該係點之後，正路就要郁手寫程式，途中會用到電腦圖像同人工智能等嘅技術，而 MMOG 嘅編程仲會考製作組識唔識做網絡編程同埋數據庫。

一個遊戲程式嘅根基係**遊戲迴圈**：隻遊戲嘅程式係一個迴圈，要係噉重複做同一樣嘅工作，而唔係將個程式啲源碼逐句逐句行一次就算－遊戲程式要知玩家撳咗咩掣以及隻遊戲上一刻係喺咩狀態，按呢啲資訊同埋隻遊戲嘅法則，計遊戲世界下一刻嘅狀態應該係點。即係話一隻電子遊戲大致上可以想像成一個噉嘅 while 迴圈：

  **while** 隻  行緊
    睇吓 input 係乜
    噏哋遊戲世界
    產生 output
  **loop**

以上段碼就噉望落好簡單，但查實佢包含咗多個複雜嘅子系統：Input 可以包括鍵盤、踎士同手掣呀噉，而控制系統嘅設計可以高深得好交關，例如「要用邊啲掣做 input 先可以令玩家覺得舒服就手？」噉；個遊戲世界即係要攞玩家嘅 input 加埋個世界前一刻嘅狀態，用一大堆（表示個遊戲世界運作法則嘅）碼計出下一刻嘅狀態應該係點，例如如果玩家撳咗**向前**，玩家角色位置就要向住佢面向嘅方向改變；跟手個程式仲要有方法將新狀態顯示喺個熒光幕上面，涉及電腦圖像技術。喺現實應用裏面，上述嘅程式閒閒哋可以有幾萬行碼咁長。

雖然遊戲編程咁專業，但係都有啲人為興趣而做遊戲編程，而且仲有成功案例。

## 基本結構

一個遊戲程式結構大致如下：一隻遊戲嘅程式啟動嗰陣，程式要初始化－設好晒隻遊戲嘅參數嘅數值、講好晒要用邊啲資源... 等等。初始化後，個程式就要進入遊戲迴圈－喺程式編寫上，迴圈係一類控制流程機制，能夠令一段程式碼唔淨只係行一次咁簡單，而係會一路重複行，直至某啲條件達到為止。好似係以下呢段食鬼嘅虛擬碼噉，就用咗 while 迴圈：

  設 move_speed 數值做...
  設 number_of_ghost 數值做...
  ... *用若干行碼設定遊戲柞參數嘅數值*
  
  **while** player.lives > 0 *當玩家有多過 0 條命嗰陣一路做...（「玩家命變咗 0」就係 [[GAME OVER]] 條件）*
     // 讀取 input
     JoystickData j = *由手掣嗰度探測玩家撳咗乜掣*
     
     // Update 遊戲世界；睇**遊戲邏輯**同**人工智能**
     *基於玩家撳嘅掣，更新玩家角色嘅位置*
     *for 每一隻鬼 ([[foreach]] 鬼)*
        if *玩家撞到嗰隻鬼*
           *玩家就死*
        else
           *基於隻鬼嘅人工智能，更新佢嘅位置*
        end
     loop
     // *喺現實應用上，以上嘅各個動作通常會每個做一個[[子程式]]。*
     
     ...
     
     // 產生 output；睇**遊戲外觀**
     *喺熒光幕上面畫相應嘅影像*
     *... 同埋整聲效*
  loop

如果讀者遇到有基礎概念唔識，可以睇睇電腦程式編寫同控制流程等嘅內容。

## 讀取輸入

一個遊戲程式首要任務係要曉讀取玩家俾嘅輸入：一隻遊戲定義上就要能夠俾玩家有操控權，唔似得戲或者文學等淨係向觀眾單向噉傳達資訊；噉就表示隻遊戲梗要有某啲方法知玩家想做乜；喺廿一世紀初嘅家用遊戲機當中，玩家會有個手掣攞喺手，個手掣有若干個掣俾佢撳，每當佢撳一個掣，個掣就會有（好微弱、唔足以危害人類嘅）電傳返去部機度；喺是但一個時間點，部機都可以靠探測由手掣嚟嘅訊號，得知玩家撳咗邊幾個掣。

喺軟件方面，遊戲機內部對輸入嘅處理可以大致上想像成噉嘅 Arduino（一種用嚟為電子硬件做編程嘅程式語言）碼：


```arduino
const int UpButton = 12; /* 用一個變數代表個 [上] 掣有冇俾人撳 */
const int DownButton = 11; /* 另一個變數代表個 [下] 掣有冇俾人撳 */

void setup(){ 
   ...
} 

void loop(){ /* 程式迴圈 */
   if(digitalRead(UpButton) == HIGH){ /* 如果個 [上] 掣有訊號... */
	Serial.println("Up:");
	delay(100);
   }
   if(digitalRead(DownButton) == HIGH){ /* 如果個 [下] 掣有訊號... */
	Serial.println("Down:");
	delay(100);
   }
}
```


一個遊戲程式要編成（例如）感應到**上**掣有訊號嗰時，將個角色向前移動，移動幅度取決於個角色嘅速度－於是玩家就可以透過撳手掣上嘅掣，控制遊戲世界入面嘅嘢。其他平台嘅遊戲通常唔會用手掣，不過原理基本上一樣：好似係電腦遊戲當中，玩家會用踎士同鍵盤俾輸入；而呢啲輸入架生都係會傳訊號俾部電腦嘅主機。

### 指令詮釋

喺現實世界，出得街賣嘅遊戲多數有**指令詮釋器**：指令詮釋器係喺
*遊戲收到嘅輸入同
*世界噏哋
之間嘅詮釋機制；例如一隻賽車遊戲當中，喺玩家撳咗個 **X** 掣嗰陣，個遊戲程式要將呢個指令「翻譯」做**個玩家想加速**，跟住再按呢份資訊將玩家架車嘅加速度作出相應嘅改變。呢個過程就噉睇好似冇咩必要，但查實相當有用：有咗指令詮釋，遊戲製作組就可以整個模組出嚟，俾玩家設定（例如）**X** 掣對應邊個指令、**Y** 掣對應邊個指令... 呀噉。

例如如果冇指令詮釋器，一隻射擊遊戲嘅原始碼會好似噉：


```cpp
void InputHandler::handleInput()
{
  if (isPressed(BUTTON_X)) jump(); // 如果撳咗 X 掣，角色要跳。
  else if (isPressed(BUTTON_Y)) fireGun(); // 如果撳咗 Y 掣，角色會開槍。
  else if (isPressed(BUTTON_A)) swapWeapon(); // ...
  else if (isPressed(BUTTON_B)) lurchIneffectively();
}
```


而如果有指令詮釋器，段碼會比較似噉嘅樣：


```cpp
void InputHandler::handleInput()
{
  if (isPressed(BUTTON_X)) buttonX_->execute(); // 如果撳咗 X 掣，做 X 掣相應嘅功能；
  else if (isPressed(BUTTON_Y)) buttonY_->execute(); // 如果撳咗 Y 掣，做 Y 掣相應嘅功能；
  else if (isPressed(BUTTON_A)) buttonA_->execute();
  else if (isPressed(BUTTON_B)) buttonB_->execute();
}
```


而呢段碼會配合好似噉嘅碼：


```cpp
class Command // 定義一個 class，代表指令
{
public:
  virtual ~Command() {}
  virtual void execute() = 0;
};

class JumpCommand : public Command // 定義「跳」嘅指令
{
public:
  virtual void execute() { jump(); }
};

class FireCommand : public Command // 定義「開槍」嘅指令
{
public:
  virtual void execute() { fireGun(); }
};
... 如此類推 ...

class InputHandler // 定義一個 class 
{
public:
  void handleInput();

private:
  Command* buttonX_; // 每個掣都係一個 pointer，point 去某個指令。
  Command* buttonY_;
  Command* buttonA_;
  Command* buttonB_;
};
```


有咗指令詮釋器，製作組就可以俾玩家揀要唔要跟預設個控制方案，而如果佢唔要，佢想揀邊個掣代表邊個指令－指令詮釋器嘅存在令玩家有得將控制方案個人化。

## 遊戲邏輯

**遊戲邏輯**指隻遊戲入面有乜物件、每件物件包含乜變數、以及物件同物件之間點互動，遊戲邏輯亦都界定咗個遊戲程式要點樣對玩家嘅輸入俾反應。用返上面嗰段食鬼碼為例，隻遊戲嘅遊戲邏輯包含咗初始化當中嗰柞定義咗嘅變數同物件，以及個遊戲迴圈入面嘅 `process input` 同 `update game world` 當中「隻遊戲點運作」嘅法則。遊戲邏輯做電子遊戲設計嗰陣就要度掂。為一隻遊戲編定遊戲邏輯嗰陣要考慮嘅嘢包括咗：
*數據結構：一隻遊戲會包含大量嘅物件同相應變數，例如一隻一般嘅射擊遊戲會有若干把唔同嘅槍俾玩家揀，每把係一件物件，而佢哋分別喺「彈匣大細」同「每槍造成幾多傷害」等嘅變數上都有異；編寫一個遊戲程式需要思考數據結構要點處理－個程式內部要用乜方法儲起各變數同物件嘅數據。
*基於事件架構係一種軟件畫則上嘅範式。喺遊戲編程上，基於事件架構將一隻遊戲軟件視為一柞**事件**嘅集合體。一件事件係指一次系統狀態嘅改變—例如「架車開始加速，加速度提升若干數值」，而喺好多遊戲嘅程式當中都有**事件管理系統**：事件管理系統界定每種事件係乜同涉及乜數據；而喺做更新遊戲狀態嗰時，遊戲世界入面每件物件同每個子系統（睇 foreach）會睇吓發生緊嘅事件當中邊啲係同佢相關嘅，再按遊戲法則決定要點俾反應。

### 遊戲物理

遊戲物理指個遊戲世界嘅物理：除咗互動式小說等少數遊戲類型之外，電子遊戲多數都會有個空間俾玩家控制某啲嘢喺入面郁動，所以一隻遊戲嘅世界多數要有物理法則主宰啲嘢應該點郁（唔一定合乎現實世界嘅物理定律），而喺「更新遊戲世界狀態」嘅過程當中，個遊戲程式要按照遊戲嘅物理法則同玩家輸入計返個世界嘅狀態應該點變，例如一隻用牛頓力學嘅賽車遊戲，喺玩家撳咗「踩油」嘅掣嗰陣，要改變架車嘅加速度數值，而架車相應嘅速度同位置等變數亦要跟牛頓力學裏面嘅法則作出相應嘅改變。

例如以下呢段用 C 寫嘅碼可以攞嚟模擬喺牛頓第二定律之下郁動嘅物體，喺動作遊戲當中基本上實會用到：


```cpp
double t = 0.0;
float dt = 1.0f;

float cuk_dou = 0.0f;
float wai_zi = 0.0f;
float lik = 10.0f;
float zat = 1.0f;
// 設一大柞變數，包括咗時間點（t）、時間間隔（dt）、速度（cuk_dou）、位置（wai_zi）、件物體受嘅力（lik）、同件物體嘅質量（zat）。

while ( t <= 10.0 ) // 重複噉計若干次，計到時間點係 10 為止。
{
    wai_zi = wai_zi + cuk_dou * dt;
    cuk_dou = cuk_dou + ( lik / zat ) * dt; // 用牛頓第二定律計吓件物體受嘅力同佢嘅質量會點影響佢嘅速度。
    t += dt;
}
```


呢段嘢會俾嘅輸出如下，佢列嗮佢模擬嗰件物體喺每個時間點 t 嘅位置同速度出嚟：


```cpp
t=0:    wai_zi = 0      cuk_dou = 0
t=1:    wai_zi = 0      cuk_dou = 10
t=2:    wai_zi = 10     cuk_dou = 20
t=3:    wai_zi = 30     cuk_dou = 30
t=4:    wai_zi = 60     cuk_dou = 40
t=5:    wai_zi = 100    cuk_dou = 50
t=6:    wai_zi = 150    cuk_dou = 60
t=7:    wai_zi = 210    cuk_dou = 70
t=8:    wai_zi = 280    cuk_dou = 80
t=9:    wai_zi = 360    cuk_dou = 90
t=10:   wai_zi = 450    cuk_dou = 100
```


### 人工智能

遊戲編程會用到人工智能（AI）嘅技術：好多遊戲嘅關卡入面都會有敵人俾玩家打，啲敵人要曉唔淨只係企喺度，仲要識探測玩家嘅位置，並且攻擊玩家，以及對玩家嘅攻擊作出迴避等等－即係話要令遊戲角色作出有智能嘅行為，而「令機械展示智能」正正就係人工智能嘅定義。

一般嘅電子遊戲人工智能有以下嘅部份：
***刺激詮釋器**：刺激詮釋器會話俾一個人工智能程式知個遊戲狀態係點；一般嚟講，每個個體敵人都會有個獨立嘅人工智能（除非隻遊戲係講玩家角色打緊一班具有集合心靈嘅外星人），每個智能會透過刺激詮釋器得到佢哋應該得到嘅資訊，跟住個程式要有方法表示「foreach 敵人，嗰個敵人手上有乜資訊」。
***決策系統**：每一個人工智能都要按手上嘅資訊同自己嘅目的，計出跟住要採取乜嘢行動；例如一個敵人見到玩家，知道玩家嘅位置，而佢目的係要行埋玩家度攻擊佢，佢個人工智能程式就要有個方法教佢點樣用「玩家嘅位置」同「自己嘅位置」（個決策系統嘅輸入）計出「自己應該向乜方向移動」（個決策系統嘅輸出）。
舉例說明，喺遊戲 AI 上，其中一樣最基本嘅嘢係教 AI 追敵人，例如遊戲敵人追蹤玩家，嘗試接近同攻擊玩家角色。以下係教一個 AI **追蹤** `seek` 一件物件嘅演算法：


```csharp
class KinematicSeek:
  // Declare 個演算法控制緊邊個角色同要追嘅目標
    gok_sik, target
  // Declare 個角色嘅最大速度
    maxCuk

  def getSteering():
    // 整個 output 嘅結構
    steering = new KinematicSteeringOutput()
    // 攞個 steering 嘅方向
    steering.velocity = target.position - gok_sik.position // 如果將呢個數變成隨機，可以用嚟教個 AI 隨機行嚟行去（wandering）。
    if steering.velocity.length() < radius: // 如果同目標之間嘅距離細過某個特定數值，就唔使郁。
      return none
    // 計出個 exact 嘅速度
    steering.velocity.normalize()
    steering.velocity *= maxCuk
    // 轉向要郁嘅方向
    gok_sik.orientation = getNewOrientation(gok_sik.orientation,steering.velocity)
    // 將個 steering 俾做 output
    steering.rotation = 0
    return steering
```


順帶一提，將 `target.position - gok_sik.position` 改做 `gok_sik.position - target.position` 就可以令段演算法變成教個 AI 避開一件物件。再進一步嘅演算法仲會教埋個 AI 離目標有返咁上下近就要剎掣、將方向對準、以及將自己嘅速度變成同目標一致等等嘅功能。

## 遊戲外觀
遊戲外觀包含一柞將遊戲邏輯向觀察者（通常指玩家）展現嘅子系統：一隻遊戲需要俾玩家決定要作出乜行動，而為咗俾玩家知要做決策，個程式實要以某啲方式，呈現遊戲世界嘅狀態俾玩家知；喺廿一世紀嘅應用當中，遊戲世界通常會用有吸引力嘅電腦圖像（CG）再加埋適當嘅聲音嚟呈現。喺攞咗玩家輸入，又計好遊戲世界嘅新狀態之後，個遊戲程式要做嘅嘢就係度好「個熒光幕要顯示乜圖像」以及「啲音響喇叭要出乜嘢聲」－負責做呢個工序嘅遊戲程式部份就係遊戲外觀系統。

### 影像

顯示影像嘅方法要睇隻遊戲係用二維定三維影像嘅做法：
*二維圖像會用精靈圖結合埋一齊整景。舉個例子說明，喺一隻二維嘅《超級瑪利奧》遊戲入面，個遊戲程式會記住主角嘅精靈圖同埋啲怪獸嘅精靈圖，而個主角瑪利奧嘅精靈圖又會包含咗（例如）佢企喺度嗰陣嘅樣、佢行路嗰陣嘅樣、同埋佢跳嗰陣嘅樣等等。喺每一個時間點，個程式都會按「個主角同每隻怪獸喺邊個位置，喺度做緊乜」等嘅資訊，揀應該用邊幾幅精靈圖，並且按照每件物件嘅位置，將呢啲精靈圖同個背景合拼埋一齊，最後形成一個畫面，最後個程式要將呢個畫面傳去個熒光幕嗰度，等個熒光幕將個畫面顯示出嚟。
*三維圖像會用到三維模型。一個三維模型係一個（對於部電腦嚟講）用數字呈現嘅三維物體，而整一幅三維嘅電腦圖像過程要用彩現嘅方法：想做彩現嘅人會首先整一個景檔案出嚟，呢個檔案會包含多種資訊，包括「個景入面用咗啲乜嘢立體模型」、「每個立體模型喺邊個位置」、「光源喺邊」、「個鏡頭擺喺邊個位置」、同埋「幾何變換」（睇下面）呀噉，描述個景係點嘅；然後部電腦會將呢個檔案入面嘅數據傳去一個彩現程式嗰度，等個程式做一大柞人手做唔嚟嘅運算，計出個鏡頭會睇到嘅（二維）影像應該係點樣嘅，並且將個鏡頭所睇到嘅影像俾出嚟做輸出。
*無論二維定三維圖像，一個電子遊戲程式會係噉做「基於遊戲狀態更新影像」嘅工作，而且一秒內做閒閒地做幾廿次－由人眼嚟睇就好似係識郁嘅影像噉。

用虛擬碼表達大致上會係噉：

  設定背景係咩樣。 *// 將背景設做應有嘅色水。*
  
  **for each** 物件 *// for each 物件，用精靈圖（2D 嘅話）或者三維模型（3D 嘅話）畫佢出嚟。*
      攞件物件嘅位置
      攞作物件嘅精靈圖或者三維模型
      畫佢出嚟

### 聲音

電子遊戲程式會內有若干個聲檔案，喺某啲事件發生嗰陣，個程式要按「玩家角色離發聲物件幾遠」等嘅資訊，傳訊號去音響喇叭嗰度，教個喇叭出乜嘢聲。電子遊戲涉及三大種聲音：
*音樂：個遊戲程式一日喺某個狀態（「喺標題畫面」、「喺第一關」、「喺第二關」... 等等）就要一路循環播嘅聲，會用到 while 迴圈（while 隻遊戲喺呢個呢個狀態，播呢首呢首音樂）。音樂對於營造一隻遊戲嘅氣氛嚟講不可或缺。
*聲效：指喺某啲事件發生嗰時一次性嘅聲，例如係一隻賽車遊戲入面，每當有兩架車相撞，個遊戲程式要叫個喇叭播出相應嗰個聲檔案嘅內容。
***講嘢**：係三種聲入面最難搞嗰種；因為喺多數情況下，個遊戲程式唔淨只要叫喇叭播出個聲檔案，仲要確保啲聲同講緊嘢嗰個角色嘅三維模型嘅嘴唇嘅郁動同步（而且喺製作上，整講嘢聲要特登請配音員返嚟做）。有啲遊戲程式會索性有個數據庫，儲住「人類嘴唇喺發每種聲嗰時係乜樣」嘅資訊，再喺要播講嘢聲嗰陣，即場由啲聲計出個角色嘅三維模型嘅嘴唇要係乜樣。

喺每一個時間點 t，個遊戲程式要考慮有邊幾個聲檔案喺度播緊，再（foreach 播緊嘅聲檔案）按「呢個聲檔案處於邊個時間點」以及「呢個聲源離玩家角色幾遠」等嘅資訊，計出佢喺嗰個時間點會造成乜嘢聲（一個聲有**幾高音**同**幾大聲**等都可以用數字表示），再將呢啲資訊傳去音響喇叭嗰度。

## 編程概念

一般認為，要寫遊戲程式，起碼要識晒以下呢啲編程概念先：
*變數（局部同全局）
*條件運算式
*迴圈（同每個... 做）
*子程序
*物件導向編程（類別物件）
*例外處理
*數據結構

遊戲編程會用到好多種設計模式：
*命令模式
*享元模式
*觀察者模式
*原型模式
*單例模式
等等。

## 編程架生

常用嚟做遊戲編程嘅程式語言同架生有以下呢啲（要留意嘅係，遊戲好多時會用多過一款語言寫）：
*組合語言（多數係廿世紀嘅遊戲先會用嘅）
*C 、C++ 、C#
*Java 
*Lua 
*DirectX 
*Direct3D 
*OpenGL 

## 睇埋
*電子遊戲設計同製作
*電腦編程同網絡編程
*遊戲模組
*程序生成
*物件生成
*遊戲描述語言
*打機用語

## 文獻

*Lengyel, E. (2001). *Mathematics for 3D game programming and computer graphics*. Charles River Media, Inc..
*McShaffry, M. (2014). *Game coding complete*. Nelson Education.
*Nystrom, R. (2014). *Game programming patterns*. Genever Benning，呢本書重點講解遊戲編程上成日用嘅設計模式。


## 引咗
呢篇文用咗嘅行話詞彙，英文版本如下：

呢篇文引用咗以下呢啲文獻同網頁：

## 拎
* 經典遊戲 Pong 用 Python 嘅 Pygame 整，一個 170 行碼咁短嘅簡單 Python 程式，寫緊《乓》呢隻經典遊戲。個程式有齊晒初始化定義好啲變數、遊戲開始嗰陣擺低個波、畫個遊戲畫面、以及係探測玩家嘅 input。 
* PyGame 教學，教人用 pygame 寫遊戲程式。
* GameDev.net
* 國際遊戲開發者協會 (IGDA)
* 2D 遊戲開發 wiki
* 遊戲開發 Wiki - GDwiki