翻譯:Kelvin Lo / 海龜
Resources 目錄
Resources 目錄是一個常見的專案問題來源,不正確的用法會導致專案大小膨脹,記憶體用量上升,也會明顯增加程式啟動的時間。
這些問題在官方的指南:Unity’s Guide to AssetBundles and Resources 有詳細的描述。特別是Resources 這一章值得一看。
未來也會翻譯成中文版,敬請期待。
一般最佳化
不同的效能問題需要不同的方法對症下藥,一般來說,強烈建議開發人員在開始 CPU 最佳化之前先仔細剖析專案的各項數值。但有幾個簡單的 CPU 最佳化是普遍適用的。用 ID 來定址
Unity 內部不會用字串來定址 Animator、Material 和 Shader 屬性。為了提升速度,所有的屬性名稱都經過雜湊化(Hashed)到屬性 ID,這些 ID 就是 Unity 用來定址這些屬性的方法。
因此,每當在 Animator、Material 或 Shader 上使用 Set 或 Get 時,請使用整數參數呼叫 API 而非字串參數呼叫。字串參數 API 底下不過是再跑一次雜湊然後把雜湊值餵給整數參數 API。
從字串雜湊化所創造的 ID 在同一次執行過程中是固定的,用它們最簡單的方法是幫每個名稱宣告一個靜態唯讀(readonly)的整數變數,然後把 ID 填入。這些都會在啟動時自動初始化不需要再寫程式。
對應的 API 用在 Animator 屬性名稱是 Animator.StringToHash,用在 Material 和 Shader 屬性名稱是Shader.PropertyToID。
使用不會配置記憶體的物理 API
Unity 5.3 之後的版本已導入了回傳時不會配置記憶體的物理查詢 API。用 RaycastNonAlloc 代替 RaycastAll,用 SphereCastNonAlloc 代替 SphereCastAll,依此類推,2D 物理也有對應的改正。
Transform 操作
每當 Transform 的座標或旋轉改變的時候,會觸發一個 OnTransformChanged 訊息送到該 GameObject 附帶的全部 Component 和所有子 GameObject 上。出於這個理由,最好的作法是在同一幀內設定移動和旋轉的次數儘可能的減少。把所有的改變整合後一次設定到 Transform 上。最小化 OnTransformChanged 在整個階層結構傳送的次數,這對於樹狀結構很大很深的物件(例如有骨架動畫的人物角色)尤其重要。
向量和四元數運算的順序
對於執行很多次的迴圈裡的向量和四元數計算,請記得整數(integer)運算比浮點數(floating-point)運算來的快,浮點數運算比向量和矩陣(matrix)或四元數來的快。
因此,當數學交換律(Commutative)或是結合律(Associative)允許的情況下請儘量簡化算式:
Vector3 x; int a, b; // Less efficient: results in two vector multiplications Vector3 slow = a * x * b; // More efficient: one integer mult, one vector mult Vector3 fast = a * b * x;
內建的顏色工具(ColorUtility)
先前當大家想要做 HTML 格式的色碼(#RRGGBBAA)跟 Unity 內建的 Color 或 Color32 結構轉換時很多人會用 Unify Community 維基上面的轉換腳本,這腳本又慢又占記憶體空間。
從 Unity 5 開始,裡面有一個內建的 ColorUtility API 可以有效的執行這些轉換,建議使用。
Find 和 FindObjectOfType
最好的方法是整個專案遊戲本體程式裡完全不要用 Object.Find 和 Object.FindObjectOfType。由於這些 API 會對 Unity 記憶體中所有的 GameObject 和元件進行迭代,因此隨著專案變大這些 API 效能就會越來越下降。
但有個例外是 Singleton 用來取得實體的屬性,通常有個全域 Singleton 管理物件會開放一個叫做 instance 或是之類的屬性,然後在屬性裡面可以呼叫 FindObjectOfType 來偵測 Singleton 的實體是否被建立過了。
class SomeSingleton { private SomeSingleton _instance; public SomeSingleton Instance { get { if(_instance == null) { _instance = FindObjectOfType<SomeSingleton>(); } if(_instnace == null) { _instance = CreateSomeSingleton(); } return _instance; } } }
雖然這種模式通常是可以接受的,但重要的是檢查程式並確保在 Singleton 物件不存在的場景呼叫存取器。如果 getter 沒有自動建立缺少的 Singleton 實體,那麼搜尋 Singleton 的程式會重複呼叫FindObjectOfType(一幀會很多次)並會導致效能不佳,這還蠻常見的。
尋找 Camera 的參考
在內部,Unity 的Camera.main 屬性會呼叫 Object.FindObjectWithTag,一個 Object.FindObject 的特殊變體。存取這個屬性並沒有比 Object.FindObjectOfType 來的好。如果要用程式找尋主鏡頭,強烈建議用下列兩個方法:
在 Start 或 OnEnable 回呼裡存取 Camera.main 並暫存其引用結果。
構造一個“Camera Manager”類別,提供別的元件 Camera 的參考或是用在依賴注入。
除錯碼和 [Conditional] 屬性
UnityEngine.Debug 紀錄 API 並不會因為切換到非開發模式就被移除,如果有呼叫也還會繼續紀錄 Log。大部分的開發者並不會在正式發佈的版本記錄 Log,因此建議自訂一個紀錄方法然後設定成只有在開發中會呼叫,像這樣:
public static class Logger { [Conditional("ENABLE_LOGS")] public void Debug(string logMsg) { Debug.Log(logMsg); } }
透過使用 [Conditional] 屬性來修飾這些方法,加入一個以上的參數來控制編譯時是否編譯這個方法。
如果 Conditional 屬性的所有參數都沒有在專案設定裡被定義,這個方法還有所有呼叫這個方法的程式碼在編譯時都會被剔除。它的效果和把方法放在 #if……#endif 很類似。
更多關於 Conditional 屬性的資料可以參閱 MSDN。
Unity最佳化 - 目錄
沒有留言:
張貼留言