有時候會發現 Unity UI 非常耗時,在 Profiler 中可以輕易的看到 UI 中 的 GraphicRaycaster.Raycast 單幀調用可以成百上千,甚至好幾千,幀速率前不忍賭,一關掉 UI 立馬滿血復活。
這種情況大多出現在一是 Canvas 比較多而且子節點添加 GraphicRaycaster 組件也比較多的情況;二是每個 Canvas 下面有很多個 UI 面板,而每次其實顯示的只有那么幾個,不用的隱藏 SetActive(false),使用的激活顯示 SetActive(true)。
第一種情況就只能自己根據需求進行簡化,不要濫用了 Canvas,優化和精簡自己的設計;第二種情況看起來貌似沒什么問題,實際上正是幀速率大大下降的元凶。
說第二種沒問題主要是符合 Unity 的使用基本原則,而且大家容易想到的使用方式,那為什么有問題,那要先看看一點相關 UI 的工作模式。每個 Graphic 組件(包括所有繼承自 Graphic 的組件)都必須要通過 GraphicRegistry.RegisterGraphicForCanvas 注冊到 GraphicRegistry 中,Graphic 中能夠引起注冊的事件有:OnTransformParentChanged(),OnEnable(),OnCanvasHierarchyChanged();OnEnable() 這個不必說就是組件激活時的回調,其它兩個 OnTransformParentChanged(), OnCanvasHierarchyChanged() 理論上應該只在控件可用 Enable 和 Active 的時候才會被調用,或者說才應該產生 GraphicRegistry.RegisterGraphicForCanvas 的注冊過程,而現在的代碼是只有 OnTransformParentChanged() 在 GraphicRegistry.RegisterGraphicForCanvas 之前檢測了 IsActive(),而 OnCanvasHierarchyChanged() 卻未做任何檢測,即使掛接了 Graphic 的對象在 Unity 的 Inspector 中處於非激活狀態,啟動游戲后 OnCanvasHierarchyChanged() 依然會被調用,導致該 Graphic 被注冊進 GraphicRegistry,並且最終參與了 GraphicRaycaster.Raycast,造成了 CPU 周期的浪費。
總而言之,這應該是 Unity UI 系統的一個 bug,本來從 BitBucket 上 Fork 了想修改下提交,但是發現無法 clone,很是奇怪,暫時擱置了,既然暫時無法修改源碼,官方也沒有修改,那么暫時的解決方案就是在啟動后,手動清理每個 Canvas 下在 GraphicRegistry 中注冊的 Graphic,將所有 IsActive() 為 false 的組件通過 GraphicRegistry.UnregisterGraphicForCanvas 從 GraphicRegistry 中移除即可,達到曲線救國的目的。