图形引擎实战:Spine动画性能优化

首先说明,本篇文章不涉及Spine操作与应用层面的内容,仅针对Spine在Unity中的性能优化。并且本次优化的背景是项目需求,该项目的Spine动画特征是:拥有大量Spine组件,但是每个Spine组件中只包含1-2个动画片段(AnimationClip)。所以本篇文章没有针对多动画片段(AnimationClip)的优化内容。

一、Spine2D简介:

Spine 2D 是一款针对游戏开发的 2D 骨骼动画编辑工具。相比于传统的逐帧动画,Spine2D内存占用体积更小,所需美术资源更少等等。Spine2D提供了良好的 Unity 支持,包括导入工具和运行时库,使得 Spine 动画能够轻松地集成到 Unity 项目中。

二、Spine2D在Unity中的性能优化

2.1 Unity中Spine2D存在的问题:

  • Spine动画加载时发生卡顿(掉帧)。
  • Spine相关mono内存过大。

2.2 性能分析工具介绍


上述两点问题是如何发现的?这就离不开性能分析工具了,本章介绍2种常用的分析工具(干货请移步2.3章节)。
(需要注意由于项目是移动端开发,所以实际性能指标,应打包安装到手机后在做性能分析才是正确的。)
2.2.1 Unity Profiler

  1. CPU Usage
    使用Unity的性能分析工具Profiler,查看CPU Usage ①下的时间花费情况以及帧率。在同行右侧观测曲线图来查看相关信息。并且也可以通过下方的“详细信息面板”②中,切换Timeline视图到Hierarchy视图③,并在同行选择“Time ms④”方便查看时间花费情况。在Profiler上方有个“Deep Profile⑤”开关,启用 Deep Profile 设置后,性能分析器会对脚本代码的每个部分进行性能分析,并记录所有函数调用,可以对脚本进行更详细的调查。(Deep Profile只能在unity中才能实现,在移动端用不了)

2. Memory

在Unity Profiler中,选择Memory,在下方的“详细信息面板”中选择Simple中会显示每一帧的内存使用情况,其中就包括Mono内存,其中Used Mono是 实际占用的Mono内存,Reserved Mono是Mono内存池保留量。

2.2.2 Memory Profiler

Memory Profiler在unity2019版本里需要在Package中添加该工具。

该工具可以截取某一帧分析其内存使用情况,比Profiler中的更加详细。运行游戏,点击Capture① 截帧,选中截取的那一帧②,在右侧就可以对该帧的内存占用情况进行分析了。

2.3 处理GC过大,加载资源卡顿的问题

启用 Deep Profile,运行项目,加载Spine时会看到Profiler的CPU Usage右侧有尖峰存在,点击尖峰所在帧,在Hierarchy视图下可以看到该尖峰存在的原因确实是Spine导致的,并且GC也非常大。

2.3.1 导出格式使用Binary格式,放弃使用json格式

在Spine动画导出时,可以选择导出Json格式和Binary格式。在Unity的Profiler中对两种格式的加载做对比,发现cpu耗时达到了1673.88ms,并且产生的GC高达5.8MB,而使用Binary耗时232.56ms,2.5MB的GC。

这是因为JSON 作为文本格式,采用人类可读的字符串来表示数据,其中包含了大量的字符处理和类型转换。这不仅使得数据冗长,还需要通过复杂的解析步骤来识别数据;相比之下,二进制格式直接以机器可读的方式存储数据,减少了解析过程中的计算量和复杂性。所以不论是资源大小还是读取速度,采用Binary才是正确的选择。

当然JSON也是有优势的,易于阅读和编写,非常适合在调整效果的时候使用,但是最终确定效果后,还是要使用Binary格式。

2.3.2 搭建List池子降低GC -- 降低20%左右GC大小

使用二进制的加载方式已经能大幅降低毫秒数和GC了,但GC相对还是较高,通过查看Deep Profiler,可以知道Spine加载时的GC发生在Awake()中,其中主要发生在以下函数中:

  • GetSkeletonData() —> SkeletonDataAsset.ReadSkeletonData ---- 加载Skeleton二进制数据

”加载Skeleton二进制数据” 时,只调用一次,且GC较大(每个角色约2-3.2mb),产生的缓存不会被卸载。

通过Deep Profile得知加载Skeleton二进制数据时产生GC的原因主要有以下两点:

  • new ExposedList<T>()
  • ToArray()

ExposedList是Spine插件里的一个类,其实则就是数组类型,对于需要频繁动态扩容的数据,使用List是更好的选择,可以避免复制数组导致申请更多的内存。所以可以通过创建List池子,来规避这部分重复实例化ExposedList<T>导致GC,并且在ToArray之后Release回池子重复使用:

可以看出来ToArray这部分GC还没有处理掉,这一块内容到第三章细说。

2.3.3 搭建对象池管理Spine对象

在项目的角色展示界面里,用户可能会频繁的切换Spine角色,导致频繁触发GC,通过搭建Spine对象池,在非关键的关卡加载时间点上加载多个实例来预热对象池,来避免频繁触发内存GC。

2.4 处理Mono内存过大的问题

加载、卸载多个Spine角色,在Profile的Memory中看到Mono内存在不断增长,最终维持在一个较高的数值,如下图。

Mono内存过大的原因主要在于Spine加载方式导致的,Spine在第一次读取Skeleton二进制数据后,会缓存数据在mono内存中不被卸载。所以不断的加载、卸载新的spine角色后,mono内存会不断增高 。

2.4.1 卸载spine缓存

可以通过在适当的时机卸载不需要的mono内存,一般来说“卸载缓存”要结合2.3.3的对象池一起管理。比如Spine角色被对象池踢出池子时,卸载Gameobject的同时卸载Spine缓存:

卸载缓存时可以通过Memory Profiler查看是否卸载干净,如果没有,再查找原因。方法如下:

截取加载Spine角色之前和卸载Spine角色之后的两帧,然后点击Compare Snapshots,选中这两帧,再右侧的Objects and Allocations中对Diff和Data Type打组(Group),再Type上搜索Spine,查看是否有New in B,如果New in B里存在Spine相关内容,说明没有卸载干净,需要查找相关原因。

还有一个问题需要解决:

假设有两个Spine角色,卸载掉其中一个缓存会导致另一个角色报错SkeletonDataAsset引用缺失,所以需要增加一个单例类,用来统计SkeletonDataAsset的引用次数,当引用次数归零后再调用卸载缓存函数(下面是一个简单的单例引用计数脚本):

三、拓展

在2.3.2章节提过SkeletonBinary.ReadVertices()函数里使用ToArray拷贝了一份给Spine使用,拷贝的过程也会有GC产生。为了解决这一问题,可以修改Vertices里的声明vertices和bones的类型,由数组类型改为List类型,并且通过方法返回值引导,把所有用到该变量的数据都修改成List类型(很麻烦,需要改的数据较多),这样改后,池子分配的内存会被一直引用,所以不能在本函数内Release回池子,需要结合2.3.2章节中卸载Spine缓存的时候再把内存Release返回给池子。

这样做会让List池子中的内存一直被Spine握着,直到该Spine缓存被卸载,导致前几个Spine角色加载时需要重新在List池中申请内存。为了解决这个问题,要在主界面加载时,提前让池子提前申请好足够数量内存块。

由于每个Spine角色的数据大小不一致,可能会导致频繁执行Set_Capacity(该函数是list扩容时的函数)产生GC。为了避免这个情况,还需要主动申请足够的内存大小。

具体申请多大内存,这主要就看池子里的申请策略了,过大的内存申请会造成内存浪费,过小的内存申请会触发Set_Capacity导致GC增加。

不过该方案会破坏原有的spine代码架,并且池子内存申请也需要一定量的扩容,所以不利于项目中spine动画的稳定性,故未采取该策略。希望以后spine官方能优化相关内容。

欢迎加入我们!

感兴趣的同学可以投递简历至: CYouEngine@cyou-inc.com

  • 18
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Unity Spine是一种专门用于游戏开发的骨骼动画工具。为了提高性能,以下是一些可以优化Unity Spine性能的方法。 首先,合理使用骨骼动画的复杂度。骨骼动画的复杂程度会直接影响性能。因此,需要谨慎设置骨骼数量和使用的骨骼动画的复杂度,以免过多的骨骼导致性能下降。 其次,减少渲染批次。在使用Unity Spine时,会生成一些渲染批次,过多的渲染批次会导致性能下降。为了减少渲染批次,可以合并相同材质和层级的骨骼动画,或者使用Sprite Atlas进行合并。 另外,注意资源管理。加载过多的骨骼动画资源可能会导致性能问题。可以在游戏中使用异步加载来减少资源加载时的卡顿现象。另外,需要在游戏播放过程中及时释放不再使用的资源,避免资源的过度占用。 优化渲染性能也是重要的一点。可以使用动态批处理来减少Draw Call的数量,或者使用静态批处理来合并静态物体的渲染。此外,通过限制每帧的更新频率和动画播放速度,也可以减少性能的消耗。 最后,进行测试和优化。在进行性能优化之前,需要进行测试,找出存在性能问题的地方。可以使用Unity Profiler或外部性能测试工具来进行分析。通过定位性能瓶颈,有针对性地进行优化,以提高游戏的运行效果。 总之,通过合理设置骨骼动画的复杂度、减少渲染批次、注意资源管理、优化渲染性能以及进行测试和优化,可以有效地提升Unity Spine的性能。同时,开发者还应根据具体项目需求灵活选择合适的优化方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搜狐畅游引擎部

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值