Unity3D 5.0版本以后的AssetBundle机制和以前的4.x版本已经发生了很大的变化,一些曾常常用的流程已经再也不使用,甚至一些老的API已经被新的API所取代。
所以,本文的主要内容就是分析5.X版本的AssetBundle机制(包括建立资源包、压缩资源包、加载资源包和从资源包中加载/卸载资源等几个方面)及其关键的API使用方式并总结一些对项目的建议(例如根据不一样的情景,选择不一样的包体加载方案等等)。算法
本小节包括:数据库
AssetBundle系统的新功能缓存
新的AssetBundle系统的优点异步
在新的AssetBundle系统中,出现了如下的新功能:socket
经过Editor中的UI便可方便的为AssetBundle标记资源。并且一个资源和对应的AssetBundle的映射将会在资源数据库(AssetDatabase)中被建立。
在箭头处便可指定该资源所述的AssetBundle,第一个选项为AssetBundle的名字,然后一个选项则是为AssetBundle建立变体,例如一些素材须要区分为高清或普通存放在不一样的AssetBundle中,那么第二选项就能够以hd和normal来区分。编辑器
提供了新的API用来设置资源所属的AssetBundle:函数
设置AssetImporter.assetBundleName的值,便可为该资源指定它所属的AssetBundle。上文中在UI中设置的AssetBundle的名字即是为该值赋值,在资源有了assetBundleName以后,实际上它的信息就已经存在于AssetDataBase里面了。工具
新版本中,建立AssetBundle文件的API变得十分简单了:ui
BuildPipeline.BuildAssetBundles():咱们只须要提供一个输出AssetBundle的地址便可。引擎将自动根据资源的assetbundleName属性(即在上文中UI中设置的值)批量打包,自动创建Bundle以及资源之间的依赖关系。加密
新增了一些打包策略/选项,且一些4.x中的旧有策略被默认开启。
CompleteAssets ,用于保证资源的完备性,默认开启;
CollectDependencies,用于收集资源的依赖项,默认开启;
DeterministicAssetBundle,用于为资源维护固定ID,默认开启;
ForceRebuildAssetBundle,用于强制重打全部AssetBundle文件,新增;
IgnoreTypeTreeChanges,用于判断AssetBundle更新时,是否忽略TypeTree的变化,新增;
AppendHashToAssetBundleName,用于将Hash值添加在AssetBundle文件名以后,开启这个选项能够直接经过文件名来判断哪些Bundle的内容进行了更新(4.x下广泛须要经过比较二进制等方法来判断,但在某些状况下即便内容不变从新打包,Bundle的二进制也会变化),新增。
ChunkBasedCompression,用于使用LZ4格式进行压缩,5.3新增。
Manifest文件。在4.x版本中,咱们一般须要自行维护配置文件,以记录AssetBundle之间的依赖关系,并供运行时使用。而在5.x版本中,使用Manifest文件能够免去4.x版本中的这一过程。而Manifest文件分为两种:
单个bundle的Manifest文件,一旦一个新的AssetBundle文件被建立导出,便会对应生成一个.manifest文件,其中包含了校验、依赖文件等信息。因此能够用来作增量更新。
实际上在打包的时候,在输出的bundle所在的文件夹内还会生成一个总的manifest文件,叫作[文件夹名].manifest。它包含了该文件夹内全部的bundle的信息,以及它们之间互相依赖的信息。因此在咱们加载bundle的时候,须要先把总的manifest文件加载进来,以确认各个bundle之间的依赖关系。
一些在运行时动态加载AssetBundle的API被新的API代替。
4.x版本中的AssetBundle.CreateFromFile方法,在5.x版本中变成了AssetBundle.LoadFromFile方法。
4.x版本中的AssetBundle.CreateFromMemory方法,在5.x版本中变成了LoadFromMemoryAsync方法。
4.x版本中的AssetBundle.CreateFromMemoryImmediate方法,在5.x版本中变成了LoadFromMemory方法。
因为引擎提供的这些新功能,咱们就再也不须要像4.x时代那么复杂的用来打包的脚本了。
同时,资源之间的互相依赖关系再也不须要开发者手动维护了,曾经因为不当使用PushAssetDependencies/PopAssetDependencies而可能会形成依赖出现的问题,如今Unity3D已经为咱们解决了。
并且因为引入了清单文件manifest,所以咱们能够实现增量更新,即只须要更新有变化的部分,而没有变化的则没必要更新。
举一个例子:
假设咱们有一个cube,它的material有一个材质,咱们分别将cube和material打包成cubeBundle和materialBundle,以后咱们修改material上的材质。在过去,咱们须要分别从新为cube和material打包,而如今只须要对material从新打包便可,cube不受影响。
本小节包括:
旧有建立AssetBundle文件的API
新的建立AssetBundle文件的API
针对项目的建议
在4.x时代,最经常使用的AssetBundle打包方法主要包括如下两个:
BuildPipeline.BuildAssetBundle
对除Scene之外的资源打包,支持单个和多个资源,须要在方法的参数中指明须要被打入AssetBundle的资源;
BuildPipeline.BuildStreamedSceneAssetBundle
对Scene文件打包,也支持单个和多个。
且在4.x时代,打包还须要注意资源之间互相依赖的问题。为了不资源冗余,同时提升资源加载和卸载的灵活性,所以依赖性打包的重要性不言而喻。老版本中,咱们可使用如下两个方法来实现这种依赖性:
BuildPipeline.PushAssetDependencies
BuildPipeline.PopAssetDependencies
这种机制并不难理解,简单的说PushAssetDependencies是将资源进栈,PopAssetDependencies是让资源出栈,每打一个包,引擎都会检查当前栈中全部的依赖项,查看是否有相同资源已经在栈中。若有,则与其相关的AssetBundle创建依赖关系。
在新版本中,Unity3D为咱们提供了惟一的API用来打AssetBundle包。即:
BuildPipeline.BuildAssetBundles
在脚本中调用BuildPipeline.BuildAssetBundles,U3D将自动根据资源的assetbundleName属性批量打包,自动创建Bundle和资源之间的依赖关系。
在资源的Inpector界面最下方可设置该资源的assetbundleName,每一个assetbundleName对应一个Bundle,即assetbundleName相同的资源会打在一个Bundle中。
若是所依赖的资源设置了不一样的assetbundleName,则会自动与之创建依赖关系,避免出现冗余,从而减少Bundle包的大小。
固然,除了能够指定assetbundleName,咱们还能够在Inpector中设置另外一个名字,即variant。在打包时,variant会做为后缀添加在assetbundleName以后。相同assetbundleName,不一样variant的Bundle是能够相互替换的。
设置好以后,咱们只须要建立一个新的脚本,经过编辑器拓展调用BuildPipeline.BuildAssetBundles方法便可:
using UnityEditor; public class CreateAssetBundles { [MenuItem ("Assets/Build AssetBundles")] static void BuildAllAssetBundles () { BuildPipeline.BuildAssetBundles ("Assets/AssetBundles"); } }
BuildPipeline.BuildAssetBundles方法的参数为bundle的导出目录。固然它有不少重载的版本,能够提供额外的参数来定制符合本身需求的AssetBundle。
虽然新的AssetBundle简化了打包和处理资源依赖的过程,可是却引入了一个新的复杂度,即须要设置资源的assetbundleName以实现打包的功能。
所以咱们可能须要作的是:
提供脚本批量对资源设置assetbundleName
规划好assetBundle所对应的资源类型,规划好assetBundle的数量
本小节包括:
AssetBundle的压缩类型
针对项目的建议
Unity3D引擎为咱们提供了三种压缩策略来处理AssetBundle的压缩,即:
LZMA格式
LZ4格式
不压缩
LZMA格式:
在默认状况下,打包生成的AssetBundle都会被压缩。在U3D中,AssetBundle的标准压缩格式即是LZMA(LZMA是一种序列化流文件),所以在默认状况下,打出的AssetBundle包处于LZMA格式的压缩状态,在使用AssetBundle前须要先解压缩。
使用LZMA格式压缩的AssetBundle的包体积最小(高压缩比),可是相应的会增长解压缩时的时间。
LZ4格式:
Unity 5.3以后的版本增长了LZ4格式压缩,因为LZ4的压缩比通常,所以通过压缩后的AssetBundle包体的体积较大(该算法基于chunk)。可是,使用LZ4格式的好处在于解压缩的时间相对要短。
若要使用LZ4格式压缩,只须要在打包的时候开启BuildAssetBundleOptions.ChunkBasedCompression便可。
BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath, BuildAssetBundleOptions.ChunkBasedCompression);
不压缩:
固然,咱们也能够不对AssetBundle进行压缩。没有通过压缩的包体积最大,可是访问速度最快。
若要使用不压缩的策略,只须要在打包的时候开启BuildAssetBundleOptions.UncompressedAssetBundle便可。
BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath, BuildAssetBundleOptions.UncompressedAssetBundle);
AssetBundle的压缩策略不只仅和包体的大小、包体的解压速度相关,并且还会关系到AssetBundle在运行时动态加载的API使用。所以,针对不一样类型资源的AssetBundle要指定出符合其使用特色的压缩策略。
本小节主要包括:
新版API
动态加载方式对比
针对项目的建议
在5.x版本中的新AssetBundle系统中,旧有的一些动态加载API已经被新的API所取代,具体内容以下:
4.x版本中的AssetBundle.CreateFromFile方法,在5.x版本中变成了AssetBundle.LoadFromFile方法。
4.x版本中的AssetBundle.CreateFromMemory方法,在5.x版本中变成了LoadFromMemoryAsync方法。
4.x版本中的AssetBundle.CreateFromMemoryImmediate方法,在5.x版本中变成了LoadFromMemory方法。
所以,本小节以后的内容将使用新版API。
使用AssetBundle动态加载资源首先要获取AssetBundle对象,第二步才是从AssetBundle中加载目标资源。所以本小节将主要关注如何在运行时获取AssetBundle的对象,关于如何从AssetBundle中加载资源将在下一小节中分析。
要在运行时加载AssetBundle对象主要能够分为两大类途径:
先获取WWW对象,再经过WWW.assetBundle获取AssetBundle对象
直接获取AssetBundle
下面咱们就具体分析一下这两种途径:
先获取WWW对象,再经过WWW.assetBundle加载AssetBundle对象:
在先获取WWW对象,在获取AssetBundle的这种方式中,咱们可使用如下两个API来实现这个功能。
public WWW(string url),直接调用WWW类的构造函数,目标AssetBundle所在的路径做为其参数,构造WWW对象的过程当中会加载Bundle文件并返回一个WWW对象,完成后会在内存中建立较大的WebStream(解压后的内容,一般为原Bundle文件的4~5倍大小,纹理资源比例可能更大),所以后续的AssetBundle.LoadAsset能够直接在内存中进行。
public static WWW LoadFromCacheOrDownload(string url, int version, uint crc = 0),WWW类的一个静态方法,调用该方法一样会加载Bundle文件同时返回一个WWW对象,和上一个直接调用WWW的构造函数的区别在于该方法会将解压形式的Bundle内容存入磁盘中做为缓存(若是该Bundle已在缓存中,则省去这一步),完成后只会在内存中建立较小的SerializedFile,然后续的AssetBundle.LoadAsset须要经过IO从磁盘中的缓存获取。
直接加载AssetBundle对象:
在4.x时代,咱们能够经过CreateFromFile或CreateFromMemory方法将磁盘上的文件或内存中的流构形成咱们须要的AssetBundle对象。可是在5.x版本中,曾经的这两个方法已经被新的LoadFromFile、LoadFromMemory方法所代替(这两个方法还有异步的版本),且机制上也有了一些区别。
public static AssetBundle LoadFromFile(string path, uint crc = 0):新的从文件建立加载AssetBundle方法和4.x中的CreateFromFile方法在机制上有了一些分别,旧的CreateFromFile必须使用未压缩的Bundle文件才能在运行时动态建立AssetBundle对象。而新的LoadFromFile方法则没有这个要求,它支持上一节中提到的几个压缩格式,针对LZ压缩格式和未压缩的磁盘上的bundle文件,该方法会直接加载。针对使用默认的LZMA压缩格式压缩的bundle文件,该方法会在幕后先将bundle文件解压后再加载。这是最快的加载AssetBundle的方式。该方法是同步版本,还有异步版本:LoadFromFileAsync。
public static AssetBundle LoadFromMemory(byte[] binary, uint crc = 0):从内存中获取Bundle的二进制数据,同步地建立AssetBundle对象。该方法通常用在通过加密的数据上,通过加密的流数据通过解密以后咱们能够调用该方法动态的建立AssetBundle对象。该方法是同步版本,还有异步版本:LoadFromMemoryAsync。
以上即是在运行时动态加载AssetBundle对象的方法。下面,咱们再从加载过程当中内存消耗的角度来对比一下这几种加载AssetBundle对象的方法,下表是Unity3D官方的一个中文版总结。
注意:当使用WWW来下载一个bundle时,WebRequest还会有一个8*64KB的缓存区用来存储来自socket的数据。
因为以上分析的几种加载手段各有各的使用情景和特色。所以建议在咱们的项目中按照如下情景使用这些方法:
随游戏一同发布的AssetBundle(通常位于StreamingAssets文件夹中):
在打AssetBundle包时,使用LZ4压缩格式进行打包(开启BuildAssetBundleOptions.ChunkBasedCompression便可)。
在运行时须要加载AssetBundle对象时,使用LoadFromFile方法进行加载。
这样作的好处是:便可以将AssetBundle文件压缩,又能够兼顾加载速度,且节约内存。
做为更新包,须要从服务端下载的AssetBundle:
在打AssetBundle包时,使用默认的LZMA格式压缩。
使用WWW.LoadFromCacheOrDownload方法下载并缓存AssetBundle包文件。
这样作的好处是:得到了最大的压缩率,在下载过程当中能够减小数据传输量。同时,在本地磁盘建立缓存以后,又能够兼顾以后的加载速度,且节约内存。
咱们本身进行加密的AssetBundle:
在打AssetBundle包时,使用LZ4压缩格式进行打包(开启BuildAssetBundleOptions.ChunkBasedCompression便可)。
在运行时须要加载AssetBundle对象时,使用LoadFromMemory方法进行加载。(这也是从内存中使用流数据加载AssetBundle对象的仅有的使用场景。)
咱们本身压缩的AssetBundle:
咱们本身也可使用第三方库或工具对生成的AssetBundle包文件进行压缩,若是须要这样作,则咱们最好不要再使用Unity3D对AssetBundle进行压缩,所以在打包时选择开启BuildAssetBundleOptions.UncompressedAssetBundle。
在运行时须要加载AssetBundle对象时,使用LoadFromFileAsync方法进行异步加载。
本小节包括:
从AssetBundle对象中加载资源
资源的卸载
新旧版的加载和卸载资源的API名称发生了一些变化,可是机制变化不大。
在旧有的4.X版本中,从AssetBundle对象中加载资源所使用的API主要包括如下几个:
Load:从资源包中加载指定的资源
LoadAll:加载当前资源包中全部的资源
LoadAsync:从资源包中异步加载资源
而在新版的AssetBundle中,加载资源的API已经变成了如下的几个:
LoadAsset:从资源包中加载指定的资源
LoadAllAsset:加载当前资源包中全部的资源
LoadAssetAsync:从资源包中异步加载资源
资源卸载部分的变化不大,使用的仍然是Unload方法。
Unload
该方法会卸载运行时内存中包含在bundle中的全部资源。当传入的参数为true,则不只仅内存中的AssetBundle对象包含的资源会被销毁。根据这些资源实例化而来的游戏内的对象也会销毁。当传入的参数为false,则仅仅销毁内存中的AssetBundle对象包含的资源。