项目背景

项目使用unity做游戏,并使用addressables方案管理资源,基于spine作为很多unity的骨骼动画实现方案,在我们的项目中有一些不合理的地方:

  1. 使用ScriptableObject作为桥接SkeletonDataUnity的方案,导致skel文件在加载完成之后不会卸载,这导致Spine动画很多的情况下会导致很多的Native资源占用;
  2. Spine动画使用的地方很多,但是大部分时候只会播放一个动画;

为此我们实现了两个Spine方案上的改动:

  1. SkeletonData加载完成之后手动卸载skel.bytes文件;
  2. 按照动画名字拆分Spine文件中的动画,按需加载Spine动画文件;

下面是具体实现

spine资源卸载

该功能主要是针对skel文件加载完成之后不回收的问题

SkeletonDataAsset作为桥接SpineUnity的承载类,主要是指定使用的Spine资源,这些资源直接使用unity的引用关系关联,加载SkeletonDataAsset的时候会自动将关联的资源加载进入内存,读取完成之后没有卸载对应的资源,导致部分TextAsset资源开销浪费:
思路很简单,本身SkeletonDataAsset方法都是通过GetSkeletonData方法获得SkeletonData实例,这里面回去调用SkeletonBinary读取数据,这里可以缓存读过的SkeletonData实例,判断首次读取之后手动销毁Skel TextAsset 文件. 这部分不会减少单独加载spine的峰值内存,但是实际运行时能减少很多的native内存.代码如下:

1
2
3
4
5
6
7
8
9
public SkeletonData GetSkeletonData(bool quiet) {
// 加载SkeletonData完成之后手动卸载SkeletonJson资源
// 编辑器情况下不要卸载,不然保存项目的时候会保存资源导致丢失引用
#if !UNITY_EDITOR
Resources.UnloadAsset(skeletonJSON);
skeletonJSON = null;
#endif
return skeletonData;
}

按需加载Spine动画

该部分并不通用,需要你的动画没有手动设置过渡效果以及不会出现动态加载spine的情况

由于项目中基本不会运行时加载Spine组件,而是加载spine组件时候通过代码控制播放的动画,因此劫持了Animation中的Timeline过程,在编辑器里面将Skel 文件拆分成头部和动画部分,懒加载Animations的部分内容,从而降低运行开销.

实现思路

Spine的主要内存开销来自复杂的动画信息和曲线信息,在Spine文件组织结构里面,代码之间隔离性相当好,可以将这部分文件拆出来分别加载,这里仅将对应的Animation部分拆分出来,其他部分应该也是能拆分出来的部分.将动画拆出来并且写入按需读取动画文件的索引表.

具体实现

感想

通用库的开发会有很多的考量,适用性相较于部分的性能可能更是他们考虑的重点,对于很多库进行项目适应的调整是很有必要的。