关于Unity中的UGUI优化,你可能遇到这些问题
一、界面制作
Q1:UGUI里的這個選項 ,應該是ETC2拆分Alpha通道的意思,但是在使用中并沒起作用?請問有沒有什么拆分的標準和特別要求呢?
據我們所知,alpha split 的功能最初只對 Unity 2D 的 Sprite(SpriteRenderer)有完整的支持,而UI的支持是在Unity 5.4版本之后的。建議大家在Unity 5.4版本以后的UGUI中嘗試該功能。
Q2:在UI界面中,用Canvas還是用RectTransform做根節點更好?哪種方法效率更高?
Canvas劃分是個很大的話題。簡單來說,因為一個Canvas下的所有UI元素都是合在一個Mesh中的,過大的Mesh在更新時開銷很大,所以一般建議每個較復雜的UI界面,都自成一個Canvas(可以是子Canvas),在UI界面很復雜時,甚至要劃分更多的子Canvas。同時還要注意動態元素和靜態元素的分離,因為動態元素會導致Canvas的mesh的更新。最后,Canvas又不能細分的太多,因為會導致Draw Call的上升。我們后續將對UI模塊做具體的講解,盡請期待。
Q3:UWA性能檢測報告中的Shared UI Mesh表示什么呢?
Shared UI Mesh是在Unity 5.2 版本后UGUI系統維護的UI Mesh。在以前的版本中,UGUI會為每一個Canvas維護一個Mesh(名為BatchedMesh,其中再按材質分為不同的SubMesh)。而在Unity 5.2版本后,UGUI底層引入了多線程機制,而其Mesh的維護也發生了改變,目前Shared UI Mesh作為靜態全局變量,由底層直接維護,其大小與當前場景中所有激活的UI元素所生成的網格數相關。
一般來說當界面上UI元素較多,或者文字較多時該值都會較高,在使用UI/Effect/shadow和UI/Effect/Outline時需要注意該值,因為這兩個Effect會明顯增加文字所帶來的網格數。
Q4:在使用NGUI時,我們通常會將很多小圖打成一個大的圖集,以優化內存和Draw Call。而在UGUI時代,UI所使用的Image必須是Sprite;Unity提供了SpritePacker。 它的工作流程和UGUI Atlas Paker有較大的差別。在Unity Asset中,我們壓根看不到圖集的存在。 問題是:
1. SpritePacker大概的工作機制是什么樣的?
2. 如果Sprite沒有打包成AssetBundle,直接在GameObject上引用,那么在Build時Unity會將分散的Sprite拼接在一起么?如果沒有拼接,那SpritePacker是不是只會優化Draw Call,內存占用上和不用SpritePacker的分離圖效果一樣?
3. 如果將Sprite打成AssetBundle,AssetBundle中的資源是分散的Sprite嗎?如果不是,不同的AssetBundle中引用了兩張Sprite,這兩張Sprite恰好用SpritePacker拼在了一起,是不是就會存在兩份拼接的Sprite集?
4. 如果想使用NGUI Atlas Packer的工作流程,該如何去實現?
簡單來說,UGUI和 NGUI 類似,但是更加自動化。只需要通過設定 Packing Tag 即可指定哪些 Sprite 放在同一個 Atlas 下。
兩種做法各有利弊,建議分析一下兩種做法對于自身項目的合適程度來進行選擇。
Q5:在Unity 5.x版本下,我們在用UGUI的過程中發現它把圖集都打進了包里,這樣就不能自動更新了,請問圖集怎么做自動更新呢?
在Unity 5.x中UGUI使用的Atlas確實是不可見的,因此無法直接將其獨立打包。但我們建議,可以把Packing Tag相同的源紋理文件,打到同一個AssetBundle中(設置一樣的AssetBundle Name),從而避免Atlas的冗余。同時這樣打包可以讓依賴它的Canvas的打包更加自由,即不需要把依賴它的Canvas都打在一個AssetBundle中,在更新時直接更新Atlas所在的AssetBundle即可。
Q6:ScrollRect在滾動的時候,會產生Canvas.SendwillRenderCanvases,有辦法消除嗎?
ScrollRect在滾動時,會產生OnTransformChanged的開銷,這是UI元素在移動時觸發的,但通常這不會觸發Canvas.SendWillRenderCanvases。
如果觀察到Canvas.SendWillRenderCanvases耗時較高,可以檢查下ScrollRect所在的Canvas是否開啟了Pixel Perfect的選項,該選項的開啟會導致UI元素在發生位移時,其長寬會被進行微調(為了對其像素),而ScrollRect中通常有較多的UI元素,從而產生較高的Canvas.SendWillRenderCanvases開銷。因此可以嘗試關閉Pixel Perfect看效果是否可以接受,或者嘗試在滾動過程中暫時關閉Pixel Perfect等方式來消除其開銷。
二、網格重建
Q1:我在UGUI里更改了Image的Color屬性,那么Canvas是否會重建?我只想借用它的Color做Animation里的變化量。
如果修改的是Image組件上的Color屬性,其原理是修改頂點色,因此是會引起網格的Rebuild的(即Canvas.BuildBatch操作,同時也會有Canvas.SendWillRenderCanvases的開銷)。而通過修改頂點色來實現UI元素變色的好處在于,修改頂點色可以保證其材質不變,因此不會產生額外的Draw Call。
Q2:Unity自帶的UI Shader處理顏色時,改_Color屬性不會觸發頂點重建嗎?
在UI的默認Shader中存在一個Tint Color的變量,正常情況下,該值為常數(1,1,1),且并不會被修改。如果是用腳本訪問Image的Material,并修改其上的Tint Color屬性時,對UI元素產生的網格信息并沒有影響,因此就不會引起網格的Rebuild。但這樣做因為修改了材質,所以會增加一個Draw Call。
Q3:能否就UGUI Batch提出一些建議呢?是否有一些Batch的規則?
在 UGUI 中,Batch是以Canvas為單位的,即在同一個Canvas下的UI元素最終都會被Batch到同一個Mesh中。而在Batch前,UGUI會根據這些UI元素的材質(通常就是Atlas)以及渲染順序進行重排,在不改變渲染結果的前提下,盡可能將相同材質的UI元素合并在同一個SubMesh中,從而把DrawCall降到最低。而Batch的操作只會在UI元素發生變化時才進行,且合成的Mesh越大,操作的耗時也就越大。
因此,我們建議盡可能把頻繁變化(位置,顏色,長寬等)的UI元素從復雜的Canvas中分離出來,從而避免復雜的Canvas頻繁重建。
Q4:我用的是UGUI Canvas,Unity 5.3.4版本,請問如何查看每次Rebuild Batch影響的頂點數, Memory Profiler是個辦法但是不好定位。
由于Unity引擎在5.2后開始使用Shared UI Mesh來存儲UI Mesh,所以確實很難查看每次Rebuild的UI頂點數。但是,研發團隊可以嘗試通過Frame Debugger工具對UI界面進行進一步的查看。
Q5:動靜分離或者多Canvas帶來性能提升的理論基礎是什么呢?如果靜態部分不變動,整個Canvas就不刷新了?
在UGUI中,網格的更新或重建(為了盡可能合并UI部分的DrawCall)是以Canvas為單位的,且只在其中的UI元素發生變動(位置、顏色等)時才會進行。因此,將動態UI元素與靜態UI元素分離后,可以將動態UI元素的變化所引起的網格更新或重建所涉及到的范圍變小,從而降低一定的開銷。而靜態UI元素所在的Canvas則不會出現網格更新和重建的開銷。
Q6:UWA建議“盡可能將靜態UI元素和頻繁變化的動態UI元素分開,存放于不同的Panel下。同時,對于不同頻率的動態元素也建議存放于不同的Panel中?!蹦敲凑垎?#xff0c;如果把特效放在Panel里面,需要把特效拆到動態的里面嗎?
通常特效是指粒子系統,而粒子系統的渲染和UI是獨立的,僅能通過Render Order來改變兩者的渲染順序,而粒子系統的變化并不會引起UI部分的重建,因此特效的放置并沒有特殊的要求。
Q7:多人同屏的時候,人物移動會使得頭頂上的名字Mesh重組,從而導致較為嚴重的卡頓,請問一下是否有優化的辦法?
如果是用UGUI開發的,當頭頂文字數量較多時,確實很容易引起性能問題,可以考慮從以下幾點入手進行優化:
盡可能避免使用UI/Effect,特別是Outline,會使得文本的Mesh增加4倍,導致UI重建開銷明顯增大;
拆分Canvas,將屏幕中所有的頭頂文字進行分組,放在不同的Canvas下,一方面可以降低更新的頻率(如果分組中沒有文字移動,該組就不會重建),另一方面可以減小重建時涉及到的Mesh大小(重建是以Canvas為單位進行的);
降低移動中的文字的更新頻率,可以考慮在文字移動的距離超過一個閾值時才真正進行位移,從而可以從概率上降低Canvas更新的頻率。
三、界面切換
Q1:游戲中出現UI界面重疊,該怎么處理較好?比如當前有一個全屏顯示的UI界面,點其中一個按鈕會再起一個全屏界面,并把第一個UI界面蓋住。我現在的做法是把被覆蓋的界面 SetActive(False),但發現后續 SetActive(True) 的時候會有 GC.Alloc 產生。這種情況下,希望既降低 Batches 又降低 GC Alloc 的話,有什么推薦的方案嗎?
可以嘗試通過添加一個 Layer 如 OutUI, 且在 Camera 的 Culling Mask 中將其取消勾選(即不渲染該 Layer)。從而在 UI 界面切換時,直接通過修改 Canvas 的 Layer 來實現“隱藏”。但需要注意事件的屏蔽,禁用動態的 UI 元素等等。
這種做法的優點在于切換時基本沒有開銷,也不會產生多余的 Draw Call,但缺點在于“隱藏時”依然還會有一定的持續開銷(通常不太大),而其對應的 Mesh 也會始終存在于內存中(通常也不太大)。
以上的方式可供參考,而性能影響依舊是需要視具體情況而定。
Q2:如圖,我們在UI打開或者移動到某處的時候經常會觀測到CPU上的沖激,經過進一步觀察發現是因為Instantiate產生了大量的GC。想請問下Instantiate是否應該產生GC呢?我們能否通過資源制作上的調整來避免這樣的GC呢?如下圖,因為一次性產生若干MB的GC在直觀感受上還是很可觀的。
準確的說這些 GC Alloc 并不是由Instantiate 直接引起的,而是因為被實例化出來的組件會進行 OnEnable 操作,而在 OnEnable 操作中產生了 GC,比如以上圖中的函數為例:
上圖中的 Text.OnEnable 是在實例化一個 UI 界面時,UI 中的文本(即 Text 組件)進行了 OnEnable 操作,其中主要是初始化文本網格的信息(每個文字所在的網格頂點,UV,頂點色等等屬性),而這些信息都是儲存在數組中(即堆內存中),所以文本越多,堆內存開銷越大。但這是不可避免的,只能盡量減少出現次數。
因此,我們不建議通過 Instantiate/Destroy 來處理切換頻繁的 UI 界面,而是通過 SetActive(true/false),甚至是直接移動 UI 的方式,以避免反復地造成堆內存開銷。
四、加載相關
Q1:UGUI的圖集操作中我們有這么一個問題,加載完一張圖集后,使用這個方式獲取其中一張圖的信息:assetBundle.Load (subFile, typeof (Sprite)) as Sprite; 這樣會復制出一個新貼圖(圖集中的子圖),不知道有什么辦法可以不用復制新的子圖,而是直接使用圖集資源 。
經過測試,這確實是 Unity 在 4.x 版本中的一個缺陷,理論上這張“新貼圖(圖集中的子圖)”是不需要的,并不應該加載。 因此,我們建議通過以下方法來繞過該問題:
在 assetBundle.Load (subFile, typeof (Sprite)) as Sprite; 之后,調用
Texture2D t = assetBundle.Load (subFile, typeof (Texture2D)) as Texture2D;
Resources.UnloadAsset(t);
從而卸載這部分多余的內存。
Q2:加載UI預制的時候,如果把特效放到預制里,會導致加載非常耗時。怎么優化這個加載時間呢?
UI和特效(粒子系統)的加載開銷在多數項目中都占據較高的CPU耗時。UI界面的實例化和加載耗時主要由以下幾個方面構成:
紋理資源加載耗時
UI界面加載的主要耗時開銷,因為在其資源加載過程中,時常伴有大量較大分辨率的Atlas紋理加載,我們在之前的Unity加載模塊深度分析之紋理篇有詳細講解。對此,我們建議研發團隊在美術質量允許的情況下,盡可能對UI紋理進行簡化,從而加快UI界面的加載效率。
UI網格重建耗時
UI界面在實例化或Active時,往往會造成Canvas(UGUI)或Panel(NGUI)中UIDrawCall的變化,進而觸發網格重建操作。當Canvas或Panel中網格量較大時,其重建開銷也會隨之較大。
UI相關構造函數和初始化操作開銷
這部分是指UI底層類在實例化時的ctor開銷,以及OnEnable和OnDisable的自身開銷。
上述2和3主要為引擎或插件的自身邏輯開銷,因此,我們應該盡可能避免或降低這兩個操作的發生頻率。我們的建議如下:
在內存允許的情況下,對于UI界面進行緩存。盡可能減少UI界面相關資源的重復加載以及相關類的重復初始化;
根據UI界面的使用頻率,使用更為合適的切換方式。比如移進移出或使用Culling Layer來實現UI界面的切換效果等,從而降低UI界面的加載耗時,提升切換的流暢度。
對于特效(特別是粒子特效)來說,我們暫時并沒有發現將UI界面和特效耦合在一起,其加載耗時會大于二者分別加載的耗時總和。因此,我們僅從優化粒子系統加載效率的角度來回答這個問題。粒子系統的加載開銷,就目前來看,主要和其本身組件的反序列化耗時和加載數量相關。對于反序列化耗時而言,這是Unity引擎負責粒子系統的自身加載開銷,開發者可以控制的空間并不大。對于加載數量,則是開發者需要密切關注的,因為在我們目前看到的項目中,不少都存在大量的粒子系統加載,有些項目的數量甚至超過1000個,如下圖所示。因此,建議研發團隊密切關注自身項目中粒子系統的數量使用情況。一般來說,建議我們建議粒子系統使用數量的峰值控制在400以下。
Q3:我有一個UI預設,它使用了一個圖集, 我在打包的時候把圖集和UI一起打成了AssetBundle。我在加載生成了GameObject后立刻卸載了AssetBundle對象, 但是當我后面再銷毀GameObject的時候發現圖集依然存在,這是什么情況呢?
這是很可能出現的。unload(false)卸載AssetBundle并不會銷毀其加載的資源 ,是必須對其調用Resources.UnloadAsset,或者調用Resources.UnloadUnusedAssets才行。關于AssetBundle加載的詳細解釋可以參考我們之前的文章:你應該知道的AssetBundle管理機制。
五、字體相關
Q1:我在用Profiler真機查看iPhone App時,發現第一次打開某些UI時,Font.CacheFontForText占用時間超過2s,這塊主要是由什么影響的?若iPhone5在這個接口消耗2s多,是不是問題很大?這個消耗和已經生成的RenderTexture的大小有關嗎?
Font.CacheFontForText主要是指生成動態字體Font Texture的開銷, 一次性打開UI界面中的文字越多,其開銷越大。如果該項占用時間超過2s,那么確實是挺大的,這個消耗也與已經生成的Font Texture有關系。簡單來說,它主要是看目前Font Texture中是否有地方可以容下接下來的文字,如果容不下才會進行一步擴大Font Texture,從而造成了性能開銷。
原文出處:侑虎科技
本文作者:admin
轉載請與作者聯系,同時請務必標明文章原始出處和原文鏈接及本聲明。
總結
以上是生活随笔為你收集整理的关于Unity中的UGUI优化,你可能遇到这些问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 技术分享连载(六十)
- 下一篇: 深入源码 UITableView 复