AB包

1.AB包内部结构

AB包简单说就是一串二进制数据,这串数据存的是资源(预制、材质、贴图、模型等)。这串数据分为:

1.头文件

AssetbundleFileHeader

记录这个AB的信息:版本号、压缩格式等信息。

AssetFileHeader

记录AB的文件信息(一个文件列表、记录了每个资源的name、offset、length等信息,后面读取具体资源的使用用)

2.具体的资源

头文件后面跟着的是n段数据,一段数据就是一个资源(也有一些信息、其中比较重要的就是资源类型id(0:Object、1:GameObject等)、对外部资源的引用情况)。

2..mainfest文件

.mainfest是一个文本文件里面有ab包的校验码,依赖以及资源内容等相关信息,这个信息是给开发读的,同时这个信息也作为二进制打入了ab包里面,所以使用ab包资源的时候,可以不用带.manifest文件。

 内容:

`ManifestFileVersion: 0 # 文件版本
CRC: 2657307167 # CRC校验码
Hashes: # 哈希
  AssetFileHash: # AB包中所有资源的哈希,可用于增量更新检测
    serializedVersion: 2 # Unity序列化版本
    Hash: 717e408ba50ee41b0960161fd2d5a827
  TypeTreeHash: # AB包中所有类型的哈希,可用于增量更新检测
    serializedVersion: 2 # Unity序列化版本
    Hash: 8d552bf2f5bdba1177c938cb98ca6f2f
HashAppended: 0
ClassTypes: # TypeTree
- Class: 1 # GameObject
  Script: {instanceID: 0}
- Class: 21 # Material
  Script: {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` 

  

3.AB变体

简单说就是一个资源的分类标签,如同一个图片的高清和低清资源。

4.压缩格式

LZMA和LZ4

LZMA需要完整解压之后才能加载包内资源(但是压缩出来的包体小),LZ4不需要完整解压就可以加载包内资源(数据被分为大小相同的块,并被分别压缩,加载时只加载Header,用哪块的数据就解压对应的块就可以,内存占用小)。

默认使用LZMA压缩方式,因为第一次解压之后,该包又会使用LZ4再次压缩。(这就是为什么第一次加载时间长,后面就没这么长了)

5.获取AB包

AssetBundle.LoadFromFile(string path)
AssetBundle.LoadFromFileAsync(string path)

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

AssetBundle.LoadFromStream(Stream stream)
AssetBundle.LoadFromStreamAsync(Stream stream)

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

AssetBundle.LoadFromMemory(byte[] binary)
AssetBundle.LoadFromMemoryAsync(byte[] binary)

一般是网络接口用,LoadFromMemory是从内存中加载AB包,它从内存中的byte[]中加载AB包。它会完整的把AB包加载出来,适用于ab包已经被加载进内存的情况(参数是byte数组)。

6.内存占用

 现在的主流做法是在进游戏之前下载热更的包并存在本地,所以不会有WebStream这部分,当然,如果是运行时下载资源就还是要用到这个。

Assetbundle的内存占用:压缩的AB包 + 解压缩的AB包(也就是具体的资源,LZ4的不会全解,只解要用的部分)+ 实例化的资源。

 

7.资源卸载

1.卸载ab包

1.ab.Unload(false) : 卸载掉紫色部分(压缩的AB包)。会解除最上面部分(实例)和最下面部分(ab包)的链接关系(Instance ID的GUID和Local ID引用变无效,其实也就是ab解出来的资源和ab关联没了),再次加载一个紫色部分,原来的绿色部分不会受新加载的紫色部分的管理。因此如果不注意的话可能会导致一些不可控的问题,Unity中有Resources.UnloadUnusedAssets()方法可以很好地解决这个问题。

2.ab.Unload(true) : 卸载掉紫色部分(压缩的AB包)以及绿色部分(具体的Asset),注意这个举动可能会引发最上面的区域(实例化区)引用的资源丢失

2.卸载ab包中解出来的资源

1.Resources.UnloadUnusedAssets : 卸载掉没有引用的绿色部分(压缩的AB包)。

2.Resources.UnloadAsset():卸载Asset,但只能用来卸载具体资源(文本、音频、贴图),对于GameObject、Assetbundle内部的Assetbundle、脚本、Component都不会卸载。

项目中用的卸载方法是:

1.多数假跳转场景:只清除资源缓存、ab缓存,确保ab和资源没有引用,不调用ab.UnLoad。

2.少数真跳转场景(战斗、关卡等):清除资源缓存、ab缓存,确保ab和资源没有引用,unity切场景会自动调用ab.Unload和Resources.UnloadUnusedAssets

3.游戏退出:所有资源、ab包卸载。

8.依赖问题

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

所以要尽量避免加载A包之前还要加载B包的问题。

在Unity5.0后,BuildAssetBundleOptions.CollectDependencies永久开启,即Unity会自动检测物体引用的资源并且一并打包,防止资源丢失遗漏的问题出现,但会导致资源冗余(同一份资源被打进不同的包中)。

最好就是将一些公共资源单独打到一个ab包中,并提前加载完成,尽可能减少AB包的依赖。

todo Tools.AnarysicsResources

9.细粒度问题

细粒度问题即每个AB包分别放入多少资源的问题,一个好的策略至关重要。
加载资源时,先要加载AB包,再加载资源。如果AB包使用了LZMA或LZ4压缩算法,还需要先给AB包解压。
AB包数量较多,包内资源较少
AB包数量较少,包内资源较多
加载一个AB包到内存的时间短,玩家不会有卡顿感,但每个资源实际上加载时间变长。
加载一个AB包到内存的时间较长,玩家会有卡顿感,但之后包内的每个资源加载很快。
热更新灵活,要更新下载的包体较小。
热更新不灵活,要更新下载的包体较大。
IO次数过多,增大了硬件设备耗能和发热压力。
IO次数不多,硬件压力小。

策略:

1.按照类型:根据文件夹,比如模型、图片、动画。

2.按照功能:同一个模型文件夹下可能有场景资源、人物资源,根据不同的功能进行打包。

3.按照模块:比如我们玩家模型1-10级,或者不同的极端需要不同的模型,我们就按照模块进行打包。

4.公共的资源单独打一个包。(公共资源:声音、shader等)

5.每个特效和他所依赖的材质贴图打成一个包。

6.代码可以分功能打包(不超过3m)

7.同时使用的尽量打一个包,不同时使用的尽量打成两个包。

8.经常更新和不经常更新的对象拆分到不同的AB包中。

9.依赖文件根据上面说的就行

10.资源校验

验证本地文件的hashcodeunity的hashcode(这两个code是固定的,不会随着设备的不同而变化)

散列算法就是一种以较短的信息来保证文件唯一性的标志,这种标志与文件的每一个字节都相关,而且难以找到逆向规律。

也就是说这两个code变了,这个文件也就变了(损坏了)。

11.meta文件和asset的文本

meta文件主要是记录文件的GUID、文件导入设置、所在ab包以及依赖的ab包。guid是唯一的,可以通过guid找到这个文件。

unity的资源都可以使用文本打开,可以通过文本来清晰的看到资源的引用关系。

比如:

 这是一个材质的文本,右边的图是将材质的贴图从空改成一个图集的某一张图片

可以看到m_Texture的值变成了fileID - guid-...。

fileID:在资源内部的子成员的定位Id,比如赋过来的这张图集这个里面有很多张图片,每张图片对应一个fileID,通过fileID和GUID就可以找到该图集下的这张图片。

fileID + GUID可以定位到某个文件的某个子文件。

InstanceID:虽然GUID和Local ID比较好用,但是毕竟因为存在磁盘上,读取比较耗时。因此Unity缓存一个instance ID对应Object,通过instance ID快速找到Object。instance ID是一种快速获取对象实例的ID,包含着对GUID和Local ID的引用。

12.打ab包工具

https://naotu.baidu.com/file/33e98b92b2d522ee9fba22be1bcc7330

13.一些问题

material的资源实例在加载的时候要检查其shader是否支持(material.shader.isSupported),如果不支持指定shader,就从这个包里找一个能用的shader赋上(苟一下,防止unity闪退),并报错,基本这种问题在上线前就被QA测出来了修掉了。

ParticleSystem的material(可能是第三方的),要在检测的回调中检测是否是粒子,是的话要检查其shader是否支持,不支持的话要找一个能用的shader放上(苟一下,防止unity闪退),并报错,基本这种问题在上线前就被QA测出来了修掉了。

posted @ 2023-12-27 20:04  mc宇少  阅读(96)  评论(0编辑  收藏  举报