[譯]實現快速迭代的引擎設計 - Capcom RE Engine的架構與實現
原文(日文):ラピッドイテレーションを実現するゲームエンジンの設計
CEDEC2016上的一個Session。基本上是根據PPT的翻譯(可能成為筆記更恰當一點),夾雜了一些現場聽來的信息。PPT里有很多優點舉例基本沒什么營養就省略了。沒正經的翻譯過文章,有錯誤歡迎指正。主要是來抱囧聚大腿的。
以下正文:
引擎簡介
- RE Engine是Capcom內部開發的次世代游戲引擎
- 支持PS4,XboxOne,PC(Steam/UWP)等平台
- 應用於開發中的項目生化危機7
- 引擎特征:高性能,高開發效率 <- 本文主要內容
游戲開發循環
- 編程 → Build → 確認 → ...
- DCC工具編輯 → 資源轉換/導出 → 確認 → ...
- 游戲啟動 → 測試/試玩 → 參數調整 → ...
- QA → Bug修正 → 打包 → ...
傳統開發流程中,上面各個步驟不是費時間就是需要大量手工操作,效率相對低下。
快速開發的實現
→ 最終節省下來的時間用來提升游戲質量
- 減少等待時間(主要是Build以及資源轉換等)
- 減少每回試錯的時間
- 提高在運行環境(特別是主機上)中運行的頻率
- 降低Bug率
RE Engine界面(譯注:基本與UE4或Unity相近,支持所見即所得的編輯方式)
一些細節
- 生化7目前大約有20萬行C#
- Editor里可以直接運行(整體更像UE4一點),支持所有資源的重新載入
- Scene是樹狀結構,有一個Master Scene,里面有Chapter1Scene等,導出Master Scene就等於是打包游戲。隨着游戲流程Load/Unload Scene
- 演講的 石田 智史 就是設計了MT Framework的人
Scene的結構示例
----------------------------------------------------------------------------------------------------------
引擎架構
(譯注:演講包括了新舊引擎的對比,但舊引擎的參考價值不大,故基本省略)
- 工具架構(Editor)
- 工具和Runtime用TCP/IP通信同步
- Runtime就算崩潰了Editor也能繼續運行
- Runtime包含了各個平台的實現(如PS4的Runtime)
- 工具部分用WPF/C#開發,運行於Windows平台
- 問題點
- Runtime使用C++,而工具使用C#開發(包括游戲邏輯)
- 通信同步導致大量操作需要異步的實現,增加了復雜度
- 通信受限於傳輸速度
- 解決方案
- 統一通信協議:二進制通信,用C#的定義生成C++代碼,Query/Response形式
- 遠程對象系統:跨語言,跨進程的標識對象
- RuntimeType,RuntimeObject跟C#自己類似,成員貌似只是key-value對
- 通過ObjectID來同步(ID的正負來標識Editor/Runtime)
- 以手動同步為主,也可以注冊進Observer來監視更新,對性能有影響
- Gizmo之類的輔助對象也采用類似的方法實現
- 優點
- Runtime可以自由切換
- 可以對應一些只能在實際環境確認的功能(VR,手機等)
- Runtime經常會崩潰ww
- 工具和Runtime用TCP/IP通信同步
- 資源架構
- File-Based(舊)→ Asset-Based(新):文件更容易產生冗余,並且不利於Reload
- 所有資源支持異步更新,用Cache加速資源轉換,根據依賴關系優化打包速度
- 問題點
- Scene加載的時候需要預載入所有資源
- 引擎需要把握資源的載入順序
- 需要實現異步載入的功能
- 解決方案
- 靜態資源依賴關系生成
- 導入Asset的概念:在文件里追加了Metadata
- 工具啟動的時候載入所有的Asset,並生成依賴關系
- 禁止代碼控制的資源載入(譯注:是不是完全不支持動態載入沒有細說)
- 導入Asset的概念:在文件里追加了Metadata
- 嚴格的資源管理(譯注:聽起來自由度相當低,但畢竟是內部引擎估計可以根據實際需求調整而不用Hack)
- 禁止同步載入(容易導致Spike),並且異步載入有利於整體吞吐量
- 強制實現Reload
- 從游戲代碼不能直接訪問資源
- 靜態資源依賴關系生成
資源依賴關系示例
- 腳本架構
- 舊引擎開發游戲即使用上分布式Build多的時候也要15分鍾,並且容易崩潰,內存破壞
- 新引擎開發游戲時,游戲的邏輯部分完全使用C#,Build最多10秒,內存管理更好
- 問題點
- C#比C++更慢(Marshal,異常)(譯注:猜測Marshal應該主要是與SDK的交互或者與第三方庫的交互)
- GC導致的長時間停止
- 主機平台兼容性差(JIT禁用等)
- 解決方案
- 自制C#虛擬機(REVM)
- 以AOT為前提,不支持JIT
- 提高C++親和性,降低Marshal開銷
- 重寫C#對象布局 ↓ ,包了一層C++對象在里面,Marshal的時候可以直接傳指針。RC為引用計數。
- C# → C++:利用Clang解析C++代碼,生成供C#訪問的接口,最終和直接調用C++函數開銷相同
- C++ → C#:利用template,最終和函數指針開銷相同(譯注:黑魔法沒仔細看。。。)
- 重寫C#對象布局 ↓ ,包了一層C++對象在里面,Marshal的時候可以直接傳指針。RC為引用計數。
- 更輕量的標准庫(從Core FX搬)
- 編譯
- 開發時:MicroCode(IL不適合解釋執行,自制MicroCode) → 解釋執行
- Release時:C++(il2cpp)→ 編譯(Marshal部分inline展開)
- 編譯時間:分散編譯15分鍾左右(跟舊引擎相近)
- 自制C#虛擬機(REVM)
C#的編譯流程(Release時生成C++代碼)
C#與C++的運行速度比較(有了GC當后盾對象生成快了很多)
開發時與實際運行時的效率比較(譯注:可以看出來開發時候也不是特別慘)
- 自制實時垃圾回收(譯注:有些概念不太懂就沒詳細解釋了)
- 現有的GC不適合游戲
- 分代GC:Major GC導致的長時間停止
- 並行GC:GC執行中的速度低下,需要足夠的空余內存
- 自動引用計數方式:循環引用的泄露,對引用計數操作的高Overhead
- 適合游戲的實時GC(FrameGC)
- 限定於游戲應用
- 主循環的同期點
- C#作為腳本的前提
- 特征
- 預測,可控的停止時間(GC中)
- 即時釋放性(譯注:用完的內存盡早釋放)
- 可以更有效的利用所有內存
- 在多核環境下發揮性能
- 與C++的親和性
- GC算法(譯注:原ppt內有簡單的流程動畫說明可供參考)
- LocalObject:TLS,存在LocalTable里(當然也是TLS),C#生成的對象都作為LocalObject
- GlobalObject:所有線程都能訪問,C++生成的對象都作為GlobalObject
- LocalObject → GlobalObject(譯注:有點類似C#的Box只不過這里不是stack和heap的區別而是可訪問線程和所屬Table的區別)
- LocalFrame GC:C#的函數調用都結束的時候執行,釋放所有LocalObject(譯注:跟函數調用完了退棧差不多)
- Local GC:LocalTable滿了的時候執行,根據引用計數釋放LocalObject(實際很少發生)
- GlobalObject 釋放
- 由各個線程增減引用計數,為0了就釋放
- 當沒有所有線程里都沒有C#的棧(C#的調用都結束)的時候,執行Global GC(釋放所有被有被用到的)
- 循環引用由Incremental Cycle GC釋放
- (譯注:基本上就是掃一遍找出循環部分)
- 每幀Check循環引用的輔助表CycleRoot,按需GC
- FrameGC的Overhead
- Write Barrier:線程內的直接Check引用計數,跨線程的利用InterlockedCAS
- LocalObject→GlobalObject:類似↑
FrameGC的一幀的Profile結果(譯注:整數估計是Profile的Sample數)
----------------------------------------------------------------------------------------------------------
總結
- C#就是好!
- 生產效率大幅提升
- 杜絕應用層(游戲邏輯)產生的崩潰情況
- 執行效率沒有問題!
- REVM比寫的挫的C++更快
- FrameGC改變了GC不適合游戲的常識
----------------------------------------------------------------------------------------------------------
現場Q&A
- C#版本? 6.0 但是因為造了GC所以不算完全支持。
- 支持yield嗎?應該不支持(?),C#的部分不支持跨幀的處理。
- 開發人數?開始2人 x 3個月,后來加了5,6人又做了幾個月。(譯注:聽起來有點快的可怕,不過去年CEDEC的時候就有相關消息了所以實際應該更長一點)。感謝評論里@大薩比補充,現場有提到了開發速度快是因為RE引擎基於panta rhei開發。
- 序列化用了什么?第三方庫?沒有,因為涉及到ID之類的問題所以自己寫了。
- 轉成C++代碼的時候出現問題了怎么辦?那是bug...初期會有,現在已經基本沒有了。順便轉出來的C++代碼很長,VisualStudio會打不開...
- 游戲部分完全是C#嗎?完全是。
- 如果開發游戲的那邊提需求呢?因為是內部引擎所以可以商量,但不會讓他們直接改。
- 場景中的頂點數之類的統計數據可以取得嗎?Asset的meta信息里有,可以做。但是美術基本不關心這個....
- WPF的框架?Livet。
- 中間件是集成到引擎里?是的。
- 對中間件有什么要求?省內存,跑的快(笑)