目录
构建一个简单易用的基于MVC的UI框架的思想?(基于UGUI)
图集
提到图集优化,我想先引出一个经典的游戏优化案例:
《超级马里奥》的Tile压缩方法:
由于红白机的性能相比现在的家用主机来说极为有限,无论是储存器性能还是处理器性能,所以当时的开发者采用“Tile瓦片”(每个瓦片8X8像素大小)作为基本的图片存储单元,然后再把这些瓦片拼接在一起。
如此以来画面分辨率256X240的红白机用8X8瓦片来填满只需要32 * 30 = 960张瓦片,给每个瓦片一个0至255的编号,那么最多只需要960B来表示一个屏幕里的所有图片,并且瓦片的内容是重复的并且个数是有限的,所要求的存储性能以及内存性能都因此大大降低。
当然为了极致的性能压缩性能,那个时代的开发者也应用了不少的技巧:
1.一个角色的行走动画,用一张图片进行左右交替翻转就能实现,减少了存储压力。
2.天上的云地上的草叠加放置,表现大小规模不同。
3.角色的图片只存储一半,用的时候再翻转拼接,又进一步压缩了存储。
这些前人的智慧也对主流的游戏引擎有着深远影响:Sprite Atlas(精灵图集,以下统称SA)
不仅仅局限于以上描述的方面,SA甚至可以应用于2D 和 3D 项目中的 UI、粒子系统、贴图等等。
现在我们对SA进行一个详细的归纳总结:
SA的原理:
是一种将多张小图汇总,打包成为一个大图的技术。在游戏运行时只需要载入一张大图到内存便可以实现多个素材的载入,是一种优化性能的手段,减少多次DrawCall对性能的占用。
SA的优点:
将多个图片合并到一张大图中,减少多次DrawCall对性能的占用。
SA的缺点:
当不经常使用的素材被放到图集中时,即使改素材不被使用也会被载入内存,浪费了内存资源;并且图集的大小固定为长边的二次幂,如果图集内素材的大小差距过大,会直接造成储存的浪费。
前期要求:
首先我们先找到PackageManager,在Unity Registry中下载2D包。不然无法在项目中创建SA内容。
点击“install”,下载这个包
然后找到Project Setting选项>Editor>Sprite Atlas>Mode
Mode默认是Disabled,更改为Always Enabled,V1和V2两个版本目前没有很大的区别,选V1就行
其他两个选项的意思是:Enabled For Builds——在项目导出后应用图集,所以在编辑器里面看到的图集仍然是原来的那些图片。
为了保证项目能接近玩家原生感受选择Always Enabled。
SA的创建:
项目内右键Project>2D>Sprite Atlas创建SA。
这里如果没有前期准备的内容,就无法创建SA。
配置SA:
点击我们新创建的SA
解释一下该面板的几个重要内容的意义:
- Include in Build:是否在游戏发布后构建到游戏中
- Allow Rotation:是否允许旋转图片,如果勾选,在构建图集时可能会旋转对应的图片,建议禁用
- Tight Packing:选中可根据图片的轮廓而非默认矩形轮廓来打包精灵,让图片排列更紧密,建议禁用,否则UI元素在显示时可能会重叠显示其他的图片,因为在获取对应Sprite时是按矩形轮廓来获取的
- Padding:不同UI元素之间的间隔,单位为像素,避免图片之间过近导致显示出问题
- Objects for Packing:需要打包的UI元素,可以放文件夹或者单个图片,放文件夹可以将文件夹所有的图片打包进这个图集中,图片的格式需要设置为Sprite(2D and UI),一个图集最大的尺寸是2048*2048
以上为SA的使用方法以及原理,当然还有其他的图集系统类似于Texture Atlas的内容,不过原理上也大同小异,下面我们深入的了解一下图集优化的内容。
前文提到了DrawCall,这里解释一下DrawCall的相关概念:
DrawCall是什么?
当我们在渲染一个物体时,需要通知GPU执行渲染的指令,这一过程叫做“DrawCall”。而调用DrawCall的次数越多,对GPU和CPU的性能开销就越大,本身电脑的性能就有限,因此我们便要减少DrawCall的次数,减轻对GPU的性能负担。所以开发者们就想到了上述类似Sprite Atlas的方法,以减少性能开销。
批处理是什么?
上面我们了解了Sprite Atlas的运行原理,而批处理便是该原理的名称;由于每一次DrawCall就可以大致理解为一个渲染批次(batch)。Draw call属于资源密集型的指令,图形API要为每个Draw call做大量的工作。造成CPU性能消耗的主要是渲染状态的切换导致的,例如切换到不同的材质,这会导致在图形驱动中产生密集的资源验证和切换。为了减少draw call的调用,Unity引入了两种优化技巧:
- 动态批处理:对于足够小的mesh,动态批处理通过将他们的顶点整合到一个批次中进行绘制。
- 静态批处理:通过将不会移动的静态物体合并到更大的mesh中,以提升渲染速度。
材质方面的要求
只有使用了相同材质的物体才能够实现批处理。如果两个不同的物体,使用的两个材质,只是纹理上的差别,那就把他们的纹理合并到一起,这样就可以使用同一个材质。
UI优化
我们先了解一下UGUI系统的运行原理吧!
UGUI是在3D网格下建立起来的UI系统,它的每个元素都是通过3D模型网格的形式构建起来的。当UI系统被实例化时,首先要做的就是构建网格。【也就是说,Unity在制作一个图元,或者一个按钮,或者一个背景时,都会先构建一个方形网格,再将图片放入网格中。可以理解为构建了一个3D模型,用一个网格绑定一个材质球,材质球里存放要显示的图片。】
那么这里有一个问题界面上成千上万个元素就会拥有成千上万个材质球、图片。如果GPU对每个材质球和网格都进行渲染,将会导致GPU的负担重大,怎么办呢?
UGUI对这种情况进行了优化它
- 将一部分相同类型的图片集合起来合成一张图,
- 然后将拥有相同图片、相同着色器的材质球指向同一个材质球,
- 并且把分散开的模型网格合并起来,
- 这样就生成几个大网格和几个不同图集的材质球,
以及少许整张的图集节省了很多材质球、图片、网格的渲染,UI系统的效率提升了很多,游戏在进行时也顺畅了许多。
这就是图集概念,它把很多张图片放置在一张图集上,使得大量的图片和材质球不需要重复绘制,只要改变模型顶点上的UV和颜色即可。
我们设想一下,如果每时每刻都在移动一个元素,那么UGUI系统就会不停地拆分合并网格,也就会不停地消耗CPU来使得画面保持应有的样子。这些合并和拆分的操作会消耗很多CPU,我们要尽一切可能节省CPU内存尽量把多余的CPU让给核心逻辑。UGUI系统在制作完成后,性能优劣差距很多时候都会出现在这里。
我们要如何想方设法合并更多的元素,减少重构网格的次数,以达到更少的性能开销目的呢?(UI优化)
UI动静分离 | 定义:将会动的UI元素和静止不动的UI元素分离开来,让合并的范围缩小,只合并那些会动的UI元素,因为它们的重绘频率比较高 动静分离后,CPU在重绘和合并时的消耗就大大降低了 那么如何分离它们呢?UGUI系统的是Canvas,NGUI系统的是UIPanel | |
拆分过重的UI | 如何拆分?把隐藏的UI拆分出来,使其成为独立运作的界面,只在需要显示时才调用实例化 | |
UI预加载 | 在游戏开始前或在进入某个场景前预先加载一些UI,让实例化和初始化的消耗在游戏前平均分摊到等待的时间线上 大都使用AssetBundle来加载资源,也有部分使用Unity的本地打包机制(即使用Resources这个API接口)来加载资源 所有的预加载都会引出另一个问题,即CPU集中消耗会带来卡顿现象。预加载并没有消减CPU消耗,只是把这些消耗分离了或者提前了,拆分到了各个时间碎片里,让人感受不到一瞬间有很大的CPU消耗 | |
UI字体拆分 |
| |
网格重构的优化 | UGUI系统的图元素在改变颜色或Alpha后会导致网格重构 UGUI网格合并机制?将拥有相同材质球的网格合并在一起,才能达到最佳效果;一个材质球对应一个图集,只有相同图集内的图片才需要合并在一起 如何优化?使用自定义特殊材质球进行渲染改变颜色和Alpha,这样UGUI就不需要网格重构了 | |
UI展示与关闭的优化 | 利用时间碎片进行预加载在关键时隐藏节点而不是销毁(~……~虽然内存没有变化,但网格重构和组件激活会有大量的CPU消耗)移除节点代替隐藏会更好(~……~移除屏幕后相机会对其进行裁剪判断,因此需要设置UI为不可见的层级Layout) | |
对象池的运用 | 程序中有重复实例化并不断销毁的对象时,每个需要使用对象池的对象都需要继承对象池的基类对象,这样在初始化时可以针对不同的对象重载,区别对待不同类型的对象销毁操作时使用对象池接口进行回收场景结束时要及时销毁整个对象池 |
UI框架
为什么要使用UI框架?
我们在开发项目中UI逻辑的实现是各种各样,需求也是千奇百怪。而且这个实现可能不是一步就位的,可能还需要迭代,重做等。
那么在这种情况下,我作为UI逻辑的开发者,我希望:
不用关注别的业务逻辑在什么地方怎么调用我,只需要通过告诉外界如何调用我
不用因为别人调用不规范导致出bug而头疼
不用因为束手束脚而头疼
不用为想扩展又不想改接口而头疼
因此一个合理的UI框架的设计,不仅能提高程序的开发效率,在应对后续策划五花八门的需求的时候,也能表现的从容不迫。
构建一个简单易用的基于MVC的UI框架的思想?(基于UGUI)
前言:什么是MVC分层开发思想呢?
我们要按照它的功能拆分成三个部分,第一个实现显示的部分,叫做View视图层,第二个实现数据模型部分叫做数据模型层,第三个实现逻辑判断部分叫做Controller控制器层。【注意:要根据项目设置游戏分层的颗粒度】
C:控制器,负责流程控制和事件相应
V:视图,负责图形交互
M:数据模型,负责数据处理
调用关系:用户跟游戏做一些交互,接收用户交互的信息由控制器来完成,控制器接到数据以后,【举例说明:以游戏登录来举例子,登录什么时候跟我交互呢?就是输入用户名、密码点击交互的时候,当然输入用户名和密码的过程也要交互,因为有可能要验证输入是否合法。(根据MVC思想要验证输入是否合法,要写在逻辑判定里)】会将数据交给模型层,之后拿到JSON里数据去校验,校验完成以后来到视图层(判断用户名和密码是否合法会有一个页面显示),视图完成以后会回到最开始的用户部分,用户进行下一步操作,形成闭环。
MVC的开发步骤
1.页面预制体制作
2.处理数据(数据模型脚本)
JSON读写操作
根据控制器调用模型的方式数量,在模型中编写对应数量的函数,以供调用
3.显示(视图脚本)
文本的显示、图片的显示、列表的显示、其他美术资源(模型,动作,特效)
4.逻辑控制(控制器脚本)
生命周期函数、逻辑控制语句
了解了上述MVC分层开发思想,我们将其引入到UI框架的设计中吧!
a.UI界面视图的制作
1.到底把哪些东西做成一个UI视图?
把完成特定功能的UI,做成一个视图.
(主游戏操作UI——>视图)(游戏商城)--->视图,游戏背包--->视图,排行榜--->视图……
按钮做一个视图; --->Button组件;
2.各个视图一定要独立,不要交叉引用;我们要能做到单独适配每个UI
b: Ul 逻辑控制
原则:一个UI视图,我们只能对应一个UI控制的代码:
1:方便接收输入;
不要在UI代码里面,来写游戏逻辑,与UI无关的游戏逻辑;
Ul:Start(){ 游戏开始,get角色,打炮}
派送事件---》触发这个游戏逻辑;
键盘映射--->A--->left Left --->
2:方便显示结果;
【具体的“通用”UI框架,我认为是一个比较复杂的内容<*皱眉*>,我暂时还写不出来它的详细代码框架,上述是我基于MVC思想对UI框架的一小部分理解】
本文成果由CSDN博主“小小数媒成员”与我共同创作