浅谈AB包

Unity资源管理

在Unity中,一般来说,资源加载方式主要分为Resources加载和AssetBundle加载。

Unity有个特殊文件夹Resources,放在这个文件夹下的资源可以通过Resources.Load()来直接加载。即Resources加载资源方式。

当获得AssetBundle之后,也可以调用AssetBundle对应的API来加载资源。

什么是AB包

AB包全名AssetBundle(资源包)。是一种Unity提供的用于存放资源的包。通过将资源分布在不同的AB包中可以最大程度地减少运行时的内存压力,并且可以有选择地加载内容。

为什么要用AB包

1、热更新。(要热更新需要确保AB包打出来的资源具有唯一性,且相同资源的AB包检验码相同。)

2、Resources加载虽然简单方便,但是也有很多问题:

  • 对内存管理造成一定的负担。
  • 在打开应用时加载时间很长。
  • Resources文件夹下的所有资源统一合并到一个序列化文件中(可以看成统一打一个大包,巨型AB包有什么问题它就有什么问题),对资源优化有一定的限制。
  • 不建议大量使用Resources。

使用方法

打AB包:

public static AssetBundleManifest BuildAssetBundles(string outputPath, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform);

BuildAssetBundleOptions枚举类型的值转化为二进制都是只有一位是1,其他位都是0。如UncompressedAssetBundle是0000 0000 0001,IgnoreTypeTreeChanges是0000 0100 0000,DisableLoadAssetByFileName是1000 0000 0000。

BuildAssetBundles底层会对传入的BuildAssetBundleOptions值进行处理,根据二进制位数来判断使用哪种策略构建AB包。因此如果在构建AB包时想要使用多种策略,用&连接即可。

BuildTarget参数用来选择针对的平台,因为AB包在不同平台下是不兼容的。

设置资源AB包名:

AssetImporter.assetBundleName // AB包名
AssetImporter.assetBundleVariant // AB包变体名

获取AB包方法:

AssetBundle.LoadFromFile(string path)
AssetBundle.LoadFromFileAsync(string path)
AssetBundle.LoadFromMemory(byte[] binary)
AssetBundle.LoadFromMemoryAsync(byte[] binary)
AssetBundle.LoadFromStream(Stream stream)
AssetBundle.LoadFromStreamAsync(Stream stream)
WWW.assetBundle

LoadFromFile是从文件中加载AB包,它从一个给定的路径来加载AB包。如果AB包是LZ4加载方式,它只会加载AB包的Header,之后需要什么资源再加载那部分的AB包chunk。极大的减少了内存占用。(LoadFromFileAsync是它的异步版本)

LoadFromMemory是从内存中加载AB包,它从内存中的byte[]中加载AB包。它会完整的把AB包加载出来。(LoadFromMemoryAsync是它的异步版本)

LoadFromStream是从流中加载AB包,它从一个Stream中加载AB包。跟LoadFromFile一样,如果AB包是LZ4加载方式,它也是只会加载AB包的Header。(LoadFromStreamAsync是它的异步版本)

WWW是Unity中的跟网络相关的类,可以通过该类从网络中下载资源,之后加载成AB包。

加载资源方法:

AssetBundle.LoadAsset(string assetName, Type resType)
AssetBundle.LoadAssetAsync(string assetName, Type resType)

LoadAsset是同步方法,LoadAssetAsync是异步方法。

还有很多关于AssetBundle的方法,官方API中有详细的介绍。

AB包变体

即AssetBundleVariant。AB包变体被用来支持定制化参数,允许不同AB包中的不同Object在加载和解析instance ID引用时显示为相同Object。

从概念上讲,允许两个Object显示为共享相同的GUID和Local ID,但实际上由Variant ID来区分。

简而言之,实际上就是一个资源的分类标签。

如同一图片的高清和低清资源,同一模型的高精度和低精度资源。

在Unity编辑器右下角设置AB包名的后面就是设置AB包变体名。

BuildAssetBundleOptions:

None - 0:默认方式。使用LZMA压缩算法,该算法压缩后包体很小,但是加载的时候需要花费很长的时间解压。第一次解压之后,该包又会使用LZ4压缩算法再次压缩。这就是为什么第一次加载时间长,之后加载时间就没那么长了。(LZMA需要完整解压之后才能加载包内资源,LZ4不需要完整解压就可以加载包内资源。)

UncompressedAssetBundle - 1:不压缩。虽然包体大,但是加载快。

DisableWriteTypeTree - 8:不包含TypeTree信息。虽然可以使得AB包更小,但是对低版本不兼容。不建议使用。

DeterministicAssetBundle - 16:创建一个哈希来映射存储在AB包里的对象的id。

ForceRebuildAssetBundle - 32:强制重建AB包。

IgnoreTypeTreeChanges - 64:当做增量构建检测时,忽略type tree的变化。

AppendHashToAssetBundleName - 128:添加哈希到AB包名。

ChunkBasedCompression - 256:使用基于块的LZ4压缩算法。

StrictMode - 512:如果在构建时有任何错误,则不允许构建成功。

DryRunBuild - 1024:干构建。

DisableLoadAssetByFileName - 4069:禁止AB包通过文件名加载资源。

DisableLoadAssetByFileNameWithExtension - 8192:禁止AB包通过文件扩展名加载资源。

AssetBundleStripUnityVersion:构建时从压缩文件和序列化文件的header中移除Unity版本号。

LZMA和LZ4

LZMA是流压缩方式(stream-based)。流压缩再处理整个数据块时使用同一个字典,它提供了最大可能的压缩率,但是只支持顺序读取。所以加载AB包时,需要将整个包解压,会造成卡顿和额外内存占用。

LZ4是块压缩方式(chunk-based)。块压缩的数据被分为大小相同的块,并被分别压缩。如果需要实时解压随机读取,块压缩是比较好的选择。LoadFromFile()和LoadFromStream()都只会加载AB包的Header,相对LoadFromMemory()来说大大节省了内存。

内存占用

下面是AB包再内存中的占用情况:

这是从网络中下载资源的内存占用情况。

下载的资源包括AB包、图片、材质、动画、音频等,以Stream的形式存储在内存中。(AB包中也可以有图片、材质、动画、音频等资源)

之后通过加载AB包的方法,将AB包加载到内存中去。

AB包内的资源需要通过AssetBundle.Load()来加载到内存中。

对于GameObject来说,通常情况下需要对其进行改动,所以它是完全复制一份该资源来进行的实例化。也就是说,当AB包中的GameObject从内存中卸载后,实例化的GameObject不会因此丢失。并且对实例化对象的修改不会影响到GameObject资源。

对于Shader和Texture来说,通常情况下不需要对其进行改动,所以它是通过引用来进行的实例化。也就是说,当AB包中的Shader和Texture资源从内存中卸载后,实例化的Shader和Texture会出现资源丢失的情况。并且对实例化对象的修改会影响到Shader和Texture资源。

对于Material和Mesh来说,有时候可能需要对其进行改动,所以它是通过引用+复制来进行的实例化。也就是说,当AB包中的Material和Mesh资源从内存中卸载后,实例化的Material和Mesh会出现资源丢失的情况。并且对实例化对象的修改不会影响到Material和Mesh资源。

总结大致流程为:

AB包先要从硬盘或者网络中加载到内存中,然后将AB包内的每一份资源加载到内存中,再之后在内存中实例化这些资源。每种资源有其自己不同的实例化方式,卸载资源的时候需要注意。

AB包内部结构

AssetBundleFileHeader:记录了版本号、压缩等主要描述信息。

AssetFileHeader:包含一个文件列表,记录了每个资源的name、offset、length等信息。

Asset1:

  • AssetHeader:记录了TypeTree大小、文件大小、format等信息。
  • TypeTree(可选,有不要TypeTree的构建方式):记录了Asset对象的class ID。Unity可以用class ID来序列化和反序列化一个类。(每个class对应了一个ID,如0是Object类,1是GameObject类等。具体可在Unity官网上查询。)
  • ObjectPath:记录了path ID(资源唯一索引ID)等。
  • AssetRef:记录了AB包对外部资源对引用情况。

Asset2…

.manifest

这是AB包对应的.manifest文件。

ManifestFileVersion: 0 # 文件版本
CRC: 2657307167 # CRC校验码
Hashes: # 哈希AssetFileHash: # AB包中所有资源的哈希,可用于增量更新检测serializedVersion: 2 # Unity序列化版本Hash: 717e408ba50ee41b0960161fd2d5a827TypeTreeHash: # AB包中所有类型的哈希,可用于增量更新检测serializedVersion: 2 # Unity序列化版本Hash: 8d552bf2f5bdba1177c938cb98ca6f2f
HashAppended: 0
ClassTypes: # TypeTree
- Class: 1 # GameObjectScript: {instanceID: 0}
- Class: 21 # MaterialScript: {instanceID: 0}
- Class: 28 # Texture2DScript: {instanceID: 0}
- Class: 48 # ShaderScript: {instanceID: 0}
- Class: 114 # MonoBehaviourScript: {fileID: 1392445389, guid: f70555f144d8491a825f0804e09c671c, type: 3}
- Class: 114 # MonoBehaviourScript: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3}
- Class: 114 # MonoBehaviourScript: {fileID: -1200242548, guid: f70555f144d8491a825f0804e09c671c, type: 3}
- Class: 114 # MonoBehaviourScript: {fileID: -146154839, guid: f70555f144d8491a825f0804e09c671c, type: 3}
- Class: 114 # MonoBehaviourScript: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3}
- Class: 114 # MonoBehaviourScript: {fileID: 1297475563, guid: f70555f144d8491a825f0804e09c671c, type: 3}
- Class: 114 # MonoBehaviourScript: {fileID: 11500000, guid: 20e8969313b8e4614b498f042e99683a, type: 3}
- Class: 114 # MonoBehaviourScript: {fileID: 11500000, guid: c86dbe77db44a434bb15895563508b65, type: 3}
- Class: 114 # MonoBehaviourScript: {fileID: 11500000, guid: 1a7e2f4cb82d9b94a91270d550c880c0, type: 3}
- Class: 115 # MonoScriptScript: {instanceID: 0}
- Class: 128 # FontScript: {instanceID: 0}
- Class: 198 # ParticleSystemScript: {instanceID: 0}
- Class: 199 # ParticleSystemRendererScript: {instanceID: 0}
- Class: 213 # SpriteScript: {instanceID: 0}
- Class: 222 # CanvasRendererScript: {instanceID: 0}
- Class: 224 # RectTransformScript: {instanceID: 0}
- Class: 687078895 # SpriteAtlasScript: {instanceID: 0}
Assets: # 包含资源
- Assets/Bundle/.../a.prefab
- Assets/Bundle/.../b.prefab
- Assets/Bundle/.../c.spriteatlas
Dependencies: # AB包依赖
- /Users/apple/.../AssetBundles/Android/q
- /Users/apple/.../AssetBundles/Android/w
- /Users/apple/.../AssetBundles/Android/e
- /Users/apple/.../AssetBundles/Android/r
- /Users/apple/.../AssetBundles/Android/t

特殊路径

Resources

对应的是Resources特殊文件夹路径。(只读)

在Unity下对应为:/Assets/Resources。

Application.streamingAssetsPath

对应的是StreamingAsset文件夹路径。(只读)

在Unity下对应为:/Assets/StreamingAssets。

在Android下对应为:jar:file:///data/app/xxx.apk!/assets。

在iOS下对应为:Application/…/xxx.app/Data/Raw。

Application.persistentDataPath

对应的是应用持久化数据存储文件夹路径。应用更新、覆盖安装时,这里的数据都不会被清除。(可读可写)

在Unity下对应为:/该Unity项目文件夹路径。

在Android下对应为:/…/data/应用名/files。

在iOS下对应为:Application/…/Documents。iOS还会自动将persistentDataPath路径下的文件上传到iCloud,会占用用户的iCloud空间。如果persistentDataPath路径下的文件过多,苹果审核可能被拒,所以,iOS平台,有些数据得放temporaryCachePath路径下。

Application.dataPath

对应的是应用Asset文件夹路径。(只读。Android不可读,因为改目录指向的是个.apk文件,而不是目录)

在Unity下对应为:/Assets。

在Android下对应为:/data/app/…/xxx.apk。

在iOS下对应为:Application/…/xxx.app/Data。

Application.temporaryCachePath

对应的是应用临时数据缓存文件夹路径。(只读)

在Unity下对应为:/该Unity项目文件夹路径。

在Android下对应为:/…/data/应用名/cache。

在iOS下对应为:Application/…/Library/Caches。

依赖问题

依赖问题,通俗的话来说就是A包中某资源用了B包中的某资源。然而如果A包加载了,B包没有加载,这就会导致A包中的资源出现丢资源的现象。

在Unity5.0后,BuildAssetBundleOptions.CollectDependencies永久开启,即Unity会自动检测物体引用的资源并且一并打包,防止资源丢失遗漏的问题出现。

因为这个特性,有些情况下,如果没指定某公共资源的存放在哪个AB包中,这个公共资源就会被自动打进引用它的AB包中,所以出现多个不同的AB包中有重复的资源存在的现象。这就是资源冗余。

这种情况下,哪怕资源是一模一样,也无法进行合并优化。

要防止资源冗余,就需要明确指出资源存放在哪个AB包中,形成依赖关系。所以对于一些公共资源,建议单独存放在一个AB包中。

在加载的时候,如果AB包之间相互依赖,那么加载一个AB包中的资源时,先需要加载出另一个AB包的资源。这样就会导致不必要的消耗。所以说尽可能地减少AB包之间的依赖,并且公共资源尽量提前加载完成。

细粒度问题

细粒度问题即每个AB包分别放入多少资源的问题,一个好的策略至关重要。

加载资源时,先要加载AB包,再加载资源。如果AB包使用了LZMA或LZ4压缩算法,还需要先给AB包解压。

AB包数量较多,包内资源较少 AB包数量较少,包内资源较多
加载一个AB包到内存的时间短,玩家不会有卡顿感,但每个资源实际上加载时间变长。 加载一个AB包到内存的时间较长,玩家会有卡顿感,但之后包内的每个资源加载很快。
热更新灵活,要更新下载的包体较小。 热更新不灵活,要更新下载的包体较大。
IO次数过多,增大了硬件设备耗能和发热压力。 IO次数不多,硬件压力小。

简单策略:

  • 经常更新和不经常更新的对象拆分到不同的AB包中。
  • 同时加载的对象放在一个AB包中。
  • 不可能同时加载的对象拆分到不同的AB包中。
  • 根据项目逻辑功能来分组打AB包。
  • 根据同一类型对象来分组打AB包。
  • 公共资源和非公共资源拆分到不同的AB包中。

卸载问题

当调用Resources.UnloadAsset()时,虽Object被销毁,但Instance ID被保留且包含有效的GUID和Local ID引用。

当调用AssetBundle.Unload(true)时,不仅Object被销毁,而且Instance ID的GUID和Local ID引用变无效。

当调用AssetBundle.Unload(false)时,虽Object不被销毁,但Instance ID的GUID和Local ID引用变无效。场景中的物体会与该AB包分离链接。即该物体的instance ID引用的GUID和Local ID会断开引用,无法再通过该instance ID找到GUID和Local ID。

如果再次加载该AB包时,分离了链接的物体不会受该新加载的AB包管理。因此如果不注意的话可能会导致一些不可控的问题。Unity中有Resources.UnloadUnusedAssets()方法可以很好地解决这个问题。

AB包的加密

因为AB包存放着游戏的各种资源,所以如果AB包不加密,那么别人在得到AB包的时候可以直接看到AB包内所有的资源。经过一定特殊操作后可以直接从AB包中导出图片、音频、动画,甚至可以在Unity中直接实例化出来另存为Prefab。

加密思路如下:

1、在构建完AB包后,可以将AB包中的内容以byte[]形式读取。

2、之后选用任意加密方式对该byte[]加密。

3、加密完后重新写入AB包中。

4、AB包加密完成。

这样对AB包加密之后,如果使用AssetBundle.LoadFromFile()来加载加密的AB包是会报错的,因为Unity以及无法识别加密过后的内容了,这样也就防止了别人随意对AB包进行的读取和加载,保证了资源的安全性。

解密思路如下:

1、先以byte[]形式读取AB包中的内容。

2、之后使用对应的解密算法对该byte[]进行解密。

3、解密过后的byte[]通过AssetBundle.LoadFromMemory()来进行加载。

4、AB包加载完成。

总的来说,这种二进制加密AB包的方式虽然有效,但是加载时间和内存占用是一个需要考虑的问题。很多时候选择不进行加密,一方面原因是因为需要多占用一份内存的问题,代价过大。虽然说从byte[]加载成AB包之后,byte[]可以从内存中释放,但是在加载的过程中还是会有一个内存占用的巅峰。

另一种简单的加密方式,即可以实现直接手段加载不出AB包,而且相对上述二进制加密AB包方式加载更快、耗费更小。

本质是通过在AB包中添加偏移量来实现加密。

public static AssetBundle LoadFromFile(string path, uint crc, ulong offset);

AssetBundle.LoadFromFile()的第三个参数是AB包内容的byte偏移量。也就是说从offset个byte开始读取AB包的内容。

因此如果在构建完AB包之后,在AB包前插入N个随机byte,那么此时想要加载该AB包,如不知道这个N值,则是无法成功读取和加载AB包的。这也就实现了加密。

从Stream中加载

AssetBundle.LoadFromStream()不像AssetBundle.LoadFromMemory()会多占用一份内存。

public static AssetBundle LoadFromStream(Stream stream, uint crc, uint managedReadBufferSize)

这是从托管流中加载AB包的方法。它跟LoadFromFile()一样,只会读取AB包的头文件。

使用Stream加载的限制:

1、AB包数据必须是从Stream的0位置开始。

2、当从AssetBundle数据的末尾开始并尝试读取数据时,Stream实现必须返回读取的0字节且不引发异常。

3、Stream必须是可读(CanRead返回true)和可搜寻(CanSeek返回true)的。

4、可以从任何Unity线程中调用Seek()和Read()。

CRC校验

AB包加载资源的完整方法实际上是AssetBundle.LoadFromFile(string path, uint crc, ulong offset),三个参数。其中第二个参数就是CRC校验符。

每个AB包的.manifest文件中也有CRC校验符,用于校验数据完整性。

各种ID

序列化后,资源用GUID和Local ID管理。

GUID对应Asset。GUID存在.meta文件中。提供了文件特定位置的抽象。是一种映射。无需关心资源在磁盘上的存放位置。

Local ID对应Asset内的每一个Object。(Asset中)

虽然GUID和Local ID比较好用,但是毕竟因为存在磁盘上,读取比较耗时。因此Unity缓存一个instance ID对应Object,通过instance ID快速找到Object。instance ID是一种快速获取对象实例的ID,包含着对GUID和Local ID的引用。解析instance ID可以快速返回instance表示的已加载对象,如果为加载目标对象,则可以将文件GUID和Local ID解析为对象源数据,从而允许Unity即时加载对象。每次AB包重新加载时,都会为每个对象创建新的instance ID。

总结

没有最好的打AB包方式,只有最适合项目的打AB包方式。

参考资料

https://docs.unity3d.com/ScriptReference/AssetBundle.html
https://docs.unity3d.com/Manual/ClassIDReference.html
https://www.xuanyusong.com/?s=AssetBundle
https://blog.csdn.net/lodypig/category_6315960.html
https://blog.csdn.net/BillCYJ/article/details/99712313
https://learn.unity.com/tutorial/assets-resources-and-assetbundles#5c7f8528edbc2a002053b5a6


后记

这是很早之前学习AB包的时候写的笔记,有很多地方理解不到位,欢迎各位进行指正和讨论。

Unity——浅谈AB包(AssetBundle)相关推荐

  1. unity|加载AB包|有依赖关系的AB包

    进阶,异步加载AB包,请看<异步加载有依赖的AB包> 目录 为什么AB包会有依赖关系呢? 步骤 代码 为什么AB包会有依赖关系呢? 例如导入一个预制体的时候,预制体可能有图片,如果只把这个 ...

  2. Unity 加载 AB包的缓存问题

    情况 在Unity中,如果重复加载相同的ab包 并且加载包之后没有去将assetbundle unload 或者在assetbundle unload调用几帧内去加载另一个同名的assetbundle ...

  3. unity加载AB包报错:Unable to read header from archive file

    说明  今天在服务器端更新AB包到本地加载依赖的时候突然提示:Unable to read header from archive file错误. 解决方案:  上传到服务器的依赖ab包可能不完整,也 ...

  4. 加载AB包(AssetBundle),通过Manifest文件,加载依赖

    加载AB包依赖,需要读取主包信息 除了我们设置的ab包之外,会自动根据文件夹名字生成一个包 自动生成的包有什么用呢,我们先简单说一下里面有什么东西吧 AssetsBundle也是一个二进制文件,Ass ...

  5. 浅谈bluebird包的使用理解

    bluebird是一个第三方Promise类库,相比其它第三方类库或标准对象来说,功能更齐全而不臃肿.浏览器兼容性更好. 我们可通过npm命令来安装:npm install bluebird 通过re ...

  6. unity 打AB包中遇到的坑以及一些心得

    unity 打AB包中遇到的坑以及一些心得 unity踩坑之旅 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 unity 打AB包中遇到的坑以及一些心得 前言 一.AB包 ...

  7. Unity中AB包详解(超详细,特性,打包,加载,管理器)

    Unity中的AssetBundle详解 AssetBundle的概念 AssetBundle又称AB包,是Unity提供的一种用于存储资源的资源压缩包. Unity中的AssetBundle系统是对 ...

  8. unity 场景ab包_Unity加载AB包

    Unity制作游戏AB包 需要注意的是在游戏场景运行的情况下,不能编译AB包,不运行的情况下编译AB包需要使用Unity的扩展菜单功能,首先需要建立菜单用来编译AB包. 1.建立AB包的名字,首先选中 ...

  9. Unity 加载AssetBundle中AB包的几种方式

    前言: 在unity中创建一个Cube,设置成预制体,生成AssetBundle中AB包,生成包代码如下: BuildPipeline.BuildAssetBundles(dir,BuildAsset ...

  10. Unity的AB包超详细+代码注释,小白一看就懂!

    基本概念 1.什么是资源? 在Unity的Assets目录下的文件,在游戏运行过程中需要从此目录加载并显示到屏幕上的都可以称之为Unity资源,例如模型.材质.纹理.预制体.脚本.声音.数据文档.动画 ...

最新文章

  1. R语言使用gganimate包可视化动画点直方图生成过程(dot histogram)、在数据遍历的过程中逐步在箱体内堆叠数据点形成最终的点直方图
  2. SAP MM 没有维护MRP 视图的物料可以正常参与采购业务
  3. jQuery开发技巧
  4. Cloud for Customer里XML view的加载原理
  5. JavaScript DOM 编程艺术 公用方法
  6. 禁止Apache列出目录内容
  7. Python实现输入电影名字自动生成豆瓣评论词云图(带GUI界面)小程序
  8. POJ 1991 Turning in Homework ★(区间DP)
  9. Javascript:学习笔记
  10. Java笔记1(2015-8-30)
  11. 使用 .NET HttpClient 下载 PDF 文件的DEMO
  12. 百战程序员 全栈软件测试课程 2022 笔记
  13. 软件设计师和软件评测师有什么区别?若想从事软件开发方面,哪个更适合?
  14. android图标分组名称唯美简单可复制,扣扣分组名称简单唯美
  15. contextcapture多区块点云_Smart 3D (ContextCapture) 4.4.6版本最新功能
  16. 土方计算过程(方格网法)
  17. 威联通nas怎么更换大硬盘_QNAP 篇一:记一次换硬盘引发的折腾
  18. python自制linux桌面,自己动手写Python实现Ubuntu自动切换壁纸
  19. vim黏贴代码格式混乱的解决方法
  20. 1、零基础学工控——初识plc

热门文章

  1. 博图安装msi失败_博途,V13,安装出现如此问题如何解决
  2. java代码自动生成
  3. 〖Python 数据库开发实战 - MySQL篇㉞〗- 综合案例 - 新闻管理系统数据库设计的基本属性
  4. 《深入解析Windows操作系统第4版》随笔记录03
  5. matlab做神经网络的步骤,matlab建立神经网络模型
  6. 数学分析学习笔记(陈纪修)
  7. JavaScript:使用js脚本写入HTML代码
  8. AutoCAD2012从入门到精通中文视频教程 第28课 文字和表格命令(1)(个人收藏)
  9. 苹果手机各种型号图片_iphone全部机型图片
  10. 卡巴斯基KEY大集合