前言
在项目开发中,经常会用到UI动画效果,因此做了一套UI动效工具,能够满足大部分使用,该工具底层采用协程驱动动画播放,带来的后果是每一个UI想要有动画都得需要挂一个动画组件,而每个动画组件运行时都至少会开启一次协程去驱动动画效果播放,如果有需求要同时在一个页面使用很多的item并且每一个item都有独立动画,那就会造成较大的性能消耗。本篇文章记录如何解决以上的问题。
主菜
设计思路
原有的设计思路围绕oop原则,某个对象需要这个效果就带一个对应效果的组件为其服务,思路如下:
- 设计一个模板类,核心功能是具备一个计时器能够指定时长去驱动动画。
- 提供一个数学缓动函数库,输入[0-1]之间的播放进度值,返回对应的系数值。
- 实现4种不同属性的动画行为(旋转、缩放、位移、透明度),每一个以子类的方式实现(可扩展)。
- 根据需要挂载不同类型的动画效果,可复合使用
优点:使用极其方便,直接挂在对象上,设置自动播放、播放时长、效果等等,也可以代码控制。
缺点:当场景中使用较多动画对象时,会变得卡顿。
极端测试
通过代码创建10000个带旋转动画并设置循环播放的UI对象后变得极为卡顿:
public void Create() {
for (int i = 0; i < 10000; i++)
{
var e = Instantiate(templateMethod, root);
e.isLoop = true;
e.Play();
easyAnimationTemplateMethods.Add(e);
}
}
协程模式下大量动画
(这里由于图片渲染的关系,其实帧率降低与大量协程并不是直接因素)
在协程模式下驱动器每一帧都会执行,打开Profiler查看更是夸张。。。
prodiler下的性能监视
可以看到,所有协程每一秒都在疯狂的产生GC, GC Alloc达到了195.3kb,如此恐怖的数值,一般来说手机上几k都会造成明显卡顿了,更不用说上百。
协程虽然方便,但这就是一个典型的反面教材。
解决思路
将原来的每一辆车都有一个发动机思路做一个大胆的改动
一个超级发动机驱动多台车辆!!!
这就让人懵逼了>...< 我一台发动机怎么给多个车用,简直疯狂。
在我第一次接触ECS设计模式的时候就是这样的,不能理解!!!
其实在ECS的世界中上面的比喻有一点问题,需要改一下:
ECS里面应该是汽车需要能源驱动,我现在有一个能源系统,能源系统会给所有能够接受能源的机器源源不断的提供能源,不管是车辆还是工厂里面的机器,如何运转在于机器自己如何使用这个能源。
那再来看看原来的模式下在ecs中是怎样的:
每一辆车都装了一个能源系统,将产生的能源驱动给自己的发动机中然后开起来了,这样就导致每一辆车的成本实在太高了。。。。而且过于浪费
在Unity里我的能源系统不能方便的找到世界中所有需要能源的机器,于是需要改动一下,改成需要能源的机器主动找能源系统登记。
以上是自己的一个简单比喻,现在大致找到原因是车辆成本太贵,因此只需要去掉原有的第一步,并改为:
- 创建一个能源系统,有一个登记处,所有需要服务的机器都过来登记,到时候按照登记表提供能源
以上是改进的核心思路,总结一下就是由原来的各自独立驱动器改为一个全局驱动器驱动所有动画,代码不详细提供。
直接看Profiler运行的监视信息:
全局驱动模式
可以直观的看到GC带来的性能消耗显著降低,由之前的195.3kb 降低到了 20b,帧率也由10几帧提高到了接近30帧左右(当然前面说过真正严重影响帧率的是渲染部分导致),仅gc的优化带来的帧率提升是非常客观的了。
在进一步优化:由于驱动器只需要每一帧去调用不需要严格的时间等待,因此可以抛弃协程,改为一个全局的Update,可以进一步优化性能:
Update全局驱动模式
如此一来动画系统直接没有GC产生了,动画系统的性能问题宣告解决!
最后的改进: 每一辆车需要用的时候都要去登记一次,显得有些麻烦,因此保留了原有的自驱动模式,通过判断是否使用公共驱动来动态决定用那一套,这样也满足在开发过程中有一些简单页面依旧可以像原来的方式需要什么效果直接挂组件就行了。
最后的最后
以上的主角是自己的一个UI动效小插件EasyAnimation,已经开源在我的git上,希望支持star一波:EasyAnimation
网友评论