Unity AssetBundle打包与资源更新

Unity的AssetBundle打包是一件让人头疼的事情,当我接手这项工作时,我以为最多只用两个周就可以把整个打包和资源热更新的流程搞定,结果还是花了一个月,期间踩坑无数,总结出来希望能够节约别人的时间。

(一)你的游戏项目是什么类型的?

在开始写打包的Editor脚本之前,你最好先详细考察一下你们的游戏项目是什么类型?是端游,手游还是页游?因为这三者涉及到bundle包的资源管理策略截然不同,如果你们是跨平台发布,那我建议你最好用宏来切换管理策略。

我先分享一下我曾经接手过打包工作的两个项目的情况。

第一个是从AS页游移植成Unity微端的项目,也就是PC的应用环境,assetbundle包的管理比较轻松,内存基本上随便用。这种情况下,我们采用的方式就是WWW从网上下载打好的AssetBundle包,然后使用CreateFromMemory直接从内存镜像构建,同时后台线程把这些资源写回硬盘。

第二个是一款横版过关的重度手游,资源量比较大。在全部资源放Resource的情况下编出来的android版本接近300M

(二)采用什么样的bundle包加载策略?

AssetBundle加载有以下几种方式:

     (1)CreateFromMemory/CreateFromMemoryImmediate

     这种方式直接从内存构建,可同步可异步,可以先通过C#的IO函数从磁盘加载进内存,再用这个API构建AssetBundle内存镜像,占用内存大。不仅有构建出来的AssetBundle内存镜像,还有用来构建的bundle包的那部分托管堆内存byte[],要等待垃圾回收。

同步构建速度比较快,异步构建的速度非常慢,但是多个bundle包一起异步构建在Unity底层有优化,测试要快过一个个的构建。

      (2)WWW加载

这种方式为异步加载到内存,多个www对象有多线程优化。相比CreateFromMemory少掉了托管堆那部分内存。

      (3)WWW.LoadFromCacheOrDownload 

     这种方式占用内存小,是因为Unity会在硬盘上开辟一块空间,用于缓存解压后(时间主要浪费在解压这一步)的AssetBundle,然后再从这块硬盘缓存上构建AssetBundle包,这种方式占用内存较小,因为构建出来的AssetBundle包主要是对磁盘文件的引用,只有在实例化的时候才会分配资源占用的内存。但是磁盘缓存有上限的,超过了上限之后仍然会变成普通的www全部加载到内存。而且你要有个版本号文件管理传入的version参数,否则有可能加载到老的assetbundle。

(4)CreateFromFile

直接从硬盘构建,也是只构建引用,所以速度快且AssetBundle包本身占用内存最小。推荐这种方式,因为同步的代码比较好写,尤其是对于项目后期才引用bundle包机制的,把以前的所有资源加载都改成异步的逻辑工作量太大。

(三)从构建好的assetbundle里load资源

AssetBundle.Load/AssetBundle.LoadAssetAsync

在PC上纹理的上传就发生在这一步。我测试过一个1024*1024的纹理上传所花费的时间往往10倍于512*512的上传时间,所以减小纹理大小才是性价比最高的优化。对于2d mmorpg经常使用的大图2048*2048,如果你使用同步load一个多帧动画,可以明显的感觉到卡一下。如果使用异步则完全不掉帧,估计Unity是采用sub-image的方式一次锁定一小块区域的纹理上传显卡,但是比较慢,且没有方法调整哪个参数来加速这个步骤。像大型2d微端这种需要在场景上实时加载很多大图的效果不能令人满意,可以采用切图的方式来优化。

还有unity对象的构建花费的时间也很长,很多游戏过关卡时间太长,主要就是prefab的构建和实例化。可以采用pool manager的方式将实例化出来的对象保存起来,过关卡时只卸载其占用内存较大的纹理音效等资源,下次需要时再加回来。这样可以大大减少过关卡的时间,但是这种方式却会给assetbundle的管理带来一些麻烦,我会在后面bundle包卸载那里提到。

(四)Assetbundle打包

(1)依赖打包

最头痛的就是这一步了,你要考虑怎样处理资源间的依赖,以避免产生资源冗余。Unity提供了PushDependencies和PopDependencies来处理依赖包的共享资源问题,例如你有如下依赖关系

(A,B)->C->D

则打包脚本为

push

    build D

    push

           build C

           push

                  build A

                  build B

            pop

     pop

pop

这是一个栈结构,后入栈的资源如果有包含先入栈的资源,则不会重复打包进去,而是依赖于这个包。加载时你要确保先加载被依赖的包,再加载最后的包才不会出错。但是被依赖的包是可以不分先后乱序加载的,如果你使用www加载,可以考虑几个www一起加。

还要你要搞清楚pushDependencies/popDependencies打包时设置的依赖关系和加载时的依赖关系其时是两码事,这也是一开始困惑我的地方。比如你有如下的依赖结构

A->(B1 B2 B3 B4)->C

D->(B3,B4,B5,B6)->G

则你的打包脚本应该是这样的

           push

                   build C,build G

                    push

                              build B1,B2,B3,B4,B5,B6

                               push

                                         build A,D

                                 pop

                      pop

                pop

看起来好像A和D都依赖于B1-B6了,其实不然,这样打包出来A包和D包还是只会依赖于包含相同资源的那些包,比如加载D包的时候你也只需要加载B3-B4    只要你打包的参数设置正确,当B1,B2变动时,走这个流程打包出来的D包二进制仍然没有变化的。

(2) 打包时的参数设置

BuildAssetBundleOptions.DeterministicAssetBundle 

设置了这个参数每次打包出来的包才能确保二进制不变,只要被依赖的包不变,打包的流程不变。所以要做资源更新,这个参数不可少,否则在资源不变化的情况下重复打包出来的MD5都不一样,怎么确保更新功能的正常?

BuildAssetBundleOptions.CollectDependencies

这个参数用来收集所有依赖的包,虽然我们会手动收集依赖关系用于push/pop dependencies,但是仍然需要加上这个参数,因为你不会把一个包依赖的所有资源都收集完,你只会先push几个它依赖的资源,然后再用collectDependencies打最后这个包,确保这个包依赖的所有资源都打进去了。

BuildAssetBundleOptions.CompleteAssets

强制包含整个资源

BuildAssetBundleOptions.UncompressedAssetBundle

采用不压缩的方式打包一个bundle包

我们打包的时候这四个参数都用了,只有最后一个参数视情况而定。

(3)收集依赖关系

打包前先使用AssetDatabase.CollectDependencies遍历所有资源收集他们间的依赖关系,在后面打包的时候按照每个资源被依赖的深度进行分级,先打包级别较低的,如shader,script这些资源被其他资源依赖但不会依赖别的资源,级别最低。如prefab依赖前面的所有资源,级别最高,放在最后打包。一般是按照资源的类型(prefab,mesh,animator,texture,script…)进行分级。即使这样按类型分好级后仍是不够的,因为同一级的资源也有可能产生相互依赖的关系。比如使用NGUI,一个面板prefab依赖于几个挂UIAtlas的prefab,这种同级的依赖需要用深度优先遍历对他们进行排序以确定依赖关系。这个依赖关系使用序列化文件记录下来,供后面加载包的时候先加载所有被依赖的包使用。每次更新的时候这个依赖关系的序列化文件也要同其他资源一起更新。

(4)打包时可能遇到的一些问题

如果你使用www.LoadFromCacheOrDownload 请在调试的时候游戏开始时调用一次ClearCache。即使你的代码有动态更新LoadCache时传入的version参数的机制,调试的时候还是要谨慎,如果BUG导致你传的version跟上次一样,相互依赖的包缓存的版本不匹配,就可能引起一些稀奇古怪的问题。

检查你的打包流程所记录的依赖结构是否稳定。这里的稳定是指,在CollectDependencies的时候有没有处理到的被依赖的资源,有可能在打包同级资源的时候出现相互吃资源的情况。比如

A->(B c)->D                     

E->(F c)->G

打包脚本

push

     build D,G

           push

                    build B,F

                    push

                             build A,E

                     pop

      

在收集依赖关系的时候,c是我们忽视的资源,打包时B和F放在同一级打包,A和E在同一级,由于使用了CollectDependencies,A包会把c给收进去,但是由于B包在同一级跟A一起打的,就会出现c打进A了就不再打进B了,但你加载B的时候又没有加载A,所以B就工作不正常。

排查这个BUG的方式就是先打一两个角色或面板,备份,再打全部资源。把两份资源用二进制工具做比较(推荐BeyondCompare,可以对比目录),如果有不稳定的结构立马就能发现。

还有texture的宽高请使用2的倍数,我在测试不标准的图的时候发现Unity对于这种图会产生一个fmt-512*512(sprite)的临时资源,这个资源get他的硬盘地址时get不到,所以也没有记录进依赖关系文件。当有两张图不规范时,一张图的bundle包收录了临时资源另一张图就没有,加载出来就会不正常。当然一般游戏项目为了优化使用的图都比较规范,不会遇到这个问题。

还有一个问题,就是使用mechanim动画系统遇到的。打出来的AssetBundle包会出现动画系统工作不正常,在Inspector里显示 “Animator is not initialized”。我在网上搜索时发现很多人都遇到这个问题,有人说Unity 4.6解决了这个问题,有人说5.0还有。我开始以为只能通过先从Assetbundle包里加载出角色prefab,然后再从Resouce里加载动画文件OverrideAnimController附给prefab来解决(prefab在未实例化前可以修改它上面挂的脚本的)。但是这样就失去了热更新的意义。 我后来发现,要把anim文件单独打一个包,controller文件单独打一个包,依赖他们的prefab再打成一个包,在打包的时候Editor会报警告说“不能把Editor对象打进包里”,但是通过这种方式打出来的一组包加载出来就是正常的。如果我用CollectDependencies让controller文件自动收录进角色包,打出来的角色就还是会出现”Animator is not initialized”的问题。也有可能不是这个原因,可能是我之前在排查”相互吃资源“BUG的时候没有排查干净,不知道有没有人遇到相同的问题。

(五)更新机制

更新机制比较简单,收集所有bundle包的md5码和文件size,做成一个列表。进游戏时先比对游戏版本号提示更新游戏程序,再比对资源版本号,如果发现新版本号就开始下载md5列表,与本地的md5列表做对比,找出需要更新的资源用http下载就行了。

这个过程还是有许多东西要考虑,比如你的http下载要有下载失败重试几次的机制,要有超时的检测,要知道在下载哪几个资源时整个更新流程卡住了,记录日志。即使遇到更新过程中出错,对于已经更新的资源下次进不用再重复更新,所以最好每更新10条资源就写回一次md5,而不是全更完再写回。

md5列表的比较,以前有的PC游戏会在远端先做好与上一个版本的对比,然后生成一个ver x 到ver x+1 的资源更新文件。在更新的时候如果游戏的资源版本号是ver x-1 就先下载 ver x的资源更新文件更新,再从ver x更新到ver x+1。但是这一套用在Unity手机资源更新上有风险,假设有些手机的清理软件提示这个程序的资源占用过大,一不小心点了导致清掉了部分资源,但你的ver x文件还在,那你更新时被清掉的这部分资源就找不回来了。所以还是每次在客户端对比所有md5比较稳妥,在提取本地的md5列表中的一项时同时检测本地是否存在这个资源文件,不存在的加入更新列表,这样即使被意外清掉的资源也可以找回。

(六)压缩bundle包

因为我们使用的是CreateFomeFile的同步机制加载包,而CreateFromFile只能用BuildAssetBundleOptions.UncompressedAssetBundle,打包出来后自己压缩再在更新时解压。所以采用什么压缩算法就是一个值得商榷的问题。

压缩你要考虑两个方面:压缩率与解压时间。

(待续)

(十)AssetBundle包卸载

时间: 2024-10-17 04:52:48

Unity AssetBundle打包与资源更新的相关文章

【小松教你手游开发】【unity系统模块开发】Unity Assetbundle打包笔记

*最近项目更新了Unity5.5.2,顺便更新了项目的ui打包,也更新一下这边的笔记 首先打包分为两部分,一部分是打包成Assetbundle包,一部分是从Assetbundle包解包出来成为可用的资源. 首先说第一部分 打包 所有资源都可以打包,甚至不是资源(一些数据)也可以打包,只要你需要. 打包出来的东西都可以直接用,一个字体,一个Texture,一个Prefab,一个场景,都是一打出来成Assetbundle包就可以直接用,但是为什么大家还是要各自开发自己的打包流程呢? 最重要的原因就是

Unity AssetBundle打包资源工具

using UnityEngine;using System.Collections;using UnityEditor; /// <summary>/// 简单资源打包Editor/// </summary>public class BuildPacketEditor : EditorWindow { [MenuItem("Tools/Packet/BuildAssetBundle-Android")] public static void ExportAnd

Unity AssetBundle共享资源打包/依赖资源打包

依赖性打包 依赖性打包的作用在于避免资源冗余,同时提高资源加载和卸载的灵活性,其重要性不言而喻.在4.x版本的AssetBundle打包系统中,涉及一对 BuildPipeline.PushAssetDependencies和BuildPipeline.PopAssetDependencies接口,从官方文档中可以大致了解其用法:http://docs.unity3d.com/ScriptReference/BuildPipeline.PushAssetDependencies.html 可以简

关于unity 中使用AssetBundle加载资源,shader偶尔会丢失的问题解决办法

问题描述: 因为项目中要进行热更新设计,所以用unity官方推荐的打包方式assetbundle进行打包,打包好了以后再电脑上运行正常,但是当发布到android上后发现偶尔场景背景会出现空白,多方查找,发现unity存在使用assetbundle加载资源会丢失shader的问题.网上找了一些解决方式,测试可用,分享出来. 方案一:(亲测可用) 第一步,在将用到的Shader加到Editor->Graphics Settings的Shader列表里再进行打包(依赖打包) 第二步,在代码中给sha

谈谈混合 App Web 资源的打包与增量更新

综述 移动 App 的运行环境具有带宽不稳定,流量收费,启动速度比较重要等特点,所以混合 App 如何加载 Web 资源并不是一个新问题.本文目的是总结出一种资源打包下载的思路和方案,并且提供一种打包工具.本文提到的思路只是一家之言,基本没有参考现有方案,各位方家有不同意见欢迎留言.另外本文没有涉及到 App 内部如何加载资源的问题,这部分我会专门撰写一篇文章讨论. 需求梳理 一般来说,Hybrid-app 对于 Web 资源下载有如下需求: 页面开启速度要快,所以资源的下载和使用不是在同一时间

[cb] Assetbundle打包(一)

Unity的Assetbundle是Unity Pro提供的功能. 理解:Asset 资源,资产:Bundle :包,一批,捆:字面上的意思,就是把资源打包. 在项目中的实际应用:Art工程,Prefab打包成AssetBundle到Produect目录,Client工程读取AssetBundle; 下面这张图是Art工程 放在Product目录下的Prefab都会打包成AssetBundle 打包AssetBundle到Product目录下[Assetbundle有运行平台之分] Client

Unity 安卓下DLL热更新一(核心思想)

大家都知道一谈起热更新的话首选是Ulua这个插件, 其实Unity可以使用dll热更新的,如果你实在不想用Lua来编写逻辑,0.0请下看Dll+AssetBundle如何实现热更新的.让你看完这个文章之后只是认识DLL热更新的方式和概念,掌握热更新的实战框架还需要你自己=.=   我们通常的做法是编译成的DLL打成AssetBundle文件, Unity通过WWW下载AB文件获取里面DLL.通过反射的方式把里面的C# 组件绑定到GameObject游戏物体上面,这就是DLL热更新的原理. 假设项

Unity AssetBundle 爬坑!!!!!!!

AssetBundle是Unity推荐的资源管理方式,官方列举了诸如热更新,压缩,灵活等等优点,但AssetBundle的坑是非常深的,很多隐藏细节让你使用起来需要十分谨慎,一不小心就会掉入深坑,打包没规划好,20MB的资源"压缩"到了30MB,或者大量的包导致打包以及加载时的各种低效,或者莫名其妙地丢失关联,或者内存爆掉,以及各种加载失败,在网上研究了大量关于AssetBundle的文章,但每次看完之后,还是有不少疑问,所以只能通过实践来解答心中的疑问,为确保结果的准确性,下面的测试

(转)Unity AssetBundle爬坑手记

转自:http://www.cnblogs.com/ybgame/p/3973177.html 这篇文章从AssetBundle的打包,使用,管理以及内存占用各个方面进行了比较全面的分析,对AssetBundle使用过程中的一些坑进行填补指引以及喷! AssetBundle是Unity推荐的资源管理方式,官方列举了诸如热更新,压缩,灵活等等优点,但AssetBundle的坑是非常深的,很多隐藏细节让你使用起来需要十分谨慎,一不小心就会掉入深坑,打包没规划好,20MB的资源“压缩”到了30MB,或