優(yōu)化UI有時候并沒有什么很簡潔的方式。本文介紹了一些可能對UI性能提升有幫助的建議,有些建議是針對結構上“不清晰”,或難于維護,或者效果很差。另一些則可能對開發(fā)初期的UI用戶界面簡化有所幫助,但也相對更容易產(chǎn)生一些性能問題。
基于RectTransform的布局
Layout組件的性能開銷相當大,因為每次當它們被標記為Dirty時,都必須重新計算所有子節(jié)點的坐標和尺寸。如果在給定的Layout內有一些相對較小的固定數(shù)量的元素,并且布局的結構也相對簡單,那么就有可能將Layout替換為基于矩形變換的布局(RectTransform-based layout)。

通過設置RectTransform的錨點(Anchors),RectTransform的坐標和大小會根據(jù)父節(jié)點進行縮放。例如,一個簡單的兩列布局可以用兩個RectTransform實現(xiàn):
左列的錨點應該是X: (0, 0.5) 以及 Y: (0, 1)
右列的錨點應該是X: (0.5, 1) 以及 Y: (0, 1)
對于RectTransform坐標和大小的計算會由Transform系統(tǒng)自身的源代碼進行驅動。通常情況下這比Luyout系統(tǒng)更高效。也可以通過MonoBehaviours來實現(xiàn)基于RectTransform的Layout。然而,這是一個相對復雜的任務,不在本文中描述。
禁用Canvas渲染器
當顯示或者隱藏UI的某個部分時,通常是激活(Enable)或者禁用(Disable)UI根節(jié)點的GameObject。這會導致被禁用UI下的所有組件都將不再接收輸入或者Unity回調。
然而,這也會導致Canvas丟棄它的VBO(Vertex Buffer Objects,頂點緩存對象)數(shù)據(jù)。重新激活Canvas需要Canvas(以及它的子Canvas)執(zhí)行重新構建(Rebuild) 以及重新批處理(Rebatch)操作。如果這種情況非常頻繁,那么CPU使用率的增加就會導致應用程序幀率的卡頓。
一個可行但有風險的解決方案是讓將那些需要切換顯示或隱藏的UI放在單獨的Canvas或子Canvas中,然后僅僅激活/禁用附加在Canvas上的Canvas渲染組件(Canvas Renderer)。
這會導致UI的網(wǎng)格不被繪制,但它們會一直存在于內存中,并且原始的批處理信息(Batching)也會被保留。此外,UI層級結構(Hierarchy)下的OnEnable 或者 OnDisable回調將不會執(zhí)行。
注意,這并不會將UI圖形從圖形記錄(GraphicRegistry)中消除,所以它們依然會出現(xiàn)在組件列表中,可以被光線投射(Raycast)檢測到。隱藏UI也不會禁用任何的MonoBehaviour,所以那些MonoBehaviour依然會接受Unity生命周期相關的回調,比如Update函數(shù)。
隱藏UI的MonoBehaviour腳本不直接實現(xiàn)那些Unity生命周期相關的回調函數(shù),而是從UI根節(jié)點上的“回調管理器”MonoBehaviour中接收回調,可以避免出現(xiàn)這樣的問題。這個“回調管理器”無論UI是否顯示都可以訪問,并且保證了生命周期事件按需發(fā)送。
分配事件相機
如果使用了Unity內置的輸入管理器,并將Canvas的渲染模式設為世界空間(World Space)或者屏幕空間相機(Screen Space – Camera)渲染,有一點很重要,就是分別設置Event Camera和Render Camera的屬性。這可以在腳本中訪問Canvas的worldCamera屬性進行設置。
如果沒有設置worldCamera屬性,那么Unity UI會查找標簽為Main Camera的GameObject上附加的Camera腳本來搜索主相機。這個查詢會在世界空間(World Space)和相機空間(Camera Space)的Canvas中都至少分別執(zhí)行一次。由于GameObject.FindWithTag非常緩慢,Unity強烈建議大家在設計或初始化所有的世界空間(World Space)和相機空間(Camera Space)的Canvas時,就分配好各自的相機屬性。
這個問題不會在渲染模式為Overlay的Canvas中出現(xiàn)。