因種種原因,本 Blog 現已搬到 http://miloyip.seezone.net/。
感謝 seezone 提供網頁空間!
2008年2月29日星期五
2008年2月27日星期三
Immediate Mode GUI
今晚主要在思考 GUI 的問題。因為之前看 mono 提及 Unity 這個引擎,便去看 Unity 對 GUI 的部份,參考它支持甚麼功能和如何跟 Script 結合。
一看之下,竟然是這種奇異的寫法:
後來再在網上找到了相關的討論及短片(我無看足全程),稱傳統的 GUI 系統為 Retained Mode (下簡稱RM),這種系統為 Immediate Mode (下簡稱IM)。
傳統的 RM 做法是建立 GUI 物件的 Hierarchy,之後當有輸入時就通知應用程式 (event-driven)。而 IM 就不需要建立物件,純粹以 procedural 的運行方式顯示及處理反饋。
乍看之下,IM 的函式非常簡單,亦不需要額外的記憶體,好像是一個很好的方案。但再想了一會,相對 RM 的做法好像沒有那麼好:
P.S. 今天無代碼上的進展......
一看之下,竟然是這種奇異的寫法:
function OnGUI () {
if (GUI.Button (Rect (10,10,150,100), "I am a button")) {
print ("You clicked the button!");
}
}
其原理是,Unity 在一個 frame 裡會執行這個函式最多兩次,第一次 GUI.Button() 會渲染那個按鈕,第二次是處理輸入,如果GUI.Button()被按下,就傳回 true。後來再在網上找到了相關的討論及短片(我無看足全程),稱傳統的 GUI 系統為 Retained Mode (下簡稱RM),這種系統為 Immediate Mode (下簡稱IM)。
傳統的 RM 做法是建立 GUI 物件的 Hierarchy,之後當有輸入時就通知應用程式 (event-driven)。而 IM 就不需要建立物件,純粹以 procedural 的運行方式顯示及處理反饋。
乍看之下,IM 的函式非常簡單,亦不需要額外的記憶體,好像是一個很好的方案。但再想了一會,相對 RM 的做法好像沒有那麼好:
- 其實 IM 和 RM 都要用一定的內存。如果 RM 是用 Script 或描述檔案 (e.g. XML) 來產生 GUI 物件,那段 Script 或描述檔案 在產生 GUI 物件之後就可以釋放,而它所描述的訊息只是轉化為內存的物件吧。所以兩種方法也是包含同樣的資訊,只是形式不同。
- IM 的 GUI 是 Stateless 的。因此一些需要 state 的 GUI 物件需要由函式外傳入及取回,例如
textFieldString = GUI.TextField (Rect (25, 25, 100, 30), textFieldString); - IM 執行效率應比一般的 RM 低。渲染 GUI 是每個 frame 都要處理的,而發生輸入事件的頻率相對非常低。IM 需要每個 frame 運行 Script,而 RM 可以在 C++ 獨立處理 GUI Hierarchy 的渲染,並且處理輸入後才產生 event 通知 Script。但如果 IM 的代碼也是用 C++ 寫的話,情況可能會相反,因為所有 GUI call 都是 static binding,而一般 RM 的做法都需要 virtual function call (例如渲染的函式)。
- 要做 IM GUI 的編輯工具比較困難。可能要用 RM 的資料去生成 IM 的代碼,但這樣做的話,連 event handling 的代碼也要從 RM 的資料插進去 IM ,那麼,IM 就好像沒有意義了。這可能就是 Unity 沒有 GUI 編輯工具的原因。
- 和第 4 點相關,RM 把物件的狀態描述和行為分離,減低 coupling。而 IM 反而應為兩者結合在一起編程比較容易。這讓我想起用 C 寫 CGI 和用 XML/XSLT 做網頁的日子......
P.S. 今天無代碼上的進展......
2008年2月25日星期一
Lua 的 Unit Testing
今天成功把 Lua 嵌入Engine (其實只是十數行代碼及設定連結等),之後開了一個新 C++ project 執行一個 Lua 檔案去測試 Engine 提供的介面。
在網上找了一會,選擇了 luaUnit 來做 Unit Testing。luaUnit 的好處是使用 Lua 的 "Reflection" (其實只是找 table 吧),自動找尋所有 test suites。使用起來真的很簡單。以下是我做的 unit test。
執行結果
找到錯誤
Oh! 竟然有一個錯誤! (當然是debug了其他錯誤後還有一個不能解決,而不是第一次寫 Lua 就沒錯)
找了一會,發現有文章講述 Lua 5.1 在呼叫使用者定義的 unary minus operator (__unm) 時傳入兩個 self 的 operands。因此 SWIG 會發生問題。
原本只是想用來測試系統架構及嘗試寫 unit test,今次,又再次證實 unit testing 真的是很有用啊。又想起這句名言
Vector3 在 Lua 的語意上是 reference type,c = a 是指兩個變數都是指著同一個物件。要寫 c = Math.Vector3(a) 才能做 copy。這可能會很麻煩而且容易出錯。Lua又好像不能 overload assignment operator。有無甚麼好提議呢?
此外,static member function 像 Dot() 好似不太好用。還是用 % 及 ^ 做 dot 及 cross product 吧。這兩個 operator Lua 都支持。
在網上找了一會,選擇了 luaUnit 來做 Unit Testing。luaUnit 的好處是使用 Lua 的 "Reflection" (其實只是找 table 吧),自動找尋所有 test suites。使用起來真的很簡單。以下是我做的 unit test。
require('luaunit')
TestVector3 = {}
function TestVector3:setUp()
self.a = Math.Vector3(1, 2, 3)
self.b = Math.Vector3(4, 6, 8)
end
function TestVector3:tearDown()
self.a = nil
self.b = nil
end
function TestVector3:testComponent()
assertEquals(self.a.x, 1)
assertEquals(self.a.y, 2)
assertEquals(self.a.z, 3)
end
function TestVector3:testCopy()
local c = Math.Vector3(self.a);
assertEquals(c.x, 1)
assertEquals(c.y, 2)
assertEquals(c.z, 3)
end
function TestVector3:testNegation()
print(-self.a)
assertEquals(c.x, -1)
assertEquals(c.y, -2)
assertEquals(c.z, -3)
end
function TestVector3:testAdd()
local c = self.a + self.b
assertEquals(c.x, 5)
assertEquals(c.y, 8)
assertEquals(c.z, 11)
end
-- Other operators, skipped here
function TestVector3:testSquredLength()
assertEquals(self.a:GetSquaredLength(), 14)
end
function TestVector3:testLength()
assert(math.abs(self.a:GetLength() - math.sqrt(14)) < 0.00001)
end
function TestVector3:testNormalize()
local c = self.a:GetNormalized();
assert(math.abs(c:GetLength() - 1) < 0.00001)
local d = c * self.a:GetLength()
assert((d - self.a):GetLength() < 0.00001)
local e = Math.Vector3(self.a)
e:Normalize()
assertEquals(c, e)
end
function TestVector3:testDot()
assertEquals(40, Math.Vector3_Dot(self.a, self.b))
end
LuaUnit:run()
執行結果
>>>>>>>>> TestVector3
>>> TestVector3:testAdd
>>> TestVector3:testComponent
>>> TestVector3:testCopy
>>> TestVector3:testDivide
>>> TestVector3:testDot
>>> TestVector3:testEquality
>>> TestVector3:testInequality
>>> TestVector3:testLength
>>> TestVector3:testMultiply
>>> TestVector3:testNegation
Error in operator - expected 1..1 args, got 2
stack traceback:
.\luaunit.lua:288: in function <.\luaunit.lua:287>
[C]: ?
main.lua:29: in function 'testNegation'
[string "TestVector3:testNegation()"]:1: in main chunk
[C]: in function 'xpcall'
.\luaunit.lua:292: in function 'runTestMethod'
.\luaunit.lua:309: in function 'runTestMethodName'
.\luaunit.lua:337: in function 'runTestClassByName'
.\luaunit.lua:369: in function 'run'
main.lua:95: in main chunk
Failed
>>> TestVector3:testNormalize
>>> TestVector3:testSquredLength
>>> TestVector3:testSubtract
=========================================================
Failed tests:
-------------
>>> TestVector3:testNegation failed
Error in operator - expected 1..1 args, got 2
stack traceback:
.\luaunit.lua:288: in function <.\luaunit.lua:287>
[C]: ?
main.lua:29: in function 'testNegation'
[string "TestVector3:testNegation()"]:1: in main chunk
[C]: in function 'xpcall'
.\luaunit.lua:292: in function 'runTestMethod'
.\luaunit.lua:309: in function 'runTestMethodName'
.\luaunit.lua:337: in function 'runTestClassByName'
.\luaunit.lua:369: in function 'run'
main.lua:95: in main chunk
Success : 92% - 12 / 13
找到錯誤
Oh! 竟然有一個錯誤! (當然是debug了其他錯誤後還有一個不能解決,而不是第一次寫 Lua 就沒錯)
找了一會,發現有文章講述 Lua 5.1 在呼叫使用者定義的 unary minus operator (__unm) 時傳入兩個 self 的 operands。因此 SWIG 會發生問題。
原本只是想用來測試系統架構及嘗試寫 unit test,今次,又再次證實 unit testing 真的是很有用啊。又想起這句名言
「唔試過又點知佢work呢?」後話
Vector3 在 Lua 的語意上是 reference type,c = a 是指兩個變數都是指著同一個物件。要寫 c = Math.Vector3(a) 才能做 copy。這可能會很麻煩而且容易出錯。Lua又好像不能 overload assignment operator。有無甚麼好提議呢?
此外,static member function 像 Dot() 好似不太好用。還是用 % 及 ^ 做 dot 及 cross product 吧。這兩個 operator Lua 都支持。
2008年2月24日星期日
混合語言的遊戲開發系統架構
用甚麼程式語言來做軟件是一個大問題,思考了一個周末,現時想做一個混合語言的遊戲開發系統架構。暫時只考慮三種程式語言: C++、C# 及 Lua。以下首先分析這三種語言的特性。
C++
C++是一個 strongly typed、static、multi-paradigm (procedural, object-oriented, meta-programming) 的語言。基本上是遊戲引擎的標準語言,所以其實沒有甚麼第二選擇。
優點
C# 是為 .Net 而設、 strongly typed、static、object-oriented 的語言。C# 的語法參考了 C++的語法,但作了許多改善。
優點
Lua
Lua 是一個 dynamic、weakly typed 的腳本語言。Lua 被應用到許多商業遊戲當中,最為人熟悉的例子如 Far cry/Crysis 用 Lua 來做 gameplay、World of Warcraft 用 Lua 做使用者介面。這些例子中 Lua 版本的 API 也開放給玩家來做 MOD 或 Add-on。
優點:
沒有一個程式語言適合做所有的任務。我把以上的資料編成一個簡單的表:
一個遊戲通常會由不同的人員製作,編程人員大概可以分為做 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 部份抽取了出來。
而在遊戲開發期中,希望可以在工具裡直接運行及調試遊戲,所以會同時有三個語言的執行環境。
混合語言的優點
其他設計科案
在搜集資料的時候,看見可以把 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。
後記
原來只是想記錄一些想法,卻發現寫這篇日記花了多個小時。希望除了作為一個記錄,也可以引發大家一起討論,那麼也可能是值得的。
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# 是為 .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 一樣做到跨平台。開源的 Mono 和 DotGNU 項目就是提供跨平台的 .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。
後記
原來只是想記錄一些想法,卻發現寫這篇日記花了多個小時。希望除了作為一個記錄,也可以引發大家一起討論,那麼也可能是值得的。
2008年2月22日星期五
Unreal 3 at GDC08
Tim Sweeney 在 GDC 介紹了 Unreal 3 的新功能。
Ambient Occlusion
因為說明了是Post-rendering Effect,即是Screen Space Ambient Occlusion (SSAO)。這並不算是很新的技術,已出版的遊戲Crysis也有這個技術。好處是不用修改現有的Rendering Pipeline,只要有Depth或Position buffer,再加上normal的buffer就可以模擬Ambient Occlusion。
Crowd Simulation
他說用了「Flocking Technology」做 Crowd Simulation。近畫面的人物移動時有一些會因為路面變窄而有停頓,但人與人之間有避免碰撞,行為做得不錯。而 Rendering 方面應該是一般的instancing,在xbox360許多遊戲也做到相同的效果。
Fluid Surface Simulation
估計是由原來的水波模擬(e.g. FFT + noise)再加上可互動的height map。這應該是可以用簡化的 Navier-Stroke 方程在 GPU 做到,要儲存height及velocity map,再用 height map 生成 normal map。我諗會難點在於如何控制那張map的resolution及位置,對於大面積的水面可能要不斷不移這些map。
Soft Body Dynamics
這是由AGEIA物理引擎提供的,基本上是純粹功能整合。
Frature System
我諗為這是比較突出的部份,相信最困難是如何在場境編輯時設定。
總結
最後,一如以往,Unreal Engine 給予我的感覺是技術運用的很好的一個產品。雖然在技術上並不會很突破,但整合及工具方面都做的非常好。目前能與它比較的也許只有Cry Engine。
每次看到一些技術就會心癢,希望可以盡快做一個 test bed 出來去做不同的嘗試。
Ambient Occlusion
因為說明了是Post-rendering Effect,即是Screen Space Ambient Occlusion (SSAO)。這並不算是很新的技術,已出版的遊戲Crysis也有這個技術。好處是不用修改現有的Rendering Pipeline,只要有Depth或Position buffer,再加上normal的buffer就可以模擬Ambient Occlusion。
Crowd Simulation
他說用了「Flocking Technology」做 Crowd Simulation。近畫面的人物移動時有一些會因為路面變窄而有停頓,但人與人之間有避免碰撞,行為做得不錯。而 Rendering 方面應該是一般的instancing,在xbox360許多遊戲也做到相同的效果。
Fluid Surface Simulation
估計是由原來的水波模擬(e.g. FFT + noise)再加上可互動的height map。這應該是可以用簡化的 Navier-Stroke 方程在 GPU 做到,要儲存height及velocity map,再用 height map 生成 normal map。我諗會難點在於如何控制那張map的resolution及位置,對於大面積的水面可能要不斷不移這些map。
Soft Body Dynamics
這是由AGEIA物理引擎提供的,基本上是純粹功能整合。
Frature System
我諗為這是比較突出的部份,相信最困難是如何在場境編輯時設定。
總結
最後,一如以往,Unreal Engine 給予我的感覺是技術運用的很好的一個產品。雖然在技術上並不會很突破,但整合及工具方面都做的非常好。目前能與它比較的也許只有Cry Engine。
每次看到一些技術就會心癢,希望可以盡快做一個 test bed 出來去做不同的嘗試。
試驗SWIG (一) C++ 連接C#和Lua
今晚初次嘗試使用 SWIG (Simple Wrapper Interface Generator)。SWIG是一個能為C++程式生成各種Script語言Wrapper的工具。簡單地說,就是有了一個C++程式,用SWIG來連結這個程式和Script,使腳本語言可以呼叫C++的函數及類別等等。
SWIG支持大部份C++的功能,甚至template也能支持。同時可以生成十數種腳本語言的wrapper,包括我正在考慮使用的Lua、Python和C#等。
跟據我的目標,先做一個3D Vector的測試。以下是一部份C++代碼 (Vector3.h):
之後寫一個SWIG的interface檔案 (Math.i):
最後幾行是因為,以我所知,SWIG對C#並不支持operator overloading。SWIG本身是支持operator overloading的,或許要深入研究一下SWIG的Wrapper/Proxy生成過程可不可以加入這功能。
C#的測試
做C#的時候,用swig -c++ -csharp -o Math_wrap.cxx Math.i,就會新成三個檔案,分別為用來做C++ Wrapper的Math_wrap.cxx、用來做C# Proxy的MathPInvoke.cs和Vector3.cs。
接著就把Math_wrap.cxx編成DLL,而MathPInvoke.cs和Vector3.cs就可以放到C#的Project裡使用。
剛看到GDC的XNA演講提及.Net的速度,就直接用來比較一下。使用了SWIG生成的Proxy,使用Vector3類別和C#寫的沒有分別:
結果留到最後和Lua一起比較吧。
Lua的測試
之前的Math.i和其他檔案都不用改,直接執行swig -c++ -lua -o Math_wrap.cxx math.i,就會生成Math_wrap.cxx這個proxy。和C#不同,是不需要Lua的Proxy的。把這個連結成Math.dll後,在Lua只需一句Require("Math")就行了!這實在太簡單!(這麼簡單的功能是在Lua 5.1才有)
同樣的測試用Lua來寫:
以前看了Lua的書,但沒有寫過,發覺Lua寫起來也很簡單,而且SWIG做的Binding也不錯。最容易錯的地方就是把object:method(...)寫成object.method(...)。
結果和分析
結果是以每秒執行iteration次數計算,數字越大表示越好。
這個比較是有數個問題。一個估計XNA的測試裡會有大量的Particle instances,但我的測試只用了一組local variables,多instance會導致cache miss,應該會慢許多,另外這亦需要用iterator或index去存取instances。另外是我的電腦是Core2 2.4G,應該比XBox360快。再者,XNA應該有使用SIMD指令做優化。
不論這個結果準確與否,在對比C++的結果似乎這個最簡單使用SWIG的Wrapper在速度上還存有改善空間。而C#比Lua快應該是基於它使用JIT,而Lua只是一個interpreter。但就這個例子而言,使用Script的速度能達到直接使用C++的二至三成,已經原全超出我的預期。
接下來我想試一試:
SWIG支持大部份C++的功能,甚至template也能支持。同時可以生成十數種腳本語言的wrapper,包括我正在考慮使用的Lua、Python和C#等。
跟據我的目標,先做一個3D Vector的測試。以下是一部份C++代碼 (Vector3.h):
#pragma once
#include
struct Vector3 {
Vector3();
Vector3(const Vector3& v);
Vector3(float x, float y, float z);
~Vector3();
Vector3 operator+(const Vector3& v) const;
Vector3& operator=(const Vector3& v);
Vector3& operator+=(const Vector3& v);
Vector3& operator-=(const Vector3& v);
Vector3& operator*=(float rhs);
Vector3& operator/=(float rhs);
bool operator==(const Vector3& v) const;
bool operator!=(const Vector3& v) const;
float GetSquaredLength() const;
float GetLength() const;
Vector3 GetNormalized() const;
void Normalize();
float x, y, z;
};
inline Vector3::Vector3(const Vector3& v) : x(v.x), y(v.y), z(v.z) {
}
inline Vector3& Vector3::operator+=(const Vector3& v) {
x += v.x;
y += v.y;
z += v.z;
return *this;
}
inline Vector3& Vector3::operator*=(float rhs) {
x *= rhs;
y *= rhs;
z *= rhs;
return *this;
}
// ...
之後寫一個SWIG的interface檔案 (Math.i):
%module Math
%{
#include "Vector3.h"
%}
%rename(Add) Vector3::operator+;
%rename(AddEqual) Vector3::operator+=;
%rename(MultiplyEqual) Vector3::operator*=;
// ...
%include "Vector3.h"
最後幾行是因為,以我所知,SWIG對C#並不支持operator overloading。SWIG本身是支持operator overloading的,或許要深入研究一下SWIG的Wrapper/Proxy生成過程可不可以加入這功能。
C#的測試
做C#的時候,用swig -c++ -csharp -o Math_wrap.cxx Math.i,就會新成三個檔案,分別為用來做C++ Wrapper的Math_wrap.cxx、用來做C# Proxy的MathPInvoke.cs和Vector3.cs。
接著就把Math_wrap.cxx編成DLL,而MathPInvoke.cs和Vector3.cs就可以放到C#的Project裡使用。
剛看到GDC的XNA演講提及.Net的速度,就直接用來比較一下。使用了SWIG生成的Proxy,使用Vector3類別和C#寫的沒有分別:
static void Main(string[] args)
{
Vector3 Position = new Vector3(0, 0, 0);
Vector3 Velocity = new Vector3(10000, 10000, 10000);
const float Friction = 0.9f;
const int count = 10000000;
System.DateTime start = System.DateTime.Now;
for (int i = 0; i < count; i++)
{
Position.AddEqual(Velocity);
Velocity.MultiplyEqual(Friction);
}
System.DateTime end = System.DateTime.Now;
System.Console.WriteLine((double)count / (end - start).TotalSeconds);
}
結果留到最後和Lua一起比較吧。
Lua的測試
之前的Math.i和其他檔案都不用改,直接執行swig -c++ -lua -o Math_wrap.cxx math.i,就會生成Math_wrap.cxx這個proxy。和C#不同,是不需要Lua的Proxy的。把這個連結成Math.dll後,在Lua只需一句Require("Math")就行了!這實在太簡單!(這麼簡單的功能是在Lua 5.1才有)
同樣的測試用Lua來寫:
require("Math")
local Position = Math.Vector3(0, 0, 0)
local Velocity = Math.Vector3(10000, 10000, 10000)
local Friction = 0.9
local count = 1000000
local start = os.clock()
for i = 1, count do
Position:AddEqual(Velocity)
Velocity:MultiplyEqual(Friction)
end
local finish = os.clock()
print(count / (finish - start))
以前看了Lua的書,但沒有寫過,發覺Lua寫起來也很簡單,而且SWIG做的Binding也不錯。最容易錯的地方就是把object:method(...)寫成object.method(...)。
結果和分析
結果是以每秒執行iteration次數計算,數字越大表示越好。
Case | iteration/sec | relative performance |
C++ | 1,544,163 | x |
C# | 519,372 | 0.336x |
Lua | 357,653 | 0.232x |
XNA on Xbox360 | 3,380,000 | 2.19x |
不論這個結果準確與否,在對比C++的結果似乎這個最簡單使用SWIG的Wrapper在速度上還存有改善空間。而C#比Lua快應該是基於它使用JIT,而Lua只是一個interpreter。但就這個例子而言,使用Script的速度能達到直接使用C++的二至三成,已經原全超出我的預期。
接下來我想試一試:
- 使用C++/CLI做Binding代替SWIG/PInvoke,看看速度的差異。如果能把Vector3當成value type,速度應該會快reference type許多,建立物件的overhead亦會減低 (不需要GC)。如果成功,可以把一部份常用類別用C++/CLI人手編寫,其他就用SWIG。
- 嘗試寫SIMD優化幾個operators,和XNA的結果比較。
- 測試SWIG對類別、smart pointer、STL等。
2008年2月20日星期三
目標
最近認為絕大部遊戲內容是應該用Script來寫的,而.Net來做工具似乎是個很好的選擇。我把最近想做到的都列出來吧。
短期目標:
短期目標:
- 學習Script在遊戲的應用。
- 選擇一個Script Language及Binding方法 (大概是Lua和Swig)。
- 先寫個3D Vector的Binding吧。
- 用Script做Unit Test。
- 做Native和Script的Performance Test。
- 再試做它的 .Net Binding (用Swig 的PInvoke或 C++/CLI)。
- 做個簡單的Graphics API Wrapper。最近剛做過某Console的簡單wrapper,感覺視野有闊了一點。始終底層都是那些硬件,Graphics API的差異變得較不重要。
- 做個簡單的GUI系統。不知做了多少次,十幾年前(?!)做的都沒有screenshots了,不然對比一下它們可能會很有趣。
- 用.Net做GUI Editor,Script做Event Handling。
- 開發簡單的Game Engine和工具,先針對遊戲內容的製作。
- 開發一個遊戲。不一定要完整的,但要有Core gameplay。
訂閱:
文章 (Atom)