2008年2月24日星期日

混合語言的遊戲開發系統架構

用甚麼程式語言來做軟件是一個大問題,思考了一個周末,現時想做一個混合語言的遊戲開發系統架構。暫時只考慮三種程式語言: C++、C# 及 Lua。以下首先分析這三種語言的特性。

C++

C++是一個 strongly typed、static、multi-paradigm (procedural, object-oriented, meta-programming) 的語言。基本上是遊戲引擎的標準語言,所以其實沒有甚麼第二選擇。

優點
  • 高移植性: 所有遊戲平台都提供 C++ 工具 (除了一些嵌入式系統,如只提供 C 或 Java)
  • 高效率: C++ 是通用語言中最高效的,無論是時間和空間上。
缺點
  • 程式庫不足: C++ 的標準程式庫是「簡而清」的,其實這是優點也是缺點。因為 C++ 本身可以在不同的應用層面及系統上,所以標準庫不可能加入如平台相關的 GUI、thread/process等程式庫,或應用相關的 image processing、encryption等功能。就是這樣,C++才會產生五花八門的第三方程式庫。經驗告訴我,要選擇、整合和維護第三方程式庫是不容易的。有時候不同的程式庫會在有兼容的問題,也會做成不協調(命名、記憶體管理等等)、或是功能上不能完全滿足需求。解決方法只有兩個──直接由程式庫的源代碼修改並嵌入系統裡、或放棄使用程式庫自己重做。
  • 高難度: 使用 C++ 的難度實在太高。其中一個原因是 C++ 的高自由度,你可以用不同的組合方式實現一個功能。另外是 C++ 接近低階,很多細節要程式員非常小心去處理,例如資源管理、pointers等等。要編寫及維護高質量的 C++ 程式是非常困難,看看剛畢業的人加入團隊時要花多少時間及培訓就可見一二了。
  • 編譯慢: C++ 源至 C,採用 #inlcude "header" 這種傳統文字方式去引用其他功能。就算採用了 pre-compiled headers 或 distributed compilation (如使用 IncrediBuild 軟件),一個大規模的系統都需要很長的編釋時間。一個 Edit-compile-run Cycle 所需要的時間成為開發速度的一個巨大因素。如有一些代碼需要 Tweaking,例如一些視覺效果及遊戲內容,用 C++ 去做的話實在會浪費太多時間在編釋上。
C#

C# 是為 .Net 而設、 strongly typed、static、object-oriented 的語言。C# 的語法參考了 C++的語法,但作了許多改善。

優點
  • 高階的語言功能: C#有許多高階的功能如 Garbage Collection、Reflection、Attribute、Serialization等等。在 C++ 要做到同樣的功能是可以的,但沒有語言本身的支持寫出來的代碼很不美觀、亦難以維護。
  • .Net 的程式庫: .Net 的程式庫可謂包羅萬有,由低階的 OS 功能,到公用的 Xml / Regular Expression,到應用層面的 GUI / Web / Database 都有很好的支持。API 的設計也做得很好 (相對於 Windows 上 C/C++ 的超大量新舊 API)。
  • 編譯快: 從 C# 編釋至 Microsoft Intermediate Language (MSIL) 是很快的。相對於 C++ 的 #include "header" 方式,.Net 語言採用引用 .Net assembly (e.g. DLL) 的資料來做編釋,節省很多時間 (包括 C++ 編釋時讀取及生成檔案的大量 I/O 時間)。
  • 中難度: 相對寫 C++ ,寫 C# 的人可能會長壽一點 (^_^)。C#代碼較簡潔美觀、不用分header/implementation 檔 (.h/.cpp)、不用那麼著緊資源管理 (雖然還是應該注意的)、不用調試release版本時看 assembly...
缺點
  • 跨平台問題: Microsoft 暫時沒有提供跨平台的 .Net,這是可以理解的。但理論上,.Net是應可以和 Java 一樣做到跨平台。開源的 MonoDotGNU 項目就是提供跨平台的 .Net 方案,包括 C#編繹器、Common Language Runtime (CLR) 和 .Net Framework Class Library等。早前看過一些 Mono 的資料,Mono 很成熟,也支持 embedding (可以用 C# 做 Script Engine),但暫時不考慮這個方案,詳情看後文。
  • 與其他程式庫連接問題: 雖然 .Net Framework Class Library 已經有大量標準的程式庫可供使用。但遊戲軟件必須使用一些特別的程式庫或API,例如 DirectX、OpenGL 等。C#提供P/Invoke及COM Interop機制去使用這些程式庫,但有一定的限制。

Lua

Lua 是一個 dynamic、weakly typed 的腳本語言。Lua 被應用到許多商業遊戲當中,最為人熟悉的例子如 Far cry/Crysis 用 Lua 來做 gameplay、World of Warcraft 用 Lua 做使用者介面。這些例子中 Lua 版本的 API 也開放給玩家來做 MOD 或 Add-on。

優點:
  • 輕量: Lua Runtime 加上它的 standard library 只是 100KB~200KB 左右。放在記憶體受限的系統也沒有問題(如可攜式遊戲 console)。
  • 執行快: 一般性而言, Lua 的速度比許多其他腳本語言快。
  • 低難度: 由於是 loosely typed,編寫腳本比較簡單及容易。
  • 動態: Lua 可以用字串生成代碼的執行,也可以在執行期為物加入 attribute 及 method。這是 Lua 與 C++/C# 設計上的一個重大分別,但其實這是一個特性,很難說是優點或缺點。
缺點:
  • 語法: 擁有自成一格的語法,和 C/C++/Java/C# 系列的語言很不同。未接觸過 Lua 的編程人員要花一點時間學習。
  • Object-oriented: Lua 語言是沒有制定 OO 的支持。但它的設計令使用者可以自行製作一套 OO 的系統,並加入一些 syntactic sugar 幫助編寫 OO 的代碼。
  • Unicode: 和 C/C++ 一樣,Lua 沒有特別支持 Unicode (可能是因為完整的 Unicode 支持可能需要很多代碼,使 Lua 變大)。要修改 Lua 的編釋器代碼,或使用 Lua 的 Mod 如 Lua Plus
分析

沒有一個程式語言適合做所有的任務。我把以上的資料編成一個簡單的表:



一個遊戲通常會由不同的人員製作,編程人員大概可以分為做 Technology、Toolset、Gameplay等領域。做 Technology 指做遊戲引擎核心部份,或客製化第三方的遊戲引擎。Toolset 包括面向不同使用者的軟件工具,從 Content pipeline (如滙入滙出檔案)、Asset Management、Level Editor及其他編輯工具等。而 Gameplay 是指遊戲內容中的行為部份,可以分為遊戲的核心行為 (如人物控制、戰鬥系統),及為個別人物及關卡編寫的行為 (如NPC對話、AI、任務、場境中的trigger等等)。

基本上 Technology 的部份需要高效、跨平台、和低階API連接。基本上只可以選擇 C/C++。

Toolset 的部份需要許多 GUI 的部份,也要因應使用者(美工、關卡設計、音效設計等)的要求迅速改變或加強功能。另外 Toolset 要處理不同種類的檔案,現時常見會使用 XML 作為中介的檔案 (如 COLLADA)。以上三種語言其實都可以使用來做 toolset。用 C++ 的話可以選擇一個 GUI API 如 Win32、MFC、WTL、wxWindows、Qt 等等、或使用自己開發的 GUI API。但是用 C++ 開發 GUI 的時候,編程困難程度比較高,編釋時間亦長。這兩點可能不符合需要經常改變需求的Toolset。如果使用腳本語言來做 GUI 的話,就需要一個好的程序庫連接及工具。許多時候這些腳本用的程序庫和原來的 GUI 程序庫會有追不到版本的問題。所以,我暫時認為用 C# 做大部份工具是比較好的選擇。.Net 提供的 GUI (Windows Form) 及 XML、Regular Expression 等功能也支持得很好。

而 Gameplay 的部份是經常要改動的。除了純粹改動數值外,還要在行為的處理上改動。對於個別人物和關卡的編程可能由關卡設計師負責,他們的編程能力可能相對較低。腳本語言比較適合。有一些情況也可以用視覺化的腳本來表達。首先嘗試用 Lua 吧。

設計科案

以下兩張圖是現時對於混合語言的遊戲開發系統架構的設計。

遊戲執行期



遊戲開發期



兩張圖的橙色部份都是由 Swig 生成的模組。

這個設計是基於以上的分析,把 Technology、Toolset和Gameplay的部份用各自最適合的語言來實現。

為了跨平台的需求,在執行期的版本是沒有C#的部份,只使用 Lua 的輕量腳本Runtime。

C++ Engine 設定為執行期和開發期的共通部份,所以把需要用 C++ 的 Toolset 部份抽取了出來。

而在遊戲開發期中,希望可以在工具裡直接運行及調試遊戲,所以會同時有三個語言的執行環境。

混合語言的優點
  • 各取所長: 透個組合各個語言的優點,可以增強開發的效率及最終的遊戲質量。
  • 適合不同的開發者: 做 Technology、Toolset 和 Gameplay 的開發人員在技能方面有所不同。不同語言各自照顧他們的需要。
  • 低藕合性 (Coupling): 不同語言的中間有一個獨立的介面,使藕合性降低。例如同樣的Gameplay代碼可以在不同版本的引擎運行。
混合語言的缺點
  • 更多知識: Technology 的編程人員應該需要學習這三種語言,及相關的連接事宜。
  • 多個介面: 需要建立及維護多個介面。希望 Swig 可以減輕這個問題。
  • 調試困難: 調試時誇越一個語言可能會增加調試的難度。

其他設計科案

在搜集資料的時候,看見可以把 Mono 的 CLR 嵌入程式中,就好像把 C# 用來做腳本語言。連接的實現方式類似於P/Invoke。用 Mono 作為 Gameplay 的腳本引擎是很吸引的。在效能上,由於採用 Just-in-Time (JIT) 把 MSIL 翻譯至 native code,執行速度一定會比直譯式的腳本快。C#在語法上跟C++/Java相似,許多人會懂得使用。此外,Mono 利用 CLR 的特性,還可以支持其他語言。

這個方案的其中一個問題是它不太 Light-weight。或許可以刪掉大部份的程式庫及JIT來改善。另一個想法是,透過另一種語言的結合,可以引證系統的可延展性。基於可延展性,在將來也可以考慮加入用Mono來做Scripting。所以暫時採用 Lua。

後記

原來只是想記錄一些想法,卻發現寫這篇日記花了多個小時。希望除了作為一個記錄,也可以引發大家一起討論,那麼也可能是值得的。

2 則留言:

Nietz 說...

Milo兄,很好的分析,我會常常看這裡的了:)

Swig 的Binding雖然已較完整,但有一些地方也不能直接連接。所以我想不大可能把所有的C++ code 全部連接。(不過這也不大需要),所以我想應只連接某一部份。

但提供那一部份作為連接,卻是個很頭痛的問題,太簡單,工能就不齊全,太多,又好像不如直接用C++ 好了。所以設計這介面時要很用心...

另一方面,除了Lua 和 Python (我不建議用Python,太heavy了),最近我在看
Squirrel
,語法更接近的script language,可以一看。

Milo 說...

Hi~ 好久不見了。

是,我也發覺 SWIG 只是幫助做 Binding,而不是完全做到,例如現時還未解決 .Net 的 value type 問題。但這些問題對我來說可能是好事,因為一開始就使用 Script,發現 C++ 的 Binding 有問題時,就可以遷就一下 Script。不會重滔以前先做 C++ 後做 Script 的覆轍。

我非常同意 Script 可使用那些 API, 個大問題。我會研究一下目前的遊戲怎樣分配,例如 CryEngine。不過我現在的心態是邊做邊學,有新的想法時就做多些 refactoring,不竟沒有死線,可以非常實驗性,試試 API 也試試新 gameplay。

Ming 少都介紹過 Squirrel 給我。它似乎是一個借鑒 Lua 又想加入自己特式的語言。但暫時不想考慮,因為 Lua 比較多例子參考。日後也可以利用 SWIG 去做多一個介面 (如 SWIG 在那時也不直接支持也可以自己做個 SWIG 的 extension)。

要過來上海玩就通知我啊。