AssetBundle详解

2016/4/8 Unity游戏开发

本文章 Unity 皆为5.3版本。Unity有两种动态加载资源机制:一种是Resource.Load。一种是AssetBundle。Assetbundle是Unity官方提供的功能,它可以把多个游戏对象或者资源二进制文件封装到Assetbundle中。供加载、解压使用。

# 一、Assetbundle原理

Unity有两种动态加载资源机制:一种是Resource.Load。一种是AssetBundle。Assetbundle是Unity官方提供的功能,它可以把多个游戏对象或者资源二进制文件封装到Assetbundle中。供加载、解压使用。

AssetBundle是Unity提供的一种用来存储资源的文件格式,它可以存储任意一种Unity引擎能够识别的资源,如Scene、Mesh、Material、Texture、Audio、noxss等等,同时,AssetBundle也可以包含开发者自定义的二进制文件,只需要将自定义文件的扩展名改为.bytes,Unity就可以把它识别为TextAsset,进而就可以被打包到AssetBundle中。Unity引擎所能识别的资源我们称为Asset,AssetBundle就是Asset的一个集合。 AssetBundle的特点: 压缩(缺省)、动态载入(减少包的大小)、本地缓存;

# 二、AssetBundle VS Resource

AssetBundle作为Unity官方推崇的资源更新方案,与传统的Resource差异如下:

  • a、Resource放在Resources目录下,resources.assets文件,单个文件有2GB限制,首次必须全部下载;
  • b、AssetBundle创建需要通过Editor脚本创建,支持动态下载,是Unity Web Caching License唯一可以缓存的内容;

# 三、AssetBundle的适用平台与跨平台性

AssetBundle适用于多种平台,包括网页应用、移动应用、桌面应用等,可以动态更新,但不同平台所使用的AssetBundle并不相同,在创建离线AssetBundle的时候需要通过参数来指定目标平台,相容关系如表所示:

N Standalone WebPlayer iOS Android
Standalone
WebPlayer
iOS
Android

# 四、AssetBundle工作流程

a、创建AssetBundle; b、上传到Server; c、游戏运行时根据需要下载(或者从本地cache中加载)AssetBundle文件; d、解析加载Assets; e、使用完毕后释放;

# 五、AssetBundle依赖关系介绍

什么是依赖关系?比如说如果你的一个模型有某个材质,当你把这个模型和材质都打包了之后,这个模型就是依赖这个材质的。也就是说你Load这个模型之前需要先将材质Load进来否则就会丢失材质。越是复杂的Prefab那么发生的依赖关系也就越多,如果不处理好就会导致素材丢失等情况。所以,我们需要对资源严格进行划分。不过在Unity5.x推出了新的AssetBundle系统,它会帮我们处理这些依赖关系项。

# 六、AssetBundle的组织

一个项目中肯定不止一个AssetBundle包,而每个AB包可能不止一个asset。而如何取组织资源文件,也就是把哪些资源打包到一起,哪些分开是值得商议的。每一个 AssetBundle 都有一些技术开销。AssetBundles 是一些封装资源的文件。这种封装会增大了 AssetBundle 的整体大小,尽管这种大小的增加不会太明显,而且是可测量的。当组织 AssetBundles 时,是更多的小的 AssetBundles 还是更少的大的 AssetBundles 呢?这是需要我们根据自己的项目的实际情况来好好权衡的。更多的小的 AssetBundles 会面临更多的跟踪和创建相关的开销,太少的 AssetBundles 会使单个AssetBundle 本身的大小变大,也会导致它们可能包含一些冗余的数据。

# 五、创建AssetBundle

在生成AB包的时候同时生成依赖关系信息文件。这样加载的时候就能够自动加载所有依赖项。

# 1. 前言

唯一创建AssetBundle文件的API - BuildPipeline.BuildAssetBundles,U3D将自动根据资源的assetbundleName属性批量打包,自动建立Bundle和资源之间的依赖关系。在资源的Inpector界面最下方可设置该资源的assetbundleName,每个assetbundleName对应一个Bundle,即assetbundleName相同的资源会打在一个Bundle中。 如果所依赖的资源设置了不同的assetbundleName,则会自动与之建立依赖关系,避免出现冗余,从而减小Bundle包的大小。 当然,除了可以指定assetbundleName,我们还可以在Inpector中设置另一个名字,即variant(例如:区分普清和高清)。在打包时,variant会作为后缀添加在assetbundleName之后。相同assetbundleName,不同variant的Bundle是可以相互替换的。

值得注意的是: AssetBundleName是可以带’/’符号的,这是一个很好的设计,因为我们打包的资源会很多,如果打包生成的所有AssetBundle都生成在同一个文件目录里,这肯定是很难管理的。不过名称引入’/’便可以很好解决这个问题。我们通过名字中设置类似”npc/demon/jushiguai.unity3d”这样的名字。那么在生成AssetBundle的时候会自动根据名字生成文件目录。这样生成的AssetBundle有了分类就很好管理了。

# 2. API

  • BuildPipeline.BuildAssetBundles(outputPath),outputPath为AB包生成文件夹路径,唯一打包接口。
  • Object mainAsset: 指定mainAsset,这样解析该AB包的时候可以通过assetBundle.mainAsset得到。
  • Object[] assets : 指定所有asset,传入对象数组.解析的时候可以通过LoadAsset(“name”)得到。
  • pathName : 指定生成的AB包的存储路径
  • BuildAssetBundleOptions:打包时候的一些特定属性选项:
  • CompleteAssets: 用于保证资源的完备性,默认开启
  • CollectDependencies: 用于收集资源的依赖项,默认开启
  • DeterministicAssetBundle: 用于为资源维护固定ID,默认开启
  • ForceRebuildAssetBundle:用于强制重打所有AssetBundle文件,新增;
  • IgnoreTypeTreeChanges:用于判断AssetBundle更新时,是否忽略TypeTree的变化,新增;
  • AppendHashToAssetBundleName:用于将Hash值添加在AssetBundle文件名之后,开启这个选项可以直接通过文件名来判断哪些Bundle的内容进行了更新(4.x下普遍需要通过比较二进制等方法来判断,但在某些情况下即使内容不变重新打包,Bundle的二进制也会变化),新增。
  • ChunkBasedCompression:用于使用LZ4格式进行压缩,5.3新增。

# 3. 创建AssetBundle

设置好之后,我们只需要创建一个新的脚本,通过编辑器拓展调用BuildPipeline.BuildAssetBundles方法即可:

using UnityEditor;

public class CreateAssetBundles
{
  [MenuItem ("Assets/Build AssetBundles")]
  static void BuildAllAssetBundles ()
    {
      BuildPipeline.BuildAssetBundles ("Assets/AssetBundles");
    }
}
1
2
3
4
5
6
7
8
9
10

# 4. AssetBundle的压缩类型

Unity3D引擎为我们提供了三种压缩策略来处理AssetBundle的压缩:

  • 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);
    
    1
    2
  • 不压缩: 当然,我们也可以不对AssetBundle进行压缩。没有经过压缩的包体积最大,但是访问速度最快。 若要使用不压缩的策略,只需要在打包的时候开启BuildAssetBundleOptions.UncompressedAssetBundle即可。

    BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath, 
    BuildAssetBundleOptions.UncompressedAssetBundle);
    
    1
    2

# 六、加载和卸载AssetBundle

# 1. 动态加载方式对比

使用AssetBundle动态加载资源首先要获取AssetBundle对象,第二步才是从AssetBundle中加载目标资源。要在运行时加载AssetBundle对象主要可以分为两大类途径:

  • 先获取WWW对象,再通过WWW.assetBundle获取AssetBundle对象
  • 直接获取AssetBundle

(1)先获取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从磁盘中的缓存获取。

(2)直接加载AssetBundle对象:

  • public static AssetBundle LoadFromFile(string path, uint crc = 0):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的数据。

# 2. 卸载AssetBundle

# 3. 针对项目的建议

由于以上分析的几种加载手段各有各的使用情景和特点。因此建议在我们的项目中按照以下情景使用这些方法:

  • 随游戏一同发布的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对象中加载资源和资源卸载

# 1.从AssetBundle对象中加载资源

  • LoadAsset:从资源包中加载指定的资源
  • LoadAllAsset:加载当前资源包中所有的资源
  • LoadAssetAsync:从资源包中异步加载资源

# 2.资源卸载

# 备注:AssetGraph-好用的AssetBundle打包工具,由Unity官方日本团队打造,可视化打包工具

Last Updated: 2022/1/8 04:00:18