你知道的越多,你不知道的越多 🇨🇳🇨🇳🇨🇳
点赞再看,养成习惯,别忘了一键三连哦 👍👍👍
文章持续更新中 📝📝📝
1️⃣深入阐述NGUI的三大基础机制吗?🔥🔥🔥
🎬这三大基础机制分别是:渲染机制事件、消息机制、间格动画
📣1、渲染机制事件
🚩NGUI基础脚本
🔑UIWidget 是UI的基础组件(UILabel,UISprite)的基类,含有组件的基本信息(width,Height,color 锚点等)
🔑 UIGeometry 是UIWidget的几何数据,记录了顶点坐标,贴图的UVs和颜色等信息
🔑UIDrawCall 是将多个UIWidget的UIGeometry组合起来一起绘制
🔑UIPanel 用于管理UIWidget、UIDrawCall等,实现界面的渲染裁剪、更新。
🔑UIRoot UI界面的根目录,用于分辨率适配和事件广播。
MeshFilter网格过滤器用于从你的资源中获取网格信息(Mesh)并将其传递到用于将其渲染到屏幕的网格渲染器MeshRenderer当中。
NGUI渲染实际上就是对通过对Mesh的渲染实现的。整个NGUI的渲染过程就把图片和文字按照一定顺序和规则组织成一系列的Mesh,然后通过Unity自身的渲染流程实现渲染。
🚩NGUI的渲染流程
-
渲染过程开始的地方UIPanel.LateUpdate:
-
调用Updateself
-
调用UpdateWidgets:更新UIWeight的位置信息、可见性[含Alpha]、UV信息,是否渲染刷新。
4.UIsprite就是继承于UIWeight的OnFill函数实现多样的化的填装方式(即把UIWeight所需要的数据处理到UIGeometry节点中),如:拉伸、九宫格等等。 -
FillAllDrawCalls:全体DrawCall节点刷新。
-
FillDrawCall:单个DrawCall节点刷新,找到属于这个DrawCall的UIWeight,UIWeight处理UV数据的组装,保存在自身的UIGeometry节点上,然后把这个Draw的数据刷新到Mesh上面去即采用UIDrawCall.UpdateGemetry。
📣2、消息机制
UICamera脚本:真正做的事情是发送NGUI事件给所有被当前Camera渲染的object,camera是UICamera脚本所在的那个。
其实这个脚本做的事情和UI无关
事实上如果你想让游戏里面的object接收OnPress、OnClick、OnDrag等这类事件,你需要把UICamera挂在你的主相机上。
游戏场景里面可以有多个UICamera,一个挂在渲染widget的相机上,一个挂在渲染游戏的相机上。
📣3、间隔动画Tween
- 间隔动画:对对象的某一个变量,使这个变量在一定时间段内,每一帧按照预先指定曲线变化,从而形成一段动画。
如:屏幕颜色的渐变、屏幕抖动、对象的运动等等。
- UITweener :是实现NGUI中间隔动画的基础。间隔动画的基类,所有间隔组建都继承该类,用于执行update()。所有的继承了都需要从Begin开始。继承类中主要函数是OnUpdate,用于处理每帧的更新处理。在Begin开启后,当前脚本状态会被设为true,在运行结束之后,又会设置为false。
- UIPlayTween:这个脚本管理一组Tween脚本的Play,提供了不同的Tirgger,然后在不同的事件函数中触发Play(true)
2️⃣如何优化NGUI的堆内存分配?🔥🔥🔥
- UIGeometry的verts、uvs、cols、mRtpVerts缓冲池,并保证OnFill时进行按需选择。
🔑当UIWidget.OnFill的时候确定了需要顶点数量的情况下从缓存池申请,当UIWidget.OnDestroy的时候放回缓存池,回收的时候以顶点数量从小到大插入,申请的时候找到第一个满足需求顶点数量的。 - NGUI创建Mesh时顶点索引缓冲的优化,通过unsafe的方法来修改IndexBuffer的长度;
🔑通过Unsafe的方法,我们只需要一个顶点索引缓冲,而不是原来的10个,而且我们只在缓冲不够大的时候从新分配内存并只填充增加了的顶点索引缓冲,而当需要的顶点索引缓冲比当前的小的时候只需要通过Unsafe方法设置下长度就行了。 - 插入前充分设置List.capacity来减少GC Alloc
🔑在UIPanel中,需要对FillAllDrawCalls和FillDrawCall进行优化,收集每个UIDrawCall对应的UIWidget和总的顶点数,然后一次性对UIDrawCall里的几个List一次性设置capacity。
链接: NGUI底层代码的详细完善和优化
3️⃣什么是Drawcall?🔥🔥🔥
游戏中的游戏对象,需要GPU来绘制
Drawcall就是,在整个场景中,CPU分批次提交请求给GPU进行绘制。
如果Drawcall过多,CPU会提交大量的请求,CPU里进行请求的🎉,造成GPU绘制延迟。
GPU绘制非常快,基本不会有卡顿。
CPU:一次提交请求,比多次提交请求,性能更好,节省CPU性能。
GPU:吞吐量,一次性处理多少个三角面。每次提交的三角面数量接近吞吐量,GPU性能更易发挥。
drawcall数量约等于batches批处理次数
4️⃣UGUI如何优化DrawCall?🔥🔥🔥
优化Drawcall的主要手段合批
📣分析场景中UGUI的DrawCall消耗情况
UGUI占用的Drawcall数量,可以通过显示和隐藏UGUI节点,来计算出来!!!通过States面板查看Batches
相同shader,相同texture 的 UI 对象,可以合并到一个Drawcall,这就是合批。
📣如何将GUI的图片打入同一个图集里
开启图集模式:Editor->Editor Settings -> Sprite Packer ->Mode -> Always Enabled
单个图片设置 PackingTag
启动打包生成图集 Window --> Sprite Packer
📣drawcall为什么会被打断
如果不影响场景效果,unity会优化绘制顺序,尽可能减少drawcall
如果影响了场景效果,则不会触发unity调整顺序,drawcall会被打断
- Text组件使用了Font Texture,不同字体来自不同图集。会打断Drawcall,所以要尽可能讲Font Texture生成到一个纹理图集里,才能合批绘制。
- RawImage组件即使同一个图集也不能合并Drawcall,除非是同一个图片。
- OnGUI性能不好,尽可能不去使用。
- UI位置三角面的重叠会打断Drawcall,在Scene视图下线框模式TextureWire观察,调整位置。
5️⃣UGUI如何在CPU性能上进行优化?🔥🔥🔥
1、DrawCall优化
具体DrawCall优化,看上一题
2、Canvas.SendWillRenderCanvases()优化
原理:真机 Profiler 会经常看到这个函数的消耗飙高,研究 UGUI 源码得知该 API 为UI 元素(Graphic 子类)自身发生变化(比如被 Enable、顶点色变化、Text 组件的文本变化等)时所产生的调用,CPU 飙高大部分情况下主要是因UnityEngine.UI.Graphic.UpdateGeometry()重新生成了所有的顶点数据。
UI 顶点数的重灾区主要是在 Text 控件。
Text 在不添加任何效果的情况下是一个字符串是 4 个顶点,单独添加 Shadow 后一个字符串顶点数是 8 个,单独添加 Outline 后一个字符串顶点数是 20 个,Shadow 和 Outline 都加一个字符串顶点数是 40 个
慎重考虑是否使用 Shadow、Outline
Canvas 下的 pixelPerfect
在刷新顶点的时候会做一些额外的操作RectTransformUtility.PixelAdjustRect,像素对齐调整,此操作很费,所以不要勾选 Canvas 下的 pixelPerfect
3、Canvas.BuildBatch()
Canvas 下的顶点数量多和Canvas 下的顶点发生变化,是导致Canvas.BuildBatch()消耗飙高的因素。
- UGUI是以 Canvas 为合批的根节点,首先 Canvas 与 Canvas 之间是没法合批的
- Canvas 下所有节点的顶点数据会生成一张大的 BatchedMesh,再根据材质纹理、渲染顺序等把这个 Canvas 下能合批的 Mesh 生成 SubMesh,一个SubMesh 就是一个绘制批次
- 当这个大的 BatchedMesh 中任意一个顶点数据发生变化的时候,UGUI 目前的实现方式很粗暴,就会直接重新生成BatchedMesh,调用Canvas.BuildBatch()函数
4、具体优化操作
加载和实例化的过程是无法规避CPU消耗的
-
UI动静分离
以canvas为节点,设置动态canvas和静态canvas,实际项目静态元素较多,动态元素较少,动静分离后,CPU在重绘和合并时消耗就会减少。 -
拆分过重的UI
将界面中隐藏的独立界面做一次拆分
对二次显示内容,如部分动效图标,小窗口等做二次拆分。 -
UI预加载
UI实例化到场景中的过程:网格合并,组件初始化,渲染初始化,图片加载,界面逻辑调用等,消耗大量CPU
预加载:把资源加载到内存、UI实例化和UI初始化的CPU消耗放在loading等待时间线上 -
UI图集Alpha分离
主要是针对NGUI方案,Unity内部已经完成了Alpha分离
首先:TexturePacker打图集时候,改成打一张RGB888的PNG图和一张Alpha8的PNG图
其次:修改NGUI的原始着色器,绑定主图和绑定Alpha图
然后:将NGUI的着色器shader中相应修改为新的颜色通道和透明通道
最后:NGUI工具类也要相应修改编辑几个类
最终:主图和Alpha图合成新图片替换原来的图片 -
UI字体拆分
提取常用字体
使用TMP,同样会生成纹理和图集,相比TEXT优势是,TMP是矢量字算法,MESH顶点数少,字体同源,各语言能同屏显示 -
ScrollView优化
不停滚动会导致合批网格重构、渲染裁剪
使用对象池进行优化 -
网格重构优化
-
UI展示与关闭优化
-
对象池运用
当程序中有重复实例化兵不断摧毁的对象时需要使用对象池进行优化
每个需要使用对象池的对象都需要继承对象池的基类对象
销毁操作是通过对象池接口提供的回收接口
场景结束时要及时销毁整个对象池 -
UI贴图设置优化
-
高低端机型画质优化
使用两套UI贴图,高清,低清,两套图,两套Prefab,NGUI和UGUI高清HD和SD切换的流程可以通过编写脚本程序一键搞定。
模型和特效使用不同质量(三角面数)的预制体,预制体命名后缀做加载区分,区分等级
阴影根据使用情况进行区分
整体贴图渲染质量进行区别对待
使用QuailtySetting的API来对阴影和贴图渲染质量做操作
通过程序来区分机型,ios通过机型就能判断UnityEngine.IOS.Device.generation== XXXX.Iphone6;安卓通过CPU型号,内存大小,系统,平均帧率等进行综合判断
-
UI图集拼接优化
充分利用图集空间
图集大小控制1024*1024
图片的拼接归类 -
GC优化
缓存变量
减少逻辑调用
链表清除
对象池
减少字符串创建和操作,使用stringbuilder,日志优化
函数引用,匿名函数闭包
主动调用GC
6️⃣UI资源如何优化?🔥🔥🔥
📣纹理资源优化
- 单个纹理尺寸为2的幂次方,最大尺寸1024*1024(内存优化)
- 纹理加载方式:流式纹理加载Texture Streaming
- 不通过增加纹理大小来增加细节,而是通过增加细节贴图DetailMap或增加高反差保留
- 纹理压缩:可以使用ETC1+Alpha(安卓),ETC2(安卓),PVRTC(ios),ASTC 6x6
ASTC更优,内存大小相同的情况下,纹理效果最好,加载速度最快,包体最小
- 纹理MipMap:逐级减低分辨率来保存纹理副本,相当于纹理LOD
内存变大1//3,通过Mipmap开启可以限制不同平台加载不同level层级的贴图
📣UI纹理图集
- UI图集最大尺寸为1024*1024
- 重复利用的公用资源放common图集(drawcall优化)
- 同一个界面UI资源放一个图集(drawcall优化)
- 使用九宫格来减少原图大小(内存优化)
- 提高图集利用率,原图分辨率需要包含在1024*1024尺寸下,如果原图过大需要进行拆分,放入图集中
- 图集合并提升利用率,如果图集低于1/3 就要合并图集。
7️⃣NGUI有哪些优化方式?🔥🔥🔥
8️⃣你知道面向对象的三大特点吗?🔥🔥🔥
9️⃣你知道面向对象的三大特点吗?🔥🔥🔥
🔟你知道面向对象的三大特点吗?🔥🔥🔥
🎁🌻🌼🌸 粉丝福利来喽 🎁🌻🌼🌸
- 免费领取海量资源 🎁
简历自测评分表、Unity职级技能表、面试题库、入行学习路径等- 《Unity游戏开发五天集训营 》50个名额 🎁
我给大家争取到了 50个《游戏开发五天集训营 》名额,原价198,前50个免费
扫码加入,暗号小听歌
即可参加ARPG狼人战斗系统、饥荒生存类游戏开发、回合制RPG口袋妖怪游戏等游戏开发训练营- 额外抽奖机会🎁
参加游戏训练营、还有机会获得大厂老师在线面试指导、或者有机会获得价值1998元的《Unity极速入门与实战》课程