关于AnimationClip在PC下加载缓慢的问题分析

关于AnimationClip在PC下加载缓慢的问题分析

这是侑虎科技第594篇文章,感谢作者阮班(巨人孵化技术部GIP)供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

作者主页:https://www.zhihu.com/people/hackself,作者也是U Sparkle活动参与者,UWA欢迎更多开发朋友加入U Sparkle开发者计划,这个舞台有你更精彩!


问题描述

项目为了解决动画FBX Mesh冗余的问题,采取了将动画切出来的措施,解决了这个问题的同时,又带来了新的问题。那就是这些动画在PC下加载很慢。


问题原因

这主要和Unity的资源管理机制有关。


问题解释

关于Unity,需要知道的几件事情:

1、所有从UnityEngine.Object继承的对象都是可以被Unity序列化的。

2、Unity的序列化支持多种格式,最常用的是可读性较好的Yaml文本格式。而在打包后,Unity会转成性能更好的二进制格式。Unity提供了二进制序列化文件转文本序列化文件的工具。在Unity的安装目录中能找到,如:D:\Program Files\Unity2018.4.0f1\Editor\Data\Tools\binary2text.exe

3、编辑器中默认的序列化格式是文本格式,便于版本合并。可以通过编辑器设置进行修改。

4、Unity的资源导入操作,实际上就是根据资源源文件,生成Unity对应的Object对象,并序列化到Library目录中去。所有被Unity识别的资源在导入时,Unity都会转成Unity内部对象(Mesh、Texture、AnimationClip…)。

5、Unity的资源加载,实际上就是对这些序列化好的文件进行反序列化操作。

6、Unity将FBX视作Prefab。

7、每个导入的资源文件,Unity都会生成一个GUID,并将这个GUID存放在同名的.meta文件中。如:

8、通过这个GUID可以找到资源文件对应的Unity对象的序列化文件。方法就是:取GUID前两位,如上:“ae”,在工程目录中的“Library\metadata”找到这个名字的目录,进去后就能找到对应的以GUID为文件名的二进制序列化文件。

下面我们先做一个实验,复现这个问题:

1) 首先准备4个带动画的FBX文件,然后将在Project面板中,Ctrl+D挨个将FBX中的动画切出。

2) 制作两个Prefab,分别命名为fbx_anim、std_anim。其中fbx_anim中的Animation组件引用4个FBX中的AnimationClip。而std_anim中的Animation组件引用4个切出的动画文件。

3) 编写测试脚本,启动游戏进行测试。

4) 为了了解二进制格式和文本格式对加载性能的影响,我们对两种格式分别进行了实验,每次实验取了10组加载耗时数据(单位:ms)。对比如下:

可以看到,文本格式情况下,切出来的动画的加载耗时是FBX中的50倍以上,而在二进制格式情况下,平均耗时是FBX中的2倍左右。

问:那么为什么从FBX切除AnimationClip后,加载会如此耗时呢?

1) 我们以上面的anim_1.anim为例。先看切出来后的AnimationClip在Unity的Library中是怎么样的。

执行命令,将anim_1.anim对应的序列化文件转成文本格式:


打开后,可以看到,这个序列化文件并没有保存动画的曲线数据数据。而是指向了“Assets/Animatoins/anim_1.anim”这个动画源文件。

  1. 我们再找到anim_1.fbx对应的序列化文件。



可以看到,里面包含了曲线数据信息,存放的是AnimationClip对象的序列化数据,还有其它的对象序列化数据(Mesh、GameObject…)。

在PC下使用非AssetBundle模式运行,项目中的资源加载接口是使用的Unity的Assetdatabase.LoadAssetAtPath。从上面不难看出,如果AnimationClip在FBX中,那么加载的是序列化好的二进制文件,而以.anim独立形态存在时,Unity是直接加载的这个动画源文件,而不是序列化好的二进制文件。由于Unity的默认序列化格式设置是文本格式,所以会很慢。

问:那么Unity为什么不将切出来的动画存成最终形态的二进制序列化格式呢?

答:那是因为FBX中的AnimationCip是只读的,而独立的.anim文件是可编辑的。如果Unity实时将.anim文件转成最终形态的AnimationClip对象序列化文件,那么编辑体验可能会很差。所以Unity只会在打包的时候将其转成AnimationClip对象序列化文件。

问:那么这个独立形态的动画源文件和最终形态的动画文件又有什么区别呢?

答:我们按照前面介绍的方法,将anim_1.fbx对应的序列化文件找到,并将AnimationClip的序列化数据复制出来放到一个独立的文件中,并命名为anim_1.txt,然后和anim_1.anim做Diff对比。

可以看到,它们之间有一些变量是相同的,但是曲线数据的组织方式已经完全不一样了。动画源文件的数据组织格式对编辑更有利,而最终发布版的AnimationClip对运行时更有利。直接加载源文件是需要一些数据格式转换,这是需要消耗CPU的。

加载独立形态的动画文件会很慢,还有另外一个重要原因,那就是.anim文件在使用Animation编辑器打开并修改保存后,Unity会插入大量的仅在编辑器下使用的数据。


可以发现,使用Animation编辑器编辑后,Unity会插入大量的数据,文件大小差不多是原来的4倍。虽然,这些数据只是在编辑器下使用,在打包后,会被删除掉,但是如果在编辑器下运行游戏,这些数据依然会拖慢动画文件的加载。下面这组实验数据可以验证这个结论。

我们将4个切出的动画全部进行细微地修改后保存,使用上面的测试环境进行一轮测试,得到数据如下:

由实验数据可以看到,使用编辑器打开并保存后的动画文件,在加载时将会更慢。在文本格式情况下,独立形态的动画文件的加载耗时竟是FBX中的150倍以上。而在二进制格式情况下,加载平均耗时差不多是FBX中的3倍。

问:那么如何解决这个问题呢?

答:从上面实验数据不难看出,对.anim动画文件加载速度影响最大的就是序列化格式。

设置二进制格式的动画文件将大幅度缓解这个问题。但是,直接将项目中的序列化格式设置成“Force Binary”是不行的,因为在实际的项目开发中,总会涉及到多人修改同一个资源文件的情况,强行改成二进制格式,合并修改的时候就麻烦了。而Unity并未提供只设置某种资源为二进制格式的选项。Unity提供的另一个选项是“Mixed”,这个选项会保持项目中现有的序列化格式,新创建或导入的资源将使用二进制格式。这似乎也并不能很好地解决这个问题。目前看来,这确实是一个两难的问题,有点无解。

我能想到的办法,就是在本地保存两份相同的工程,一个设置成二进制格式,用于编辑器下开发测试,另一个工程保持和版本库同步。本地开发完成后,将差异文件导入到同步工程中提交。(如果你的项目在编辑器中加载游戏,已经慢到无法忍受的程度了,可以考虑我这个办法。)


总结

动画从FBX中切成独立的.anim文件后,这是一个从只读动画变成可读写动画的过程。

独立的动画文件和最终发布的动画文件数据格式是不一样的,Unity会在打包的时候进行转换。在编辑器中运行游戏,如果使用的是FBX中的动画文件,Unity是直接加载最终发布形态的动画序列化数据,这些数据是在FBX导入到工程中就生成好了的,所以很快。如果使用的是独立的.anim文件,Unity加载的是这个动画源文件,需要进行数据格式转换,而文本格式的序列化文件进行反序列化时,也是极慢的,加上在Animation编辑器编辑并保存动画后,Unity还会插入原有数据量的3倍到这个动画文件中去,这也极大地拖慢了动画文件的加载速度。

工程链接https://pan.baidu.com/s/1CYCNWw2TZLgtKWNUY5AuAA


文末,再次感谢阮班(巨人孵化技术部GIP)的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

也欢迎大家来积极参与U Sparkle开发者计划,简称“US”,代表你和我,代表UWA和开发者在一起!