NGUI开发优化技巧(上)

发表于2017-10-25
评论0 2.2k浏览

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会使用物理系统PhysicsPhysics2D

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.   
  4. public class UIEventManager : MonoBehaviour {  
  5.   
  6.     private UIButton[] buttons;  
  7.   
  8.     // Use this for initialization  
  9.     void Start () {  
  10.         buttons = GetComponentsInChildren<UIButton>();  
  11.   
  12.         foreach (var button in buttons)  
  13.         {  
  14.             UIEventListener.Get(button.gameObject).onClick  = OnButtonClick;  
  15.         }  
  16.     }  
  17.       
  18.     // Update is called once per frame  
  19.     void Update () {  
  20.       
  21.     }  
  22.   
  23.     void OnButtonClick(GameObject go)  
  24.     {  
  25.         Debug.Log(go.name);  
  26.     }  
  27. }  

自定义材质

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

UIDrawCall:管理UI的显示,任何材质的替换都是在这个类里面做的,包括在裁剪前替换材质,并把裁剪区域做一个限定,也都是在这个脚本里面进行的;

 

DrawCall优化:

1Panel Tool

2Draw Call Tool



优化时,可以看到每个Panel产生了多少个Drawcall

如果Drawcall使用UISprite,材质一样,并且相邻,则会自动合并成一个Drawcall,如果没有合并,说明使用了UITexture,因为每个UITexture都会产生一个Drawcall

 

多个Drawcall合成一个时,并不会增大内存,因为多个UI元素,每个UI元素都对应一个Mesh,对应的Mesh顶点固定,如果不合并Drawcall,则他们的Mesh是分开的,合并后Mesh是合并的,但定点数和顶点属性是固定的;

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引