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