Unity3d插件SmoothMoves加载速度优化

我们游戏是使用Unity3d做的2D游戏,角色特效等都使用SmoothMoves来制作(在国内估计也算奇葩一朵吧,据说燃烧的蔬菜也是SmoothMoves作的),游戏中的所有的资源--角色、特效、技能ICON、角色ICON、音效等几乎都使用assetbundles来实现。

问题:加载一场战斗的时间大概要30s左右!!!

解决方案关键字依赖打包、数据块共享、冗余数据剔除

优化后:5s左右 :)

1. 依赖打包

  1.1 使用AssetDatabase.GetDependencies()接口可以查看资源的依赖引用情况,利用这些依赖信息,就可以设计如何规划依赖打包了

PS: 曾猜测unity是在meta文件存储了资源间的依赖关系,结果在meta中没有找到什么痕迹... 有了解的兄弟请分享~

PS: GetDependencies 对prefab不起作用?我的打开方式不对?

  1.2 依赖打包指的是使用BuildPipeline.BuildAssetbundle()打包资源时,使用BuildPipeline.PushAssetDependencies() 和 PopAssetDependencies()两接口,将资源间共享的引用资源抽离,避免重复资源。比如A、B两资源都引用了C资源,如果不使用依赖打包,A、B对应生成的assetbundle中都会有C资源的拷贝,在内存中也就有两份C资源,这样既增大了资源包,也浪费了宝贵的内存空间。于是,Push/Pop组合就可以派上用场了。

 // 打包示例1 1 Push
 2     BuildAssetbundle C
 3
 4     Push
 5         BuildAssetbundle A
 6     Pop
 7
 8     Push
 9         BuildAssetbundle B
10     Pop
11 Pop

这样会得到3个assetbundle文件:A、B、C,在加载时由于依赖关系,一定要先加载C,才可以加载A或者B。而A与B间则没有任何其他的依赖关系,先加载哪个无所谓。

对示例打包方式略加修改:

// 打包示例21 Push
2     BuildAssetbundle C
3
4     Push
5         BuildAssetbundle A
6         BuildAssetbundle B
7     Pop
8 Pop

或者再干脆点

// 打包示例31 Push
2     BuildAssetbundle C
3     BuildAssetbundle A
4     BuildAssetbundle B
5 Pop

这两种打包方式,A和B仍然依赖于C,最后一种B同时还潜在的依赖于A。如果A B间除了共同引用了C资源之外,还有其他共同的依赖项D(善用AssetDatabase.GetDependencies),则在加载B之前还必须要先加载A。所以推荐使用第1种打包方式,以避免类似情况的发生。

还是上面ABC的例子,如果A、B两文件本身没有更新,而C有修改,此时,可以只重新打包C,无须重新打包A或B,但一定要使用BuildAssetBundleOptions.DeterministicAssetBundle选项。

另外,依赖关系本身我们使用ScriptableObject来存储,当然也可以考虑使用XML等其它方式。

 1.3 SmoothMoves动画打包

前面说到我们使用了SmoothMoves来制作2D动画:

a. 由于动画文件间会交叉引用atlas文件,为避免atlas的重复,将所有的atlas都由动画文件中抽离,单独打包,然后再打包动画文件。

b. 所有atlas都引用同样的Shader,使用Profiler可以看到每个atlas的shader都要解析一次,为避免shader的重复解析,shader也抽离后单独打包

c. 每个动画文件都挂载有BoneAnimation脚本,其atlas信息则由TextureAtlas来存储,这两个脚本都在SmoothMoves_Runtime.dll中。实测中发现,DLL文件的依赖打包一定要小心处理。我们的方法是建立一个无用的TextureAtlas(仅仅为引用SmoothMoves_Runtime.dll),而所有的动画文件和相应的atlas文件都依赖于此进行打包。

大致打包流程如下:

 1 // SmoothMoves动画打包示例
 2 BuildSMAnimation()
 3 {
 4     Push
 5         BuildAssetbundle shared_shader
 6
 7         Push
 8             BuildAssetbundle SmoothMoves_Runtime.dll
 9
10             Push
11                 foreach atlas do
12                      BuildAssetbundle atlas
13                 end
14
15                 foreach sm_animation do
16                     Push
17                         BuildAssetbundle sm_animation
18                     Pop
19                 end
20
21             Pop
22
23         Pop
24
25     Pop
26 }

如上所示,可以在打包的同时生成依赖关系配置,并在游戏初始化时,首先读取该依赖关系,然后是shared_shader、SmoothMoves_Runtime.dll,并且将两者常驻内存,即不对其assetbundle执行unload操作,因为任何的动画文件加载时对BoneAnimation脚本的处理都依赖于SmoothMoves_Runtime.dll,任何atlas加载时其shader都依赖于share_shader。

2. SmoothMoves.BoneAnimation 中的大数据块共享

使用依赖打包后,加载速度有提升,但依旧需要近20s!!! 继续使用Profiler查看分析,最终确定是动画文件挂载的脚本BoneAnimation中的有大量数据,引起了大量GC Alloc。而且在动画实例化时,BoneAnimation也会进行深度复制,进一步测试后确定是BoneAnimation中的triggerFrames和mAnimationClips两成员变量占用绝大多数内存(在较大的动画文件中,这两项竟占到1MB)。阅读SmoothMoves_Runtime的源代码后,确定游戏中这两个成员变量是只读的(事实上triggerFrames会有写操作,只是我们的游戏不会用到),所以决定将这两项数据抽离BoneAnimation,并保存在SMAnimationData(自定义的ScriptableObject) 中单独加载,并在需要的时候为BoneAnimation添加引用。这样保证了同一动画文件的各实例化对象共享同一份数据,减少了GC Alloc的次数,同时也减少了内存占用。如下:

 1 // 抽离BoneAnimation.triggerFrames 和 mAnimationCips
 2 sm_animation_data = ScriptableObject createInstance of SMAnimationData
 3
 4 sm_animation_data.triggerFrames = bone_animation.triggerFrames
 5 sm_animation_data.mAnimationClips = bone_animation.mAnimationClips
 6
 7 bone_animation.triggerFrames = null
 8 bone_animation.mAnimationClips = null
 9
10 BuildSMAnimation
11
12 BuildAssetbundle sm_animation_data

虽然已经减少了实例化SmoothMoves动画时的大数据块复制带来的GC Alloc,但由于SMAnimationData中的triggerFrames 和 mAnimationClips两大数据,加载时的大量GC Alloc依旧不可避免。继续想办法压缩这两个大数据块。

3. 优化BoneAnimation.triggerFrames

    a. 出发点

    BoneAnimation.triggerFrames 是以clipIndex & frame 为键值的TriggerFrame数组,每个TriggerFrame都存储了相应clip的相应frame上所有骨骼关键帧的信息,即TriggerFrameBone列表(TriggerFrame.triggerFrameBones)。详细分析后发现,这些TriggerFrameBone列表间或列表内部,存在大量属性完全一致的TriggerFrameBone。以此为出发点,建立一个无重复的TriggerFrameBone集合,并让每个TriggerFrame中的TriggerFrameBone列表都引用该集合中的元素。

    b. 建立TriggerFrameBone集合和索引表

        b.1 在SMAnimationData中添加新成员List<TriggerFrameBone> triggerFrameBoneSet,作为TriggerFrameBone集合使用;

b.2 在TriggerFrame中添加新成员List<int> triggerFrameBoneIndexes,存储该TriggerFrame中原有的triggerFrameBones列表在SMAnimationData.triggerFrameBoneSet中的索引;(事实上项目中使用的List<byte>类型,就是这么抠门...)

这样在SMAnimationData.triggerFrames赋值后就可以建立SMAnimationData.triggerFrameBoneSet 和各个 TriggerFrame.triggerFrameBoneIndexes了:

 1   // 建立TriggerFrameBone 集合
 2   BuildTriggerFrameBoneSet
 3   {
 4        foreach tf in sm_animation_data.triggerFrames do
 5            foreach tfb in tf.triggerFrameBones do
 6                //此处遍历整个列表是否有属性完全一致的TriggerFrameBone,即是说需要一个对比两个TriggerFrameBone是否一致的接口
 7                if sm_animation_data.triggerFrameBoneSet not contains one TriggerFrameBone equaling tfb
 8                    sm_animation_data.triggerFrameBoneSet.Add(tfb)
 9                endif
10
11                tf.triggerFrameBoneIndexes.Add(sm_animation_data.triggerFrameBoneSet.IndexOf(tfb))
12            end
13
14            tf.triggerFrameBones.Clear()
15        end
16   } 

如此,在对SMAnimationData进行序列化时,TriggerFrame.triggerFrameBones是没有内容的,而所有的TriggerFrameBone都在triggerFrameBoneSet中。实际效果表明,TriggerFrameBone重复率非常高,其数量可减少90%以上。

相应的在加载SMAnimationData完成时,要根据TriggerFrame中的triggerFrameBoneIndexes重建triggerFrameBones列表。

 

4. 优化BoneAnimation.mAnimationClips

BoneAnimation.mAnimationClips是以clip name为键值的AnimationClipSM_Lite数组,每个AnimationClipSM_Lite存储了该clip所有骨骼的颜色信息,即bones列表

    a. 删除空的AnimationClipBone_Lite

        AnimationClipSM_Lite.bones 列表中上存在大量没有实际意义的元素,即AnimationClipBone_Lite中的颜色信息为空,也就是下面的函数返回为true。

 1         // 判断AnimationClipBone_Lite是否为空
 2         public bool IsEmpty()
 3         {
 4             if (colorACurveSerialized.keyframes.Count > 0
 5                 || colorRCurveSerialized.keyframes.Count > 0
 6                 || colorGCurveSerialized.keyframes.Count > 0
 7                 || colorBCurveSerialized.keyframes.Count > 0
 8                 || colorBlendWeightCurveSerialized.keyframes.Count > 0)
 9             {
10                 return false;
11             }
12             else
13             {
14                 return true;
15             }
16         }

在AnimationClipSM_Lite的构造函数中加入AnimationClipBone_Lite是否空的判断,如果为空则不添加到bones列表中。如此,还要调整原本对bones的访问:原代码中都使用boneDataIndex来对bones进行读取,即boneDataIndex即是bones列表的索引下标。将缩减后的bones列表要转换为以boneDataIndex为键值的映射表,如下

 1         public void BuildBoneDict()
 2         {
 3             m_bone_dict = new Dictionary<int, AnimationClipBone_Lite>();
 4
 5             if (bones != null)
 6             {
 7                 for (int i = 0; i < bones.Count; i++)
 8                 {
 9                     AnimationClipBone_Lite each_bone = bones[i];
10                     m_bone_dict.Add(each_bone.boneDataIndex, each_bone);
11                 }
12             }
13         }

代码中所有对bones的访问,都改用m_bone_dict,而bones列表仅起到序列化/反序列化的作用。

    b. 删除AnimationClipBone_Lite中的无用帧

        AnimationClipBone_Lite中的存储是该骨骼的颜色变化曲线,以下两种情况可以优化:

b.1 当颜色变化为一条直线,那除了直线两端的点以外,其它点都是多余的

b.2 当颜色变化仅有一个点,且其blendWeight值为0时,该点是多余的

Unity3d插件SmoothMoves加载速度优化,布布扣,bubuko.com

时间: 2024-12-28 13:29:24

Unity3d插件SmoothMoves加载速度优化的相关文章

页面加载速度优化的12个建议

Radware发布的2014年春季电商页面速度与Web性能”调查报告强调了电商页面加载速度的重要性,同时指出很多网站都没有利用最佳的页面优化技术,页面加速速度都存在很大缺陷.那么该如何补救,提高网站页面的加载速度呢? 报告给出了12个页面加载速度优化的补救措施,用以改善加载时间,改善站长浏览者的用户体验.网站运营人员可以通过这些建议来解决页面加载速度难题.编译如下: 1.合并Js文件和CSS 将JS代码和CSS样式分别合并到一个共享的文件,这样不仅能简化代码,而且在执行JS文件的时候,如果JS文

页面加载速度优化

1.合并Js文件和CSS 将JS代码和CSS样式分别合并到一个共享的文件,这样不仅能简化代码,而且在执行JS文件的时候,如果JS文件比较多,就需要进行多次“Get”请求,延长加载速度,将JS文件合并在一起后,自然就减少了Get请求次数,提高了加载速度. 2.Sprites图片技术 Spriting是一种网页图片应用处理方式,它是将一个页面涉及到的所有零星图片都包含到一张大图中去,然后利用CSS技术展现出来.这样一来,当访问该页面时,载入的图片就不会像以前那样一幅一幅地慢慢显示出来了,可以减少了整

网站加载速度优化的14个技巧

下面我将介绍几个优化网站加载网页速度的简单方法,一起来看一下. 1.服务器响应时间 即使网站已经格外优化,但是除非服务器响应时间非常快,否则就不会有什么大的效果.当涉及到提高网站的速度,服务器响应时间起着重要的作用.下面是一些提高服务器响应时间的小贴士. 有独立的服务器,而不是选择共享/托管服务器. 提高Web服务器的质量. 移除不必要的插件,只有那些必要的插件,才需要一直保持启用状态. 2.浏览器缓存 浏览器缓存可以减少HTTP请求,从而反过来提高网站的加载速度.下面就是如何利用浏览器缓存的代

网页加载速度优化4--图片懒加载

当前网页设计理念主要以大气简洁主流(文艺小清新网站例外).更多的信息用图片来展示. 由于图片都是高清,所以会影响到网页加载速度.这里我们就用到图片的懒加载(延迟加载)功能. 最快速解决方案:jquery lazyload插件. Lazy Load 是一个用 JavaScript 编写的 jQuery 插件. 它可以延迟加载长页面中的图片. 在浏览器可视区域外的图片不会被载入, 直到用户将页面滚动到它们所在的位置. 这与图片预加载的处理方式正好是相反的. 在包含很多大图片长页面中延迟加载图片可以加

android加载速度优化,通过项目的优化过程分析

通过这么长时间的盒子开发以及之前手机项目的经验,总体感觉两种不同设备还是有很多不同的地方的,首先一点不同的就是,手机项目和电视项目默认启动页面加载速度有重要区别 对于手机:手机加载网络数据,由于屏幕小,如果主页有网络图片的情况下,基本都是显示默认图片,这也是由于网速的限制,更重要的是手机上基本是图文混排,用户没看到图片可能焦点就在文本上了. 对于电视:如果应用首页加载使用默认图,会感觉特别丑,因为屏幕大,重要信息都是图片,如果没有图片,那用户看到的都是空白,用户的焦点没有了,只有等待和抱怨. 因

asp.net中TreeView的大数据加载速度优化

由于数据量太大,加载树时间很长,所以进行了优化 前台 .aspx <asp:Panel ID="Panel2" runat="server" Height="600px" ScrollBars="Auto"> <asp:TreeView ID="TreeView1" runat="server" ForeColor="Black" OnTreeNod

前端页面加载速度优化---Ngnix之GZIP压缩

gzip on; #开启Gzip gzip_static on;#是否开启gzip静态资源 #nginx对于静态文件的处理模块,该模块可以读取预先压缩的gz文件,这样可以减少每次请求进行gzip压缩的CPU资源消耗.该模块启用后,nginx首先检查是否存在请求静态文件的gz结尾的文件,如果有则直接返回该gz文件内容.为了要兼容不支持gzip的浏览器,启用gzip_static模块就必须同时保留原始静态文件和gz文件.这样的话,在有大量静态文件的情况下,将会大大增加磁盘空间.我们可以利用nginx

优化网站加载速度的14个技巧

本文为转载,原作者版权声明在最下方.个人觉得总结的很好 优化了加载速度的网站不仅可以提高其搜索引擎的排名,同时也可以降低网站的跳出率,提高其转换率,还能提供更好的终端用户体验,这是当今基于Web环境取得成功的关键. 下面我将介绍几个优化网站加载网页速度的简单方法,一起来看一下. 1.服务器响应时间 即使网站已经格外优化,但是除非服务器响应时间非常快,否则就不会有什么大的效果.当涉及到提高网站的速度,服务器响应时间起着重要的作用.下面是一些提高服务器响应时间的小贴士. ●有独立的服务器,而不是选择

优化页面加载速度的方法

1. 优化图像 图像对于吸引访客的关注是很重要的.但是你添加到页面上的每一张图片都需要用户从你的服务器下载到他们的电脑上.这无疑增加了页面的加载时间,因此很可能让用户离开你的网站.所以,优化图像是非常必要的. 过大的图像需要的下载时间更多,因此要确保图像尽可能的小.可以使用图像处理工具如PS来减小颜色深度.剪切图像到合适的尺寸等. 2. 去掉不必要的插件 一个非常值得关注但经常被忽略的因素是你网站安装的插件.如今,大量免费的插件诱导网站开发者添加很多不必要的功能.您安装的每个插件都需要服务器处理