1.入门:
Resources:表示U3D自动将资源打成一个AssetBundle包,所有放在Resources下的文件夹都会打成一个AssetBundle包,资源非常大,Resources文件夹在真机上最大只有2G的内存(专业版会增大内存)
AssetBundle包:其实可以看成一个压缩包(有固定格式的),默认情况下使用LZMA压缩方式压缩的资源文件;和压缩ZIP一样的,只不过里面的格式变了
Unity3D引擎为我们提供了三种压缩策略来处理AssetBundle的压缩,即:
1、LZMA格式:
在默认情况下,打包生成的AssetBundle都会被压缩。在U3D中,AssetBundle的标准压缩格式便是LZMA(LZMA是一种序列化流文件),因此在默认情况下,打出的AssetBundle包处于LZMA格式的压缩状态,在使用AssetBundle前需要先解压缩。使用LZMA格式压缩的AssetBundle的包体积最小(高压缩比),但是会相应的增加解压缩的时间
2、LZ4格式:
Unity5.3之后的版本增加了LZ4格式压缩,由于LZ4的压缩比一般,因此经过压缩后的AssetBundle包体的体积较大(该算法基于chunk)。
3、不压缩
Resources和AssetBundle的区别:
1、Resources:有2G的限制,不支持动态更新(拿Android为例)
a、上线后,Resources里面的资源都在APK里面,只能读不能写
b、StreamingAssets和dataPath都是在Apk中,Apk为一个压缩包,里面的东西只能读不能写,因此这个限制就决定了Resources不能支持热更新
2、PersistPath(支持热更):
a、只能手动打包
b、打包方式:
1、4.0以前,打包的依赖关系都必须手动
2、5.0以后,这种依赖关系全部自动
创建AssetBundle:AssetBundle可以将任何资源打包成assetbundle(如果是unity不能识别的文件,可以通过改变后缀进行打包)
1、在任何一个游戏物体的右下角有AssetBundle,第一个为包名,第二个为后缀(不能识别的物体不能修改,修改后缀变成能识别的物体)
更新流程:
1、代码的更新:热更新
2、资源的更新:AssetBundle更新
a、创建assetbundle
b、将assetbundle打成一个zip文件,放进streamingAssetPath里面
c、把需要更新的AssetBundle先上传到Server,如果Server有更新(与服务器版本进行比对),把server的assetbundle包下载下来(zip文件),然后解压到persistentPath里
d:加载:判断PersistentPath里是否有这个资源,如果没有就读取StreamingAssetPath里面的,如果有,就直接读取PersistentPath里的
1、LoadAsset:从资源包中加载指定的资源
2、LoadAllAsset:加载当前资源包中所以的资源
3、LoadAssetAsync:从资源包中异步加载资源
3、资源卸载:资源卸载部分使用的是Unload方法
Unload:
a、该方法会卸载运行时内存中包含在bundle中的所有资源。
b、当传入的参数为true,则不仅仅内存中的AssetBundle对象包含的资源会被销毁;根据这些资源实例化而来的游戏内的对象也会销毁
c、当传入的参数为false,则仅仅销毁内存中的AssetBundle对象包含的资源
BuildPipeline.BuildAssetBundles(outputPath, BuildAssetBundleOptions.AppendHashToAssetBundleName|BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget);
AssetBundle的适用平台与跨平台性
AssetBundle适用于多种平台,包括网页应用、移动应用、桌面应用等,可以动态更新,但不同平台所使用的AssetBundle并不相同,在创建离线AssetBundle的时候需要通过参数来指定目标平台,相关关系如表所示:
IOS:在Mac版本的Standlone打包
Windows:打Android、PC、WebPlayer
BuildPipeline.BuildAssetBundles():
1、第一个参数:AssetBundle输出到哪一个文件夹
2、第二个参数:枚举类型,可以选择多个参数,多个参数之间可以用“|“隔开
a、CompleteAssets:保证资源的完备性,默认开启
b、CollectDependencies:用于搜集资源的依赖项,默认开启
和完整性有点相像,会把游戏物体所依赖的资源一起进行打包,防止有些被依赖的资源没有被打进去
c、DeterministicAssetBundle:用于为资源维护固定ID,默认开启
所以的资源都有固定的标记的,在资源的mate文件中的guid:64位组成的字符串,电脑里是随机生成并且唯一,每一个文件的唯一标识
d、ForceRebuildAssetBundle:用于强制重打所有AssetBundle文件,5.3以后新增
资源更新了,重新打包,将以前的资源包丢弃
e、IgnoreTypeTreeChanges:用于判断AssetBundle更新时,是否忽略TypeTree的变化,新增
打包时是否忽略父子关系
f、AppendHashToAssetBundleName:用于将Hash值添加在AssetBundle文件名之后,开启这个选项可以直接通过文件名来判断哪些Bundle的内容进行了更新
(4.X下普遍需要通过比较二进制等方法来判断,但在某些情况下即使内容不变重新打包,Bundle的二进制也会变化),新增
在包名后缀名后面添加一个Hash表
g:ChunkBasedCompression:用于使用LZ4格式进行压缩,5.3新增
一、如何组织assetBundle:
unity5以前,打包需要自己去找依赖,然后需要按照拓扑图顺序压入AB栈,这样在最后打AB时才能有效利用依赖(栈内已有的AB才能作为依赖)。
unity5.x后,打包变得简单,但如何组织assetBundle依然需要琢磨和规划。
首先我们需要知道:AB、asset和资源的关系:一个AB包括1个或多个asset,一个asset可能没有依赖其他asset(即包括了其需要的所有资源),也可能依赖其他asset(包括一些资源和指向其他asset所在AB的引用)。
其次,使用asset和AB:要使用一个asset,需要先加载该asset的所有依赖的asset(迭代下去,把所有相关的AB都加载出来),然后加载自己的AB,最后取出asset来使用。
所以,要使用一个asset,都需要加载所有相关的AssetBundle镜像进入内存。这就需要我们要研究打ab的方案了。并且打ab的方式也影响ab的总大小,即使在Unity5下。
文档提供了3种打ab方案:
1.逻辑实体分类法,即根据功能模块分类,(大体上意思是不同功能不同ab,这样就可以开一个功能下载一个ab)比如先大层次分:UI模块,角色模块,场景模块等,
UI模块又根据具体功能模块分:比如各个副本的UI是不同的,那每个副本的UI可以打成单独ab;
角色模块则是每个角色包括其依赖的东西,如mesh,model,animation,texture等打成一个AB;
场景模块可以一个场景分成多个片,每个片相当于一个“子模块”,单独成ab,多个场景共享片。
2.根据资源类型打ab:比如音效打成一个ab,国际化文本打成1个ab等,文档说这种方法是打多平台ab的一种好方法,比如音效ab在各个平台下都一样则可以复用,而shader平台区分则不能复用
并且在此方法下,因为原始资源(比如Texture)很少改动,所以利于版本更新(增量少)。这种方法我也不是很理解,感觉就是把ab粒度细化到原始资源层次(纹理,音效,网格等),他们有的在不同平台保持一样,有的平台区分,但基本不会发生改变。
3.根据“同时加载”来打ab,即把需要同时加载的资源打入同一个ab.比如每个场景包括其依赖的所有东西打成一个AB。
无论使用什么方案(一般是都使用一点点,比如第3种用来打场景,第1种用来打各个系统(即功能)的资源,等),为了减少AB大小,增量AB大小,加载时间和运行时内存消耗,都需要注意下面细节:
1.频繁变动的asset不应该和稳定的asset打入一个AssetBundle;小增量AB
2.如果稳定的大/多asset中含有频繁变动的资源,应该把资源单独打AB;小增量AB
3.使用时一般同时加载的东西如Model和它的texture,material,animation等应该打入一个AssetBundle中,所以角色一般每个一个AB;减少加载时间
4.如果多个AB都依赖某个asset或资源,应该把该asset或资源单独打AB;小AB,省内存
5.如果2个asset极少同时加载,那应该分开打AB;省内存
6.把经常同时加载的小asset的AB合并成大AB;减少加载时间
二、打AB的参数选择和加载AB的方案:
1.清单:
1)打AB时,每个AB都会额外带一个.manifest文件,用来描述该AB相关信息,包括
CRC码,(file hash)a single hash for all assets in AB,(type tree hash)a single hash for all types in AB,(class types)all class types in AB,(asset names)all asset paths in AB.
此文件只用于生成增量AB, not necessary for runtime,所以不需要打入安装包,依赖关系可以在2)那里统一查找。
2)并且在打AB的输入参数中的“输出文件夹”那里生成一个.manifest文件,里面包括所有AB路径及其依赖,这个在运行时加载资源时使用,因为加载资源需先加载依赖。
2.AB的压缩方式
AB有3种压缩方式(当然你也可以自定义压缩算法,不过没必要),一种是无压缩UncompressedAssetBundle,一种是块压缩LZ4(ChunkBasedCompression),一种是最大压缩LZMA(None)
加载时无压缩最快,其次是块压缩(只需要解压AB的一部分就能加载,与无压缩具有可比性),最后是LZMA(需要完全解压AB),当然打出的AB大小就反过来了(LZMA:50-60%,LZ4:70%)。
一般本地AB建议以LZ4格式存储,较小且快,需要网络下载的用LZMA并使用LoadFromCacheOrDownload对其加载,这样到了本地就变成LZ4了(LoadFromCacheOrDownload在接受数据流时就直接解压,无额外耗时)。
3.AB加载的几个接口
1)加载AB文件(这里是指build出来后的AB文件,如果build后还进行加密等则不能直接加载)有几种方式:
WWW:输入URL,AB在www.assetBundle里;PS:如果ab在本地,url用file:///前缀
WWW.LoadFromCacheOrDownload:输入URL,AB在www.assetBundle里;PS:如果ab在本地,url用file:///前缀
LoadFromMemory(Async):输入是bytes[],返回的就是AB;PS:bytes的获取一般用WWW,也可以直接File.ReadAllBytes,反正获得文件的字节流即可。
LoadFromFile(Async):输入是path,AB在AssetBundleCreateRequest.assetBundle里;
UnityWebRequest:文档说此方法比WWW好,用法也类似,但因为WWW比较成熟,而且WWW我们基本只是用来加载本地bytes资源,而且此方法在5.4以后版本才有,我们没有使用。
在网络资源下载方面我们项目用的是C#提供的HttpWebRequest
2)加载非.assetBundle文件例如.bytes,.jpg,.png,.txt,.ogg,.mp3等文件一般使用WWW加载:
WWW.bytes,WWW.texture,WWW.text,WWW.audioClip里分别存加载后的数据。
如果原先是.assetBundle,加密成.bytes,先www加出bytes,然后loadfrommemory;
如果原先是.bytes或者其他自定义数据类型,如xml,json等,打ab成.assetBundle,则先加出assetBundle,然后加载出Asset,最后强转为TextAsset类型,然后你的数据就可以从中取出来了,Unity经常把这些数据看成TextAsset类型;
简而言之,对特殊文件格式,我们使用之前先测试一下其加载方式,基本在WWW和TextAsset这块考虑,看数据在它们哪个变量里,一测便知。
4.针对2和3介绍的多种方式,按情况选择压缩方式+加载接口
文档根据各个函数对内存和CPU消耗的特点做出以下建议:
1.对于安装包内的AB,尽量用LoadFromFile(Async)+ChunkBasedCompression,因为它和ChunkBasedCompression配合起来很快(可以压缩30%+而且加载时不需要额外解压),如果AB的压缩格式是LZMA(即Option=None),LoadFromFile(Async)需要额外解压和重压缩(压为LZ4);
2.对于网络资源,尽量用WWW.LoadFromCacheOrDownload,并且资源尽量用默认的LZMA压缩法,因为这样可以省流量,并且此函数在接收网络流时就对流进行解压,为LZ4(即AB压不压对其无区别),而加载LZ4不需要额外解压。
3.对于加密的AB,需要用WWW把文件加载成bytes,然后解密,然后用LoadFromMemory[Async]加载出AB(这个大概是LoadFromMemory[Async]使用的唯一场景了)
4.如果使用自定义压缩算法,选UncompressedAssetBundle不压缩来build AB,然后加载时先对数据解压,然后LoadFromFile(Async)
总结:本人当前项目是用LZMA build AB(不使用LZ4的原因可能是没深入了解,也可能是贪图LZMA的小包体,也可能是加载的多半是小ab,LZMA解压再压LZ4的消耗可以忍受),用LoadFromFile(Async)加载.assetbundle,WWW+LoadFromMemory(Async)加载加密过的ab.bytes,用WWW加载原始图片。
三、如果管理AssetBundle
1.AssetBundle.unload(true/false)的用法:
true会把AB,由AB加出来的Asset,由Asset实例化出来的GameObj(也可能只是指向Asset的引用)都销毁,内存一干二净;
false只是把AB卸载,官方文档描述为把AB和Asset,Gobj的link断开,即后者们自己删除自己,如果再次加载Asset则是新的Asset,与原来的Asset独立共存于内存;
这里需要明确两点:
1.AssetBundle加载出来后没有Unload则其镜像一直在内存,再次加载会报错,可以unload掉重新加载或者hold住它,不重新加载;
2.false时要卸掉Asset需要把由它实例化出来的Gobj都销毁(引用则置空),然后Resource.unloadunusedassets卸载,Gobj则destroy即可;
官方文档建议选择true,true调用的时机可以是切地图或者Asset引用为0时,如果选择false,那Asset只有在2的情况下才能卸掉。
但官方文档没说的一点是在调用unload(true)前如果再次加载应该怎么办。
1.其实只需在上层存个Pool即可,Pool里的元素对应一个AssetBundle,加载AB时先看Pool里有没有,有则直接使用它来GetAsset和实例化,并把引用+1,当实例化的东西Destroy时把该AB的引用减一,当引用变为0时unload(true)即可。
2.但这种方式主要用来处理实例化的GameObject资源,对某些资源比如AudioClip,Text,场景,自定义数据等一般很少频繁加载,这样我们在加载AB出来后直接unload(false),然后当它不使用时,对应的Asset引用就空了,就可以在UnloadUnusedAsset时卸载(切场景时默认调用一次,一般在程序中定时调用一次)
3.官方文档之所以建议使用unload(true),是想避免内存存多份Asset和Asset不能及时卸载,如果资源是很少加载如2中那些,用unload(false)较好,至于unloadunsedAsset的调用可以在该资源使用完后手动调用一下(当资源消耗时刻明确时),也可以交给游戏卸载系统(定时调用unloadunusedasset)或切场景。
我的一种方案:
A.对不频繁加载的资源(大部分都是),我们加载完后就unload(false),这样可以迅速释放内存镜像,而当加载出来的资源不再被使用(引用为空)时,Asset就能被识别为unsedassets而被卸载;
B.对频繁加载的资源(小部分),我们在上层弄一个pool,里面存着stringtoasset的字典,字典里只存asset,即即使是频繁加载的资源我们也要在加载出asset后用unload(false)把内存镜像卸载掉,只保留加载出来的资源以便实例化/引用,这里我们需要搞个引用计数,因为字典里永远引用着该资源,当引用计数为0时我们需要从字典中删除。
在A,B的指导下+定时调用Resources.UnLoadUnUsedAssets,GC.Collect应该能保持内存的尽量干净了。那哪些资源是频繁加载哪些是不频繁加载,标准是咋样的呢?这个需要具体测试得到相关标准和性能的关系才能做决定,和游戏逻辑有关,和资源有关,和体验有关,需要写工具来确定。
总结:AssetBundle的管理主要抓住几个接口即可AssetBundle.unload(true/false),Resources.UnLoadUnUsedAssets,GC.Collect,Destroy,但要真正了解他们,需要深入理解Unity对AssetBundle,Asset,资源等的内存管理机制,可以参考这篇文章:
http://www.cnblogs.com/88999660/archive/2013/03/15/2961663.html
四、Ps(存疑与补充)
AssetBundle Variant:ab变种,一般用于解决“多态性”问题,比如资源有hd,ld,图片要适应各种设备需要不同压缩格式等,有空可以研究一下。