1.UI管理设计模式
使用工厂模式创建window,通过UI名字索引进行动态创建,降低耦合性,至于gameobject所绑定的类,可以选择使用addcomponent添加,也可以在预制体上直接绑定好,这两种方法的区别是AddComponent可能会有小的开销,比起直接将代码放在预制体上面。
步骤为:加载对应名字的预制体,通过GetOrAddComponent为UI添加对应名字的类并且Init初始化,直接add或者通过反射获取类
(1)window = winGo.GetComponent<IUGUI>() as IUGUI;
(2)System.Type tp = System.Type.GetType("UITest");
if (tp != null)
{
window = System.Activator.CreateInstance(tp) as IUGUI;
}
2.ui的加载,同步还是异步?
同步加载的好处:直接打开UI,将逻辑在一帧内执行完毕,对于不是特别庞大的UI感觉不到卡顿的话,玩家的体验也比较好,UI瞬间打开,Cpu得到充分利用
弊端:对于比较庞大的UI打开可能会引起掉帧卡顿,尤其在预制体很大的情况下
异步加载的好处:异步加载的思路是将加载的过程分帧进行,好处就是在UI打开的时候不会引起当前帧的线程处理速度而引起掉帧卡顿,那么关键就在于我们的加载过程如果用协程,yield return来分割的话,在哪几个节点进行分割最能平滑和高效的分割这个过程,而卡顿的元凶则是预制体prefab的加载,而一般工程都会有异步加载资源的机制,所以将prefab的加载设置为异步加载,待加载完成后再进行后续逻辑可有效的缓解卡顿
而异步加载同样也存在弊端,所有的异步加载都可能存在的问题就是Cpu得不到充分利用,或者说分帧不合理不均匀适得其反,拿刚进入游戏的Loading来说,很多做法是异步加载,但其实从逻辑上分析来说,同步加载更好些,为什么呢,异步加载每帧的cpu大多数是得不到充分利用的,也就是说33ms的时间,20ms在加载,剩下13ms在等待,而同步加载每帧的时间都得到充分利用,而loading这种特殊的情况下,本来界面就是进度条状态,玩家也是等待,同步并不会造成卡顿上的影响,反而整体应该比异步更快
3.UI中的Mesh
UI中的Mesh重建过程会带来大量的开销,那么如何避免和减少Mesh重建的可能呢
(1)当ui发生改变,比如有组件的添加、删除、遮挡关系改变等事情发生的时候会需要重建Mesh,而当ui的transform发生改变的时候,如果不影响原本的遮挡关系,是不会导致Mesh重建的过程的。
Tips:减少Mesh的重建次数,创建或者销毁,还有active和deactive的过程都会导致Mesh重建,推荐通过Layer对相机的层进行隐藏,或者将其移除视口外
(2)UGUI中Mesh的重建的基本单位是Canvas。
Tips:减少Mesh重建时的影响范围,动静分离,动态变化的部分每帧都会引起Mesh重建,可以选择将动态的部分合并,并且添加一个Canvas组件
4.慎用UI的outLine和shadow
(1)OutLine
OutLine的描边会额外生成四份Text文本,而每个文本是一个矩形,两个三角形,这会增加大量的顶点,可以通过将OutLine组件的Effect Distance调大看到周围多了四个文本,这就是产生的额外开下,如果我们有大量的Text而他们都添加了这个组件,可能会导致顶点剧增甚至引起崩溃
(2)Shadow
Shadow和OutLine很类似,但好一点的是Shadow会额外产生两个,但是这在数量巨大的时候也同样要小心
5.防止预制体过大或者频繁加载和卸载
(1)每个预制体不应该过大,如果超过了一定的大小,应该对其进行拆分,保证单个预制体不至于过大,不然在加载资源的时候难免会遇到卡顿和掉帧的情况
(2)页签间的Prefab切换应该进行缓存,在第一次访问时加载(或者界面打开的时候都加载进来,需要进行分帧),然后再代码中进行缓存,下次访问直接使用
6.同一个Layer的UI要尽可能不要重叠
7.Button的射线检测实际上是通过绑定在Button上Image组件来判断是否点击的,同样如果Image不需要进行事件监听,则应该把其RayCast Target取消勾选
8.如何隐藏画布
通过禁用Canvas而不是setactive(false)来隐藏,禁用Canvas组件会阻止画布向GPU发起绘图调用,所以该画布不再可见。然而,此时该画布不会丢弃它的顶点缓冲区,它会保留所有网格和顶点,当重新启用时不会触发重构过程,它只会重新绘制画布内容
1.分帧的地方 2.逻辑线程帧的原理
9.UI继承关系分析
(1)UIBehavior
UI的基类可以看到 UIBeahavior中的大多数方法都是沿用的MonoBehavior的方法(绿字),而要证明调用的到底是父类MonoBehavior中的这个方法还是子类的方法,可以新写一个类,观察继承自MonoBehavior和UIBehavior的情况差别,看是调用的父类还是子类的。(继承自mono可以监听到,说明子类UIBehavior做了其他的工作而不是监听到的父类事件)。
tips:Mono的组织原理,On类的方法多是作为框架的回调,比如UI父节点中发生变化时,调用父节点下面所有UI节点的OnCanvasChange方法(此处可以应用脏标记,不发生变化的不用处理)
(2)Graphic
图形基类继承自UI基类和一个接口ICanvaseElement(名字可以看出来时UI中的基本单元),UIBehavior上面已经分析过了,下面分析一下这个接口和Graphic
ICanvasElement:
ICanvasElementinterface 1:父类中Component有一个Transform,也就是我们平时可以Get到的Transform方法,所以这个接口中的方法,应该是用来计算UI系统中的位置而产生的额外的变量
interface 2: 图形构建完的回调
interface3:布局完成的回调
interface 4:Canvas重建完成的回调(这也解释了为什么UI优化时提到的UI重建的基本单元是Canvas,而一个Canvas管理着他下面的ICanvasElement节点,所以要减少UI的重建以达到减少这些调用和重建子节点的开销)
Graphic看到很多脏标记回调(一种优化策略)
这个层级顾名思义,主要负责图像的生成,点击检测(raycastTarget),纹理, 材质,颜色, 透明度处理,再就是很多图像生成,混合,处理后的回调
(4)MaskableGraphic
MaskableGraphic
基本完全继承了Graphic,只不过多加了Mask这层功能,可以看到,后面三个新的接口也基本上是服务于这个新的mask功能
(5)对比Image和Button
Image的定义 Button的定义对比来看,Image父类中具有图形相关的功能,包括点击事件的监听,而Button中基本上都是IPoint开头的接口,也就是用于实现事件系统的回调,而Selectable中有一个变量public Graphic targetGraphic,也就是他需要检测的点击事件,一般会绑定一个Image,那么为什么要绑定一个Image呢,原因肯定就是Button自己本身不具有这个功能呗,将这个功能放在了Image里面,所以Button的点击其实是通过Image,而Button的这个变量即使不绑定任何东西,只要子节点有Image就可以监听到消息(可以自己尝试)。
10.自己实现UI组件(比如曲线滑动列表)
踩过这方面的一些坑,简单说下思路把,继承UIBehavior,外加一些接口,类似于IDragHandler等,OnBeginDrag(PointEventData eventData)
底层相应的数据都在eventData里面,自己做处理,通过鼠标移动的坐标做一些自己组件的计算
网友评论