1. 程式人生 > >Unity UI效能優化技巧

Unity UI效能優化技巧

問題:UI Canvas上有一個或多個元素變化時,會汙染整個畫布。

畫布(Canvas)是Unity UI的基本元件。它會生成網格來呈現放置在畫布上的UI元素,當UI元素變化時,它會重新生成網格並向GPU發起繪圖呼叫,從而顯示出UI。

生成這些網格會消耗大量效能,需要將UI元素收集到批處理中,從而儘可能減少繪圖呼叫。因為批處理的生成過程效能消耗較大,通常只在必要時候才重新生成。問題在於,當畫布上有一個或多個元素變化時,必須重新分析整塊畫布,才能得到繪製元素的最優方法。

許多使用者將整個遊戲的UI都放到一塊畫布,上面擺放了成百上千個元素。因此當修改其中一個元素時,會產生持續數毫秒的CPU使用量飆升情況。

解決方案:劃分畫布

每塊畫布上的元素都與其它畫布的元素相隔離,所以我們可以使用工具來切分畫布,從而解決Unity UI的批處理問題。

我們也可以通過巢狀畫布來解決,這樣允許設計師建立大型分層UI,而且不必擔心不同內容出現在多個畫布上。子畫布的內容與父畫布和同級畫布相互隔離,它們會保持自帶幾何體,執行自己的批處理。

當使用子畫布分離畫布時,嘗試根據畫布更新時間來分組。例如:分離動態元素和靜態元素。

問題:Graphic Raycaster有哪些最佳用法?

Graphic Raycaster元件能夠將輸入內容轉換為UI事件,它會把觸屏輸入轉為事件,然後傳送給相關UI元素。每個接收輸入內容的畫布都需要Graphic Raycaster元件,包括子畫布。

儘管該元件名為Graphic Raycaster,但它卻不是個光線投射器,預設情況下它只會測試UI圖形。該元件會獲取特定畫布上輸入資訊相關的UI元素集,然後執行交點測試,它會針對Graphic Raycaster的畫布上每個互動式UI元素的RectTransform,檢查輸入事件發生的位置。

解決方案:關閉靜態或非互動式元素的Raycast Target。

例如:有一個帶文字的按鈕,關閉該元素的Raycast Target會直接減少Graphic Raycaster每幀進行的交點測試次數。

問題:有時候Graphic Raycaster會充當光線投射器使用。

如果將畫布上的渲染模式設為世界空間攝像機(Worldspace Camera)或螢幕空間攝像機(Screen Space Camera),此時可以設定阻擋遮罩(Blocking Mask)。

阻擋遮罩決定光線投射器是通過2D物理還是3D物理投射光線,從而瞭解特定物理物件是否阻擋使用者與UI互動。

解決方案:通過2D或3D物理投射光線會消耗不少效能,所以要謹慎使用該功能。

儘量減少Graphic Raycaster的數量,不要將Graphic Raycaster新增到非互動式UI畫布上,因為這樣做無法檢查互動事件。

問題:世界空間畫布需要了解互動事件來自哪個攝像機。

當設定畫布進行渲染時,不管該畫布是在世界空間還是攝像機的螢幕空間,都可以指定用於為UI中Graphic Raycaster生成互動事件的攝像機。渲染模式為“Screen Space - Camera”的畫布需要使用該設定,該設定名為“Render Camera”。

然而在渲染模式為“World Space”的畫布上,該設定是可選的,名為“Event Camera”。

如果將世界空間畫布的Event Camera欄位留空,這不意味著該畫布不會接收事件。它會使用遊戲的主攝像機。為了確定哪個攝像機是主攝像機,該畫布會訪問Camera.main屬性。

根據Unity所使用的程式碼路徑,每幀中每有一個Graphic Raycaster和世界空間畫布,該畫布會訪問7到10次Camera.main。每次訪問Camera.main都會呼叫Object.FindObjectWithTag。這個做法在執行時並不合適。

解決方案:避免使用Camera.main。

快取攝像機的引用,然後建立系統來跟蹤主攝像機。如果使用世界空間畫布,要指定Event Camera,不要將該屬性留空。如果需要修改Event Camera,編寫程式碼來更新Event Camera屬性。

問題:每個影響佈局的UI元素都會至少執行一次GetComponents呼叫。

當修改佈局系統的一個或多個子元素時,會使佈局變髒。修改後的子元素會使擁有該元素的佈局系統(Layout System)無效化。

簡單介紹一下佈局系統:佈局系統是一組連續的佈局分組(Layout Group),它們在佈局元素(Layout Element)之上。佈局元素不只是名為Layout Element的元件,它們還包括UI影象、文字和Scroll Rect元件,而且Scroll Rect同時也是佈局分組。

回到問題本身,每個使佈局變髒的UI元素都會至少執行一次GetComponents呼叫,該呼叫會在佈局元素父物件上尋找有效的佈局分組。找到有效佈局分組後,它會繼續遍歷Transform層級,直到停止尋找分組或是到達層級的根部分,無論先滿足哪個條件都會停止尋找過程。因此。每個佈局分組會給每個子佈局元素的改變過程新增一次GetComponents呼叫,使巢狀佈局分組的效能變差。

解決方案:避免使用佈局分組。

使用錨點進行比例佈局。在擁有動態元素數量的活躍UI上,考慮編寫程式碼來計算佈局,僅在需要時執行該程式碼,而不是每次發生改變的時候。

問題:用錯誤的方法聚集UI物件。

通常情況下,使用者通過重置父物件來聚集UI物件,然後再禁用物件,但這樣會造成不必要的汙染。

解決方案:首先禁用物件,然後將其父物件重置為物件池。

這樣操作僅會改變一次原有的層級,但在重置父物件時,要避免二次改變原有的父物件,也不要改變新的層級。如果要從物件池移除物件,首先重置它的父物件,然後更新資料,再啟用該物件。

問題:如何隱藏畫布?

有時需要隱藏UI元素和畫布,要怎樣高效完成該任務呢?

解決方案:禁用Canvas元件。

禁用Canvas元件會阻止畫布向GPU發起繪圖呼叫,所以該畫布不再可見。然而,此時該畫布不會丟棄它的頂點緩衝區,它會保留所有網格和頂點,當重新啟用時不會觸發重構過程,它只會重新繪製畫布內容。

此外,禁用Canvas元件不會觸發Canvas層級上效能消耗較大的OnDisable/OnEnable回撥。禁用子元件時要小心,注意它是否執行效能消耗較大的每幀程式碼。

問題:如何在UI上使用Animator?

Animator每幀都會改變元素,即使動畫中的數值沒有變化。Animator沒有空指令檢查。

解決方案:

只在頻繁變化的動態元素上加入Animator。對於很少變化的元素,或是僅響應事件時才變化的元素,請自行編寫程式碼或補間系統,你可以在Asset Store資源商店找到許多補間系統外掛。