Unity優化技巧(中)
下面介紹一下具體的優化方式,按照硬體劃分分為CPU,GPU,內存&硬碟。
CPU
- Top10
使用Profile找到CPU佔用最靠前的函數,從最高的開始依次分析優化。定位的方法有很多,Unity的Profile,UWA的性能測試工具,比較推薦的是使用XCode,可以抓取一段時間內函數的開銷。更品均準確也可以看到更底層。
如過函數比較複雜可以使用BeginSample/EndSample拆分,我在項目里通過添加條件編譯進行了封裝類似:MDebug.BeginSample("Character.TakeDamage");
這樣可以跟隨意和高效的定位到關心的地方。下面列舉部分比較通用的優化方案。
- LOD,代碼只在必要時才會運行
最常用的優化方式。例如屏幕外的角色不計算動畫更新,不計算技能效果冒字血條等,屏幕外的角色休眠,只有主角才會冒字等等。還可以做一些LOD,例如AI可以做行為樹lod,動畫LOD,粒子特效LOD,更新頻率LOD(更新頻率隨著遊戲幀率,離主角的距離,重要程度,視錐,類似CSM的提醒分段,以及周圍的角色個數動態調整。例如軒轅城擂台,同屏高配200人,未優化在68幀左右優化的後到94幀左右)
- 限幀,負載均衡
為了降低耗電發熱量我們會根據玩家機型進行限幀。另一方面我們將邏輯幀和渲染幀分開執行,邏輯代碼以更低的幀率執行,部分邏輯也可以使用線程,負載均衡分段計算等方法提升性能。
- 演算法
一些代碼本身的運算。例如優化物理運算,空間換時間使用查表預計算等方式對計算提速,減少頻繁索引FindGetComponentd以及各種運算,優化遍歷利用稀疏矩陣九宮四叉樹等等。
- 伺服器計算或者客戶端計算
根據不同類型的遊戲也會調整一些運算是在伺服器還是客戶端,如果伺服器性能強大可以讓伺服器計算物理,尋路,AI,戰鬥邏輯等複雜運算,客戶端只要變現即可,特別是MMO的項目。而如果希望伺服器開發較少提升開發效率和降低伺服器性能要求,也可以將絕大部分運算放在客戶端,例如幀同步的遊戲我們就採取的這種方式。當然有時候也是兩者相結合併沒有絕對標準。
- Unity介面
- OnGUI,FixedUpdate,Update等空函數也會有gc開銷,因為會產生從C++到C#層調用的開銷。
- MainCamera是一個遍歷操作Camera比較多的時候不要頻繁調用。
- 盡量少使用GetComponent,AddComponent(還會產生GC),Find等操作。
- 使用Unity5.6有個新函數SetPositionAndRotation。因為Transform的Position每次髒了會有一次消息,Rotation也會有,並且會開一個線程來做這個操作極大提升性能。所以最好每幀只設置一次,並且使用SetPositionAndRotation一次性設置可以提升一倍的性能開銷。
- 其他。
- 物理
Unity使用PhysX作為物理引擎,本身優化還是很好的,會做空間劃分。Unity官方建議碰撞對小於100,其實這個標準非常嚴苛,我們測試在300左右物理的開銷還是蠻少。優化方面可以通過分層減少碰撞對,盡量使用BoxCollider而不是MeshCollider,UI界面不需要點擊的控制項不要打開Raycaster。因為我們只使用了最基本的射線檢測,其他物理是自己實現的,主要的優化還是在物理演算法上。如過在Profile中發現Physucs.Simulate開銷比較大就是物理需要優化了。
- IL2CPP & C++
把Unity編譯設置成IL2CPP,編譯成C++版運行效率會有較大提升。還可以把一些運算邏輯放到C++的庫里,這樣可以優化更極致減少gc。
- 動畫
如過Prifile中發現Animator.Update或者MeshSkinning.Udpate開銷比較大就說明動作可能需要優化。
- 優化:打開Optimize GameObject,可以把一些無效的節點骨骼去掉,注意如果有自定義的節點需要拖到不被優化列表裡。
- 壓縮:打開Keyframe
Reduction,可以壓縮很多不必要的關鍵幀,這個值越大壓縮比率越高失真越嚴重。
- BoneWeights:頂點受骨骼影響,對要求不高的環境可能一個骨骼就夠了。可以每個模型設置,也可以實時全局改變。
- BakeMesh:對於同屏需要顯示大量模型可以使用SkinnedMeshRenderer.BakeMesh,把動畫烘成模型,這樣在渲染的時候可以合併(帶動畫不能合併)。可以大幅減少DC,省去蒙皮計算,不過缺點是內存增大,增加DynamicBatching的CPU開銷,表現會差一些。
- 不使用Animator:Animator的開銷比Animaton比一個量級。
- 不可見不更新設置CullCompletely,但是需要注意一些消息也會停掉如果對動畫有依賴會出問題。
- 其他:骨骼LOD,GPU Skinning(有些設備和情況會更慢),使用Bone代替CS等等。
- UI
UI也是個開銷大頭,一般會佔到30%-50%。UGUI對應Profile中Canvas.BuildBatch &
Canvas.SendWillRenderCanvases開銷,類似NGUI的LastUpdate,UI的優化又很多文章這裡也簡單列舉一下。
- 動態靜態分離:因為UI會合併。NGUI是按Panel進行重建的、UGUI是按Canvas進行重建的,防止動態UI觸發合併導致靜態UI也一起合併。
- 預載入,常駐,即時釋放:UI按類型劃分,比較大的常用的UI在創建的時候會卡頓,可以進行預載入。主城到戰鬥場景,在保證峰值內存的情況下,將英雄界面工會界面等常駐內存,可加快Loading速度,實測優化後提升一倍以上loading速度。其他不常用界面拆分成小界面,使用即時載入,關閉時卸載節省內存。需要注意的是,UI節點過多也會導致載入緩慢,我們曾經Loading要10秒,其中序列化UI佔了一半左右的時間(貼圖預先載入測試),減少UI節點數,太大了拆開。
- 圖集:合理拆分UI圖集,區分公共圖集(常駐)和非公共圖集。太大容易造成冗餘載入,容易導致內存佔用過大,導致內存顯存交換開銷。太小有容易導致顯存碎片影響效率。規則很複雜。
- 內存池:UI冒字等頻繁創建的UI使用內存池減少創建的時間和內存碎片。
- Active/Deactive:不推薦通過Active/Deactive來頻繁切換UI界面,因為會觸發UI合併操作,可以通過移到屏幕外的做法或者設置Layer。但需要注意移到屏幕外還是會被合併渲染,如果是長時間不顯示的還是Deactive比較好需要視情況而定。
- UISprite來代替UITexture:Texture不會合併。
- 不移動不可見的UI不更新:例如血條名字等。
- layout group, canvas group組件,任何子節點變了父節點都會用getcompent找到laygroup。這是Unity的UGUI的兩大坑。
- 檢查不需要拾取的Raycast target是否關了。
- 資源預載入:例如前面介紹的UI預載入,內存允許的情況下所有資源都應該預載入,結合內存池。我們遊戲中所有變現邏輯,角色,怪物,道具,UI都會做預載入,並且有一套池膨脹和回收的策略。
- Shader預載入。
- 等等。
- GC
GC是一個非常高開銷的系統調用也,是大部分卡頓的主要原因,不能完全控制。因此我們要盡量減少代碼堆內存分配過量防止頻繁觸發GC,同時也可以在Loading或者對性能不敏感的時候主動GC。
- 升級Unity:Unity5.6修改了粒子系統的源碼減少了lamda表達式的gc。
- 減少一些字元串拼接,使用StringBuilder代替string減少GC開銷,不要使用富文本改變Text組件的顏色直接通過修改Text組件顏色來改。
- 內存池:前面說過GameObject的內存池,另外還有類對象的內存池。所有頻繁反覆創建刪除的都應該使用。兩個用途,減少載入創建釋放的時間,減少內存碎片降低GC的頻率。
- Unity介面:AddComponent,OnGUI,UI合併頻率,delegate,等(一些Foreach,協程等Unity已經優化)。
- 邏輯優化:避免頻繁創建開闢空間。
- 插件的GC優化:對行為樹,FMODStudio等一些插件的源碼進行了修改減少GC。
- 等等。
推薦閱讀:
※關於演算法競賽中快速乘的一些優化
※看完性能簡報,想不優化好都難!
※請問將方陣做特徵值分解,再去掉對角陣中的較小特徵值,這種操作叫什麼?
※如何根據網站日誌進行分析並做出優化改進?
※索引的索引:如何不系統地了解運籌學