(轉)Unity 之 UGUI 小總結


轉自:http://www.jianshu.com/p/5b6f5022662e

 開發過程中對UGUI的一個小總結。

   首先從原畫師拿到效果圖,美術切圖,拿到碎圖后打成大圖。

    我們先來說一下圖:RGBA8888:每一個通道占8位。大圖:1024*1024。高端    機:2048*2048。

   我們通常從美工那里拿來碎圖,歪歪使用的一個工具texturepacker 把碎圖打成大圖,導出成 .tpsheet  .png格式。其次我們要做的是,在Unity3d中導入插件texture import(此插件會自動把大圖打成圖集).然后把大圖導入U3D。

  在UI優化中較為明顯而眾所周知的就是降低DrawCall(注釋:DrawCall CPU向GPU發送的一次渲染指令)。而降低DrawCall我們會采用靜態合批和動態合批。(注釋:合批必須同類型 Mesh 同材質。)DrawCall的標准 RGP 類 小於 150。

   然而,工作中我們優化大概分為UI層級計算,UI重建和多層級渲染。接下來我們來說一下:

UI層級 計算 :

1,計算層級:

1,如果有一個UI元素,它所占的屏幕范圍內(通常是矩形),如果沒有任何UI在它的底下,那么它的層級號就是0(最底下);

2,如果有一個UI在其底下且該UI可以和它Batch,那它的層級號與底下的UI層級一樣;

3,如果有一個UI在其底下但是無法與它Batch,那它的層級號為底下的UI的層級+1;

4,如果有多個UI都在其下面,那么按前兩種方式遍歷計算所有的層級號,其中最大的那個作為自己的層級號。

合並批次原則;

同層級{同材質球,}

{0 : { image   , image, text  }}

1,Unity會將每一層的所有元素進行一個排序(按照材質、紋理等信息),合並掉可以Batch的元素成為一個批次

2,Text組件會排在Image組件之前渲染。

3,,Unity會再做一個優化,即如果相鄰間的兩個批次正好可以Batch的話就會進行Batch(這么處理,可以合成一個批次,如下:)

{0 : { text   image  }}textàimage

{1 : { text  ,image }}

0: textàimage    1: textàimage

下面我們來看一個列子,這樣會更加的清晰:

一個層級為0的ImageA,一個層級為1的ImageB(2個Image可Batch)和一個層級為0的TextC,

textCàimageAàimageB

一個層級為0的TextD,一個層級為1的TextE(2個Text可Batch)和一個層級為0的ImageF,

0 : textD –>imageFà1 : textEàimage h    3:

0 : textD –>imageFà1 : textEàimage h

總結:

1,有相同材質和紋理的UI元素是可以Batch的,可以Batch的UI上下疊在一塊不會影響性能,但是如果不能Batch的UI元素疊在一塊,就會增加Drawcall開銷

2,盡量讓同一個材質球上的東西 放在同一級上

3,有些情況可以考慮人為增加層級從而減少Drawcall,比如一個Text的層級為0,另一個可Batch的Text疊在一個圖片A上,層級為1,那此時2個Text因為層級不同會安排2個Drawcall,但如果在第一個Text下放一個透明的圖片(與圖片A可Batch),那兩個Text的層級就一致了,Drawcall就可以減少一個。

Text0 –》Imageàtext 1

4,應該盡量避免使用Mask,其實Mask的功能有些時候可以變通實現,比如設計一個邊框,讓這個邊框疊在最上面,底下的UI移動時,就會被這個邊框遮住;

5, z值 保持為0

UI重建:

1,動靜分離:

經常發生變動的ui單獨放在一個Canvase

2,刪除不必要的元素。

3,CanvasRender.setDisable .

4,Text的Best Fit選項.

5,Canvas的Pixel Perfect選項

6,使用緩存池來保存ScrollView中的Item,對於移出或移進View外的的元素,不要調用disable或enable,而是把它們放到緩存池里或從緩存池中取出復用。

7, 除了rebuild過程之外,UGUI的touch處理消耗也可能會成為性能熱點。因為UGUI在默認情況下會對所有可見的Graphic組件調用raycast。對於不需要接收touch事件的grahic,一定要禁用raycast。對於unity5以上的可以關閉graphic的Raycast Target而對於unity4.6,可以給不需要接收touch的UI元素加上canvasgroup組件。


 

多層級渲染:

1,canvasRender .setdisable()

2,只掛載button替代  空的image

3,不要使用空的Image,在Unity中,RayCast使用Graphi作為基本元素來檢測touch,在筆者參與的項目中,很多同學使用空的image並將alpha設置為0來接收touch事件,這樣會產生不必要的overdraw。通過如下類NoDrawingRayCast來接收事件可以避免不必要的overdraw。

3. public class NoDrawingRayCast : Graphic

4. {

5.     public override void SetMaterialDirty()

6.     {

7.     }

8.     public override void SetVerticesDirty()

9.     {

10.     }

11.     protected override void OnFillVBO(List vbo)

12.     {

13.         vbo.Cslear();

14.     }

}

性能檢測工具:

1profile

常見問題:

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。

Q:動靜分離或者多Canvas帶來性能提升的理論基礎是什么呢?如果靜態部分不變動,整個Canvas就不刷新了?

在UGUI中,網格的更新或重建(為了盡可能合並UI部分的DrawCall)是以Canvas為單位的,且只在其中的UI元素發生變動(位置、顏色等)時才會進行。因此,將動態UI元素與靜態UI元素分離后,可以將動態UI元素的變化所引起的網格更新或重建所涉及到的范圍變小,從而降低一定的開銷。而靜態UI元素所在的Canvas則不會出現網格更新和重建的開銷。

Q:UWA建議“盡可能將靜態UI元素和頻繁變化的動態UI元素分開,存放於不同的Panel下。同時,對於不同頻率的動態元素也建議存放於不同的Panel中。”那么請問,如果把特效放在Panel里面,需要把特效拆到動態的里面嗎?

通常特效是指粒子系統,而粒子系統的渲染和UI是獨立的,僅能通過Render Order來改變兩者的渲染順序,而粒子系統的變化並不會引起UI部分的重建,因此特效的放置並沒有特殊的要求。

Q:多人同屏的時候,人物移動會使得頭頂上的名字Mesh重組,從而導致較為嚴重的卡頓,請問一下是否有優化的辦法?

如果是用UGUI開發的,當頭頂文字數量較多時,確實很容易引起性能問題,可以考慮從以下幾點入手進行優化:

1.盡可能避免使用UI/Effect,特別是Outline,會使得文本的Mesh增加4倍,導致UI重建開銷明顯增大;

2.拆分Canvas,將屏幕中所有的頭頂文字進行分組,放在不同的Canvas下,一方面可以降低更新的頻率(如果分組中沒有文字移動,該組就不會重建),另一方面可以減小重建時涉及到的Mesh大小(重建是以Canvas為單位進行的);

3.降低移動中的文字的更新頻率,可以考慮在文字移動的距離超過一個閾值時才真正進行位移,從而可以從概率上降低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界面,會使得被隱藏的UIPanel繼續執行更新(LateUpdate有持續開銷),那么如果打開的界面比較多,CPU的持續開銷是否就會超過一次SetActive所帶來的開銷?

這確實是需要注意的,通過移動的方式“隱藏”的UI界面只適用於幾個切換頻率最高的界面,另外,如果“隱藏”的界面持續開銷較高,可以考慮只把一部分Disable,這個可能就需要具體看界面的復雜度了。一般來說在沒有UI元素變化的情況下,持續的Update開銷是不太明顯的。

Q3:如圖,我們在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加載模塊深度分析之紋理篇有詳細講解。對此,我們建議研發團隊在美術質量允許

1,1024*768    1024*1024

2, 640*480      512*512



作者:歪歪小花鹿
鏈接:http://www.jianshu.com/p/5b6f5022662e
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM