2016年10月19日 星期三

ReCore開發系列:Mecanim動作系統

作者:William Armstrong 原文
潤稿:Kelvin Lo


ReCore是一款支援Xbox One及Windows平台的遊戲,Unity有幸參與和Armature和Microsoft Game Studios一起合作開發。本文為大家分享如何在ReCore中重複利用Mecanim的技巧。

一條狗、一隻猿猴、一隻蜘蛛和一個人一起走進了遊戲場景......然後我們第一個問題就來了,如何讓他們都能走?在ReCore專案中我們遇到了許多有趣的動作製作挑戰,而這些挑戰也間接推動Unity Mecanim系統的狀態機(State machine)更上一層樓
。我們與Armature和Microsoft Game Studios團隊緊密合作,盡可能地利用內建工具來解決這個領域的問題。 

Joule – Layered for State
Joule - 狀態層級化

Joule是一個具有豐富動作的角色。她能跑、跳躍及用火箭靴進行衝刺,除此之外她還能夠瞄準、背後射擊或拿著槍做上述所有事情。所以她會需要一個非常複雜的狀態圖
火箭靴超好玩!

對於如此多樣的動作組合而言,如果要想將這麼多的狀態和轉換,複製並應用到每一個Joule能使用的武器上是不現實的。還好Mecanim系統的同步層(Sync'd Layers)提供了很好的解決方案。一旦勾選同步功能Mecanim就會複製層中的所有狀態機分佈到一個新的層,你就可以在
不用維護多個狀態下,根據需要更換動畫片段。當然,新層裡面無用的狀態和轉換會產生一點額外的消耗。但這樣做帶來的好處還是很高的,複製發生bug機率更低,迭代速度更快,整體來說這項功能物超所值。

在同步層的待機動作 - 底層, 掏槍動作層和瞄準動作層


Corebots -動物的動作 



Joule與她一些被稱為Corebot的朋友結伴開始冒險。它們會跟著她走過這片土地並參與戰鬥並有許多特殊攻擊和環境互動能力。它們用一套複雜的AI狀態機和相應的Mecanim狀態機控制著同步。理想狀態下,這種複雜的結構只需要建立一次,然後作為基礎給所有其他的AI使用。 

誇張的Corebot狀態機


遊戲中看到的三個Corebot看起來像人卻又不是人類。這時我們可能會想到用Mecanim Humanoid Rig中內建的重定向技術(Retargeting)來實現動畫,但這並不是一個好選擇。儘管Humanoid rig非常強大,但它有著一些很苛刻的設定條件。狗、猿猴、蜘蛛恰恰無法滿足這些條件。即便是Joule也沒使用Humanoid rig,因為她在脊椎上額外的骨骼造成她並不適用這套系統。

還好的是,這些AI變數全都有類似特徵,它們都要移動、戰鬥、跟隨,然後進入待機狀態。儘管這些動畫看起來截然不同,不過它們基礎上建立的邏輯是一樣的。這樣一來AI就非常適合使用執行覆蓋控制器(Runtime Override Controller)。這個控制器允許你從一個動作片段轉道到另一個特定的Animator進行重新定向。在ReCore裡,開發團隊設定好了狗的動畫控制器,然後讓猿猴和蜘蛛在運作時覆蓋控制行為就能達成目的。

相同的Animator Controller,同時的待機狀態。

透過這一個解決方案Armature得以發表他們的遊戲,也讓他們遇到了一些問題。如果在三個Corebot類型中僅有一個類型需要某個狀態,因為只有一個控制器,所以這個狀態必須放在共用的Animator Controller中。這就會導致一些額外的效能消耗,因此這種技術最好在擁有大量共用狀態和轉換時使用。不過對於Corebot來說,這點開銷是可以接受的。

Armature意識到他們必須謹慎處理動畫的轉換。因為當在Animator中覆蓋動作時無法更改轉換(Transition)的設定。ReCore中絕大多數轉換都是固定的時間,好控制讓Animator能有比較好的結果。也就是說對覆蓋動作的時間進行大量改變會導致轉換發生在錯誤的時間上。因此當使用覆蓋控制器時,儘量不要或少量修改多個動作之間的時間間隔。

當狀態機中開始涉及到大數量的動作時,覆蓋控制器很快會變得臃腫繁複,甚至變得很難去修改。ReCore專案將程式碼中的即時覆蓋狀態器,按字母進行排序處理才得以解決這個問題。隨著動作數量增長,最終還寫了一套工具來根據玩家設定的模式來對動作進行重映射(Remapping)。

效能


在Armature和Microsoft的團隊不眠不休努力下,我們抓到了幾個嚴重的效能問題。如果臨時記憶體池已滿,Unity就會退而求其次用預設的記憶體分配器造成速度會慢一些。目前我們正在尋找改善動作記憶體分配的辦法。這種情況通常發生在多個Animator同時運作時,可以在Profiler中找到“TempAlloc.Overflow”來查看消耗。我們還在尋找如何解決讓Mecanim擁有處理很多不同的低混合權重(low blend weights)動作的能力。目前情況下,所有的動作將會被完全執行,即便這些動作最終可能混合結果沒動作。因此要注意當建立Mecanim層或Blend Tree的時候請避免使用太多最後效果很小的動作。

即使有了所有的引擎核心提升支援,Armature的資料仍然需要盡可能的優化。為了避免在主執行緒UpdateTransform塊中產生上百個骨骼Transform更新,Armature還需要確保所有的模型都導入時要勾選Optimize Game Objects選項。它會合併SkinnedMesh的骨骼,也允許Mecanim直接向圖形記憶體中寫入資料,在更新動畫時就不需要再每幀都更新所有骨骼的GameObject了。Armature同樣還需要避免使用OnStateMachineEnter和OnStateMachineExit返回呼叫。Mecanim能夠檢測到這些呼叫(Callback)是否存在,如果發現的話會阻止狀態機執行緒進行存取,因為這個操作對執行緒是不安全的。儘管這操作會佔用總動畫系統開銷的5%,但這是在每幀中間一點點的佔用,對於大型遊戲而言將會是大問題。

為了加快載入速度,Armature做了一個AI池放這些物件,而不是即時實例化(Instance)物件,然後根據需要開啟或禁用物件。但這種作法會導致Animator元件重建所有內部的狀態。這種做法的目的是保持禁用Animator不要佔用記憶體,並在禁用時乾淨的重置Animator的狀態。可以透過停用Animator Controller而不是停用整個物件來避免這種現象。Armature已經不能接受任何額外記憶體佔用或處理重置Animator狀態的程式碼了。

在啟動期間,太多的時間花在分配每個狀態使用的狀態機實例化上。這個記憶體消耗對於任何狀態機行為是必要的,因為它可以有自己的變數,每個Animator都需要一份新的副本,而不僅僅是每個AnimatorController。為此我們將[SharedBetweenAnimators]屬性加到不需要自己狀態的行為上,這樣可以節省大量的開銷。這屬性會通知Mecanim系統不需要對這部分實例狀態進行追蹤,它只要幫相關類別建立一個靜態實例就好了。


結論


我們與Armature一同研究如何讓Joule和她的Corebot夥伴奔跑、戰鬥和跳躍非常有意義,這是一個很好的Mecanim案例。透過與這樣一個雄心勃勃的團隊合作,我們借機提升引擎的品質,同時Armature也能得到他們想要的效果。想了解更多Armature與我們合作開發的ReCore專案,未來我們還會發佈更多的內容。

沒有留言:

張貼留言

著作人