Resources 文件夾和一般優(yōu)化
Resources?文件夾是 Unity 項目中許多常見問題的來源。Resources?文件夾的使用不當會使項目構建出現膨脹,導致內存消耗過高,并顯著增加應用程序啟動時間。
有多少原因導致性能問題,就有多少種不同的方式來優(yōu)化代碼。通常,強烈建議開發(fā)者在嘗試應用 CPU 優(yōu)化之前對其應用程序進行性能分析。不過,還是存在幾種普遍適用的簡易 CPU 優(yōu)化方式。
一般優(yōu)化
按 ID 尋址屬性
Unity 不使用字符串名稱對 Animator、Material 和 Shader 屬性進行內部尋址。為了加快速度,所有屬性名稱都經過哈希處理為屬性 ID,實際上正是這些 ID 用于尋址屬性。
因此,每當在 Animator、Material 或 Shader 上使用?Set?或?Get?方法時,請使用整數值方法而非字符串值方法。字符串方法只執(zhí)行字符串哈希處理,然后將經過哈希處理的 ID 轉發(fā)給整數值方法。
從字符串哈希創(chuàng)建的屬性 ID 在單次運行過程中是不變的。它們最簡單的用法是為每個屬性名稱聲明一個靜態(tài)只讀整數變量,然后使用整數變量代替字符串。啟動期間將自動進行初始化,無需其他初始化代碼。
Animator.StringToHash?是用于 Animator 屬性名稱的對應 API,Shader.PropertyToID?是用于 Material 和 Shader 屬性名稱的對應 API。
使用非分配物理 API
在 Unity 5.3 及更高版本中,引入了所有物理查詢 API 的非分配版本。將?RaycastAll?調用替換為?RaycastNonAlloc,將?SphereCastAll?調用替換為?SphereCastNonAlloc,以此類推。對于 2D 應用程序,也存在所有 Physics2D 查詢 API 的非分配版本。
與 UnityEngine.Object 子類進行 Null 比較
Mono 和 IL2CPP 運行時以特定方式處理從?UnityEngine.Object?派生的類的實例。在實例上調用方法實際上是調用引擎代碼,此過程必須執(zhí)行查找和驗證以便將腳本引用轉換為對原生代碼的引用。將此類型變量與 Null 進行比較的成本雖然低,但遠高于與純 C# 類進行比較的成本。因此,請避免在緊湊循環(huán)中或每幀運行的代碼中進行此類 Null 比較。
矢量和四元數數學以及運算順序
對于位于緊湊循環(huán)中的矢量和四元數運算,請記住整數數學比浮點數學更快,而浮點數學比矢量、矩陣或四元數運算更快。
因此,每當交換或關聯算術允許時,請嘗試最小化單個數學運算的成本:
Vector3 x;?
int a, b;?
// 效率較低:產生兩次矢量乘法?
Vector3 slow = a * x * b;?
// 效率較高:一次整數乘法、一次矢量乘法
Vector3 fast = a * b * x;
內置 ColorUtility
對于必須在 HTML 格式的顏色字符串 (#RRGGBBAA
) 與 Unity 的原生?Color
?及?Color32
?格式之間進行轉換的應用程序來說,使用來自 Unify Community 的腳本是很常見的做法。由于需要進行字符串操作,此腳本不但速度很慢,而且會導致大量內存分配。
從 Unity 5 開始,有一個內置?ColorUtility?API 可以有效執(zhí)行此類轉換。應優(yōu)先使用內置 API。
Find 和 FindObjectOfType
一般來說,最好完全避免在生產代碼中使用?Object.Find
?和?Object.FindObjectOfType
。由于此類 API 要求 Unity 遍歷內存中的所有游戲對象和組件,因此它們會隨著項目規(guī)模的擴大而產生性能問題。
在單例對象的訪問器對上述規(guī)則來說是個例外。全局管理器對象往往會暴露“instance”屬性,并且通常在 getter 中存在?FindObjectOfType
?調用以便檢測單例預先存在的實例:
class SomeSingleton {
private SomeSingleton _instance;
? ?
public SomeSingleton Instance {
? ? ? ?
get {?
if(_instance == null) {
_instance =
FindObjectOfType<SomeSingleton>();?
}
if(_instnace == null) {
_instance = CreateSomeSingleton();
}
return _instance;?
} } }
雖然這種模式通常是可以接受的,但必須注意檢查代碼并確保調用訪問器時場景中不存在單例對象。如果 getter 沒有自動創(chuàng)建缺失單例的實例,那么尋找單例的代碼經常會重復調用?FindObjectOfType
(通常每幀多次發(fā)生)并且會對性能產生不良影響。
攝像機定位器
在內部,Unity 的?Camera.main
?屬性會調用?Object.FindObjectWithTag
(這是?Object.FindObject
?的一個專用變體)。訪問此屬性并不比調用?Object.FindObjectOfType
?更高效。如果代碼必須尋址主攝像機,強烈建議您執(zhí)行以下兩項操作之一:
訪問?
Start
?或?OnEnable
?回調中的?Camera.main
,并緩存所產生的引用。構造一個可提供或添加活動攝像機引用的?
Camera Manager
?類。
調試代碼和 [conditional] 屬性
UnityEngine.Debug
?日志記錄 API 并未從非開發(fā)版中剝離出去,如果被調用,則會寫入日志。由于大多數開發(fā)者不打算在非開發(fā)版中寫入調試信息,因此建議在自定義方法中打包僅用于開發(fā)用途的日志記錄調用,如下所示:
public static class Logger {
[Conditional("ENABLE_LOGS")]
public static void Debug(string logMsg) {
? ? ? ? ? ? ? UnityEngine.Debug.Log(logMsg);
}
}
通過使用 [Conditional] 屬性來修飾這些方法,Conditional 屬性所使用的一個或多個定義將決定被修飾的方法是否包含在已編譯的源代碼中。
如果傳遞給 Conditional 屬性的任何定義均未被定義,則會被修飾的方法以及對被修飾方法的所有調用都會在編譯中剔除。實際效果與包裹在?#if … #endif
?預處理器代碼塊中的方法以及對該方法的所有調用的處理情況相同。
有關?Conditional
?屬性的更多信息,請參閱 MSDN 網站:msdn.microsoft.com。