1. 程式人生 > >NGUI開發優化技巧(上)

NGUI開發優化技巧(上)

UGUINGUI的區別:

使用率:NGUI佔大多數,NGUIUGUI的使用佔比為81% : 19%

易用性:NGUI佔一定優勢,UGUI仍在慢慢改善;

效能:如果都合理搭配,UGUI在效能上還是有一點優勢的,在Unity5.25.3之後UGUI有一部分網格合併的操作是放在多執行緒中進行,相對來講,效能有一定的提升;

NGUI的優化相對更簡單一些,主要是因為NGUIDrawcall相對容易估計,可以知道Drawcall從哪裡來的,也更容易定位網格的重新整理是由什麼元素引起的, 但UGUIDrawcall難以估計,且網格重建的開銷因為引入了多執行緒,也會被隱藏起來,所以優化起來,

UGUI的難度相對更高一些;

螢幕自適應

1UIRoot/Scaling Style

   — FlexiblePixelPerfect

   — ConstrainedFixedSize


可以讓使用者指定一個區間,當真實螢幕的畫素高度介於最大、最小高度之間,那麼UI元素將保持原有的解析度,也就是說,在這個範圍之間,UI元素的畫素和裝置的畫素是可以匹配的;在解析度高的設配上UI會相對小一些,在解析度低的裝置上這些UI圖片會相對大一些;這種模式較為複雜,使用不多;


不同的裝置上看到的畫面只是等比的縮放,佈局更加容易,使用較多;


2UIAnchor 在舊的NGUI

版本上就存在,但現在使用較少,因為使用後,UI的層次會非常多;

3UIRect/AnchorPoint:在較新的NGUI中,Anchor放在了UIRect這個類中;


Execute的模式會影響效能,對於靜態的UI,不要使用On Update模式;

如果設定為On Update,則會在Profiler中看到UIRect.Update會比較高;

事件處理

1UIButton

2UIEventListener

NGUIUGUI相差很大,UGUI中的事件處理沒有使用到物理中的Physics.Raycast,而是通過

射線跟四邊形去做一些檢查,而NGUI會使用物理系統Physics

Physics2D

Unity4.XUGUI事件檢測的開銷有時候可能會有比較高的持續開銷,因為在Unity4.X中預設所有Graphics的元素(包含ImageTexture)都會作為事件檢查的目標,除非把Camera給禁用掉,在Unity5.2中才出現對每一個Graphics可以設定RaycastTarget這個屬性,去掉這個屬性之後,可以不參與檢測;

NGUI中就不會出現這個問題,因為NGUI只會在需要檢測的地方(ButtonToggle)掛上碰撞體,所以只有在掛了碰撞體的元素才會參與事件的檢測;

可以藉助UIEventListener元件,在按鈕或UI元素上掛上這個元件,然後在一個統一的地方去檢查,去處理所有Button

如下程式碼所示:

  1. using UnityEngine;  
  2. using System.Collections;  
  3. publicclass UIEventManager : MonoBehaviour {  
  4.     private UIButton[] buttons;  
  5.     // Use this for initialization
  6.     void Start () {  
  7.         buttons = GetComponentsInChildren<UIButton>();  
  8.         foreach (var button in buttons)  
  9.         {  
  10.             UIEventListener.Get(button.gameObject).onClick += OnButtonClick;  
  11.         }  
  12.     }  
  13.     // Update is called once per frame
  14.     void Update () {  
  15.     }  
  16.     void OnButtonClick(GameObject go)  
  17.     {  
  18.         Debug.Log(go.name);  
  19.     }  
  20. }  

自定義材質

1Gray

2ETC Split



比如:做一個會變灰的按鈕,自定義的Shader放到UI元素上可以正常顯示,但是把它放到ScrollView裡面的滾動區域時,材質丟失或完全變黑,這是因為NGUI在處理滾動框裡面的內容時,為了實現遮罩效果,會去動態替換裡面元素的Shader

Texturefen採用ETC1分離為RGBA圖片後,替換原有的Shader,當放入ScrollView的裁剪區域後,圖片變黑,想要恢復原狀,需要把原有的Shader替換成NGUI可以識別的Shader

比如:把Shader “UI/UI_ETC”替換為:Shader Hidden/UI/UI_ETC 1”(只用替換Shader名字,1代表被1Panel做了clipping,以此類推)



當我們需要提供一個自定義的材質時,需要附帶3個配套的版本,以保證UI元素放到滾動區域後依然能夠正常顯示;

ParticleSystem互動

1:前後遮擋

2ScrollView裁剪

如何把一個ParticleSystem放在兩個UI元素之間?

通過指令碼把ParticleSystemRendererRenderQueue控制在兩個UI元素之間;

ParticleSystem放到了滾動區域之後(比如揹包),有一些圖示需要高亮或者特殊效果,當滾動揹包時,希望揹包區域把這些粒子裁減掉,但預設情況下,並不會被裁剪,因為粒子系統的Shader是另一種渲染方式,跟NGUI的不一樣,NGUI不僅自身有一種UIShader,而且還需要去替換做clip裁剪的Shader版本;

一般可以考慮的會有以下幾種方式:

1:轉成序列幀,做成UI的方式去做,但是表現力會大打折扣,並且圖片量會有所提升;

2:設計自定義的ParticleSystemShader,並且NGUI中裁剪的機制放到自定義的Shader中去,相對複雜一些,畢竟ParticleSystem的座標系和產生的Mesh時候的座標系的對應不一樣;

3:通過額外的Camera去做,就是說Particle的裁剪是通過Camera的顯示區域去做,這種限制比較大,多加的Camera使UI層級的管理會比較複雜,所以還是使用第二種方式;


程式碼如下:

  1. using UnityEngine;  
  2. using System.Collections;  
  3. [ExecuteInEditMode]  
  4. [RequireComponent(typeof(ParticleSystemRenderer))]  
  5. publicclass UIParticle : MonoBehaviour {  
  6.     private UIPanel panel;  
  7.     private ParticleSystemRenderer pRenderer;  
  8.     private Material dyMaterial;  
  9.     private UIWidget cover;  
  10.     // Use this for initialization
  11.     void Start () {  
  12.         panel = GetComponentInParent<UIPanel>();  
  13.         pRenderer = GetComponent<ParticleSystemRenderer>();  
  14.        // dyMaterial = new Material(Shader.Find("Hidden/Unlit/Transparent Colored 1"))
  15.         dyMaterial = new Material(Shader.Find("Unlit/Transparent Colored"))  
  16.         {  
  17.             renderQueue = 4000,  
  18.             mainTexture = pRenderer.sharedMaterial.mainTexture  
  19.         };  
  20.         pRenderer.material = dyMaterial;  
  21.     }  
  22.     // Update is called once per frame
  23.     void OnWillRenderObject () {  
  24.         if (cover!=null && cover.isActiveAndEnabled && cover.drawCall!=null)  
  25.         {  
  26.             dyMaterial.renderQueue = cover.drawCall.renderQueue;  
  27.         }  
  28.         Vector4 cr = panel.drawCallClipRange;  
  29.         Vector2 soft = panel.clipSoftness;  
  30.         Vector2 sharpness = new Vector2(1000.0f, 1000.0f);  
  31.         if (soft.x > 0f) sharpness.x = cr.z / soft.x;  
  32.         if (soft.y > 0f) sharpness.y = cr.w / soft.y;  
  33.         float scale = 1.0f / transform.lossyScale.x;  
  34.         Vector3 position = -panel.transform.position * scale;  
  35.         dyMaterial.SetVector(Shader.PropertyToID("_ClipRange0"),   
  36.             new Vector4(-cr.x/cr.z + position.x/cr.z, -cr.y/cr.w + position.y/cr.w, 1f/cr.z*scale, 1f/cr.w*scale));  
  37.         dyMaterial.SetVector(Shader.PropertyToID("_ClipArgs0"), new Vector4(sharpness.x, sharpness.y, 0, 1));  
  38.     }  
  39.     void OnDestroy()  
  40.     {  
  41.         DestroyImmediate(dyMaterial);  
  42.         dyMaterial = null;  
  43.     }  
  44. }  

UIDrawCall:管理UI的顯示,任何材質的替換都是在這個類裡面做的,包括在裁剪前替換材質,並把裁剪區域做一個限定,也都是在這個腳本里面進行的;

DrawCall優化:

1Panel Tool

2Draw Call Tool



優化時,可以看到每個Panel產生了多少個Drawcall

如果Drawcall使用UISprite,材質一樣,並且相鄰,則會自動合併成一個Drawcall,如果沒有合併,說明使用了UITexture,因為每個UITexture都會產生一個Drawcall

多個Drawcall合成一個時,並不會增大記憶體,因為多個UI元素,每個UI元素都對應一個Mesh,對應的Mesh頂點固定,如果不合並Drawcall,則他們的Mesh是分開的,合併後Mesh是合併的,但定點數和頂點屬性是固定的;