轉自: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
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。