前言

本篇文章是为了记录学习了Unity资源加载(Resource & AssetBundle)相关知识后,对于AssetBundle打包加载框架实战的学习记录。因为前一篇学习Unity资源相关知识的文章太长,所以AssetBundle实战这一块单独提出来写一篇。

基础知识学习回顾,参考:
Unity Resource Manager

鸣谢

这里AssetBundle加载管理的框架思路借鉴了Git上的开源库:
tangzx/ABSystem

同时也学习了KEngine相关部分的代码:
mr-kelly/KEngine

AssetBundle打包这一套主要是自己基于对新版(5.X以上)的AssetBundle打包机制自行编写的一套,这一套相对来说很不完善有很多缺点,个人建议读者参考Git上的其他一些开源库或者直接使用Unity官方推出的高度可视化和高度自由度打包方案:
AssetBundles-Browser

本文重点分享,参考ABSystem编写的一套AB加载管理方案实现:
基于AB引用计数的AB加载管理

AssetBundle打包

Note:
这里的AssetBundle打包主要是针对Unity 5.X以及以后的版本来实现学习的。

相关工具

  1. Unity Profiler(Unity自带的新能分析工具,这里主要用于查看内存Asset加载情况)
  2. Unity Studio(Unity AB以及Asset等资源解析查看工具)
  3. DisUnity(解析AB包的工具)

AB打包

AB打包是把资源打包成assetbundle格式的资源。
AB打包需要注意的问题:

  1. 资源冗余
  2. 打包策略
  3. AB压缩格式

资源冗余

这里说的资源冗余是指同一份资源被打包到多个AB里,这样就造成了存在多份同样资源。

资源冗余造成的问题:

  1. 余造成内存中加载多份同样的资源占用内存。
  2. 同一份资源通过多次IO加载,性能消耗。
  3. 导致包体过大。

解决方案:
依赖打包

依赖打包

依赖打包是指指定资源之间的依赖关系,打包时不将依赖的资源重复打包到依赖那些资源的AB里(避免资源冗余)。

在老版(Unity 5之前),官方提供的API接口是通过BuildPipeline.PushAssetDependencies和BuildPipeline.PopAssetDependencies来指定资源依赖来解决资源冗余打包的问题。

在新版(Unity 5以后),官方提供了针对每个Asset在面板上设置AssetBundle Name的形式指定每个Asset需要打包到的最终AB。然后通过 API接口BuildPipeline.BuildAssetBundles()触发AB打包。Unity自己会根据设置AB的名字以及Asset之间的使用依赖决定是否将依赖的资源打包到最终AB里。

这里值得一提的是Unity 5以后提供的增量打包功能。

增量打包(Unity 5以后)

增量打包是指Unity自己维护了一个叫manifest的文件(前面提到过的记录AB包含的Asset以及依赖的AB关系的文件),每次触发AB打包,Unity只会修改有变化的部分,并将最新的依赖关系写入manifest文件。

*.manifest记录所有AB打包依赖信息的文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
ManifestFileVersion: 0
CRC: 961902239
AssetBundleManifest:AssetBundleInfos:Info_0:Name: nonuiprefabs/nui_capsulesingletextureDependencies:Dependency_0: materials/mt_singletexturematerialInfo_1:Name: shaders/sd_shaderlistDependencies: {}Info_2:Name: materials/mt_singletexturematerialDependencies:Dependency_0: shaders/sd_shaderlistDependency_1: textures/tx_brick_diffuseInfo_3:Name: textures/tx_brick_diffuseDependencies: {}Info_4:Name: nonuiprefabs/nui_capsulenormalmappingtextureDependencies: {}Info_5:Name: textures/tx_brick_normalDependencies: {}Info_6:Name: materials/mt_normalmappingmaterialDependencies:Dependency_0: shaders/sd_shaderlistDependency_1: textures/tx_brick_diffuseDependency_2: textures/tx_brick_normal

问题:
虽然Unity5提供了增量打包并记录了依赖关系,但从上面的*.manifest可以看出,依赖关系只记录了依赖的AB的名字没有具体到特定的Asset。

最好的证明就是上面我把用到的Shader都打包到sd_shaderlist里。在我打包的资源里,有两个shader被用到了(SingleTextShader和NormalMappingShader),这一点可以通过UnityStudio解压查看sd_shaderlist看到:
sd_shaderlist

从上面可以看出Unity的增量打包只是解决了打包时更新哪些AB的判定,而打包出来的*.manifest文件并不能让我们得知用到了具体哪一个Asset而是AssetBundle。

解决用到哪些Asset这一环节依然是需要我们自己解决的问题,只有存储了用到哪些Asset的信息,我们才能在加载AB的时候对特定Asset做操作(缓存,释放等)。

Note:
每一个AB下面都对应一个.manifest文件,这个文件记录了该AB的asset包含情况以及依赖的AB情况,但这些manifest文件最终不会不打包到游戏里的,只有最外层生成的*.manifest文件(记录了所有AB的打包信息)才会被打包到游戏里,所以才有像前面提到的通过读取.manifest文件获取对应AB所依赖的所有AB信息进行加载依赖并最终加载出所需Asset的例子

依赖Asset信息打包

存储依赖的Asset信息可以有多种方式:

  1. 通过加载依赖AB,依赖Unity自动还原的机制实现依赖Asset加载还原(这正是本博客实现AssetBundle打包以及加载管理的方案)
  2. 存储挂载相关信息到Prefab上
    在设置好AB名字,打包AB之前,将打包对象上用到的信息通过编写[System.Serializable]可序列化标签抽象数据挂载到该Asset对象上,然后打包AB,打包完AB后在运行时利用打包的依赖信息进行依赖Asset加载还原,从而做到对Asset的生命周期掌控。
    DPInfoMono
  3. 创建打包Asset到同名AB里,加载的时候读取
    通过AssetDatabase.CreateAsset()和AssetImporter.GetAtPath()将依赖的信息写入新的Asset并打包到相同AB里,加载时加载出来使用。
    MaterialAssetInfo

打包策略

除了资源冗余,打包策略也很重要。打包策略是指决定各个Asset如何分配打包到指定AB里的策略。打包策略会决定AB的数量,资源冗余等问题。AB数量过多会增加IO负担。资源冗余会导致包体过大,内存中存在多份同样的Asset,热更新资源大小等。

打包策略:

  1. Logical entities(按逻辑(功能)分类 — 比如按UI,按模型使用,按场景Share等功能分类)
    优点:可以动态只更新特定Entity
  2. Object Types(类型分类 — 主要用于同类型文件需要同时更新的Asset)
    优点:只适用于少部分需要经常变化更新的小文件
  3. Concurrent content(加载时机分类 — 比如按Level分类,主要用于游戏里类容固定(Level Based)不会动态变化加载的游戏类型)
    优点:适合Level based这种一层内容一层不变的游戏类型
    缺点:不适合用于动态创建对象的游戏类型

打包策略遵循几个比较基本的准则:

  1. Split frequently-updated Objects into different AssetBundles than Objects that usually remain unchanged(频繁更新的Asset不要放到不怎么会修改的Asset里而应分别放到不同的AB里)
  2. Group together Objects that are likely to be loaded simultaneously(把需要同时加载的Asset尽量打包到同一个AB里)

从上面可以看出,不同的打包策略适用于不同的游戏类型,根据游戏类型选择最优的策略是关键点。

AB压缩格式

AB压缩不压缩问题,主要考虑的点如下:

  1. 加载时间
  2. 加载速度
  3. 资源包体大小
  4. 打包时间
  5. 下载AB时间
    这里就不针对压缩问题做进一步介绍了,主要根据游戏对于各个问题的需求看重点来决定选择(内存与加载性能的抉择)

AB打包相关API

  1. Selection(获取Unity Editor当前勾选对象相关信息的接口)

    1
    
    Object[] assetsselections = Selection.GetFiltered(Type, SelectionMode);
    
  2. AssetDatabase(操作访问Unity Asset的接口)

    1
    2
    3
    4
    
    // 获取选中Asset的路径
    assetpath = AssetDatabase.GetAssetPath(assetsselections[i]);
    // 获取选中Asset的GUID
    assetguid = AssetDatabase.AssetPathToGUID(assetpath);
    
  3. AssetImporter(获取设置Asset的AB名字的接口)

    1
    2
    3
    4
    5
    
    // 获取指定Asset的Asset设置接口
    AssetImporter assetimporter = AssetImporter.GetAtPath(assetpath);
    // 设置Asset的AB信息
    assetimporter.assetBundleVariant = ABVariantName;
    assetimporter.assetBundleName = ABName;
    
  4. BuildPipeline(AB打包接口)

    1
    2
    3
    
    BuildPipeline.BuildAssetBundles(outputPath, BuildAssetBundleOptions, BuildTarget); BuildPipeline.BuildAssetBundles(string outputPath, AssetBundleBuild[] builds, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform);
    

可以看到AB打包Unity 5.X提供了两个主要的接口:

  1. 前者是依赖于设置每个Asset的AB名字,然后一个接口完成增量打包的方案。
    优点:
    自带增量打包
    缺点:
    需要有一套资源AB命名规则。
    开发者可控度低。
  2. 后者是提供给开发者自定义哪些Asset打包到指定AB里的一个接口。
    优点:
    可自行实现指定需求的打包规则。
    开发者可控度高。
    缺点:
    不自带增量打包需要自己实现。

Note:
AssetBundle-Browser也是基于前者的一套打包方案,只不过AssetBundle-Browser实现了高度的Asset资源打包可视化操作与智能分析。

AB打包框架实战

这里本人实现的打包方案是使用的后者。(这一套并不完善(未写完,也有不少问题,可以简单看哈借鉴哈思路。)

主要实现了以下功能:

  1. 支持设置四种Asset打包规则(通过设置AB名字)

    • sharerule(作为共享资源,单独打成AB)
    • entirerule(作为一个整体,所有Asset用到的资源打包成一个AB)
    • mutilplerule(作为一个集合,同目录下同类型Asset打包成一个AB)
    • normalrule(作为一个普通资源,谁依赖使用它,他就打包到使用它的Asset所属打包规则的AB里)
  2. 资源划分类型,针对不同资源类型,可以自定义限制打包规则设置以及Unity格式设定检查等。
  3. 可以对内置资源打包做单独处理(比如内置shader,内置default sprite等)
  4. 每次打包生成的依赖文件信息提取出来后最后合并得出最终的依赖信息。
  5. Shader打包资源时自动记录,最后打一次Shader AB即可。

缺点与问题:

  1. 不支持增量打包,每一次打包都是单独分析的。(增量打包这一点很重要)
  2. 依赖分析是根据AssetDatabase.GetDependencies()来做的分析,看网上有提到说
    EditorUtility.CollectDependencies()获取的数据才是更正确的,依赖分析可能有问题。(这个比较好改,直接换成后者即可)
  3. normalrule设计的时候没有考虑到单个normalrule资源被多个同类型资源引用的情况(单个资源单次打包不能指定到多个AB里),这里分析出来的数据会有误(导致normalrule的资源只会被打包到某一个引用他的资源对象AB里)。(比较严重的问题,前期依赖分析设计考虑欠佳导致的)
  4. 因为没有增量打包,每一次都是单独打包,所以只支持同类型或者单个资源分析打包。
  5. 修改某个资源的打包规则会导致需要把所有用到该资源的资源全部重新打包。(很严重的问题)

基于上面的很多缺点,导致本人最终也放弃了继续写该方案的打包。但从这一次打包代码编写中加深了对AssetBundle打包的理解。后面说完AB加载管理会一起附上最后的源代码Git链接。

Note:
个人建议读者参考其他开源库或者使用Assetbundle Browser。

AssetBundle加载管理

实战学习AB加载之前让我们通过一张图先了解下AB与Asset与GameObject之间的关系:
AssetBundleFramework

依赖加载还原

还记得前面说到的依赖的Assest信息打包吗?
这里就需要加载出来并使用进行还原了。
这里接不细说加载还原了,主要就是通过存储的依赖信息把依赖的Asset加载进来并设置回去的过程(可以是手动设置回去也可以是Unity自动还原的方式)。

这里主要要注意的是前面那张大图上给出的各种资源类型在Asset加载还原时采用的方式。
资源加载还原的方式主要有两种:

  1. 复制+引用
    UI — 复制(GameObject) + 引用(Components,Tranform等)
    Material — 复制(材质自身) + 引用(Texture和Shader)

  2. 引用
    Sprite — 引用
    Audio — 引用
    Texture — 引用
    Shader — 引用
    Material — 引用

AB加载相关API

  1. AssetBundle(AB接口)

    1
    2
    3
    4
    5
    6
    
    // 加载本地压缩过的AB
    AssetBundle.LoadFromFile(abfullpath)
    // 加载AB里的指定Asset
    AssetBundle.LoadAsset(assetname);
    // 加载AB里的所有Asset
    AssetBundle.LoadAllAssets();
    

AB回收相关API

  1. AssetBundle(AB接口)

    1
    2
    3
    4
    
    // 回收AssetBundle并连带加载实例化出来的Asset以及GameObject一起回收
    AssetBundle.Unload(true);
    // 只回收AssetBundle
    AssetBundle.Unload(false);
    
  2. Resource(资源接口)

    1
    2
    3
    4
    
    // 回收指定Asset(这里的Asset不能为GameObject)
    Resource.UnloadAsset(asset);
    // 回收内存以所有不再有任何引用的Asset
    Resources.UnloadUnusedAssets();
    
  3. GC(内存回收)

    1
    2
    
    // 内存回收
    GC.Collect();
    

AB回收的方式有两种:

  1. AssetBundle.Unload(false)(基于Asset层面的重用,通过遍历判定的方式去判定Asset是否回收)
  2. AssetBundle.Unload(true)(采取索引技术,基于AB层面的管理,只有AB的引用计数为0时我们直接通过AssetBundle.Unload(true)来卸载AB和Asset资源)

接下来结合这两种方式,实战演练加深理解。

基于Asset重用的AB加载

核心思想:

  1. 打包时存储了依赖的Asset信息,加载时利用存储的依赖信息并还原
  2. 缓存加载进来的Asset的控制权进行重用,卸载AB(AssetBundle.Unload(false))
  3. 加载时通过Clone(复制类型)或者返回缓存Asset(引用类型)进行Asset重用

一下以之前打包的CapsuleNormalMappingTexture.prefab进行详细说明:
CapsuleNormalMappingPrefab.PNG
可以看出CapsuleNormalMappingTexture.prefab用到了如下Asset:

  1. NormalMappingMaterial(材质)
  2. Custom/Texture/NormalMappingShader(Shader)
  3. Brick_Diffuse和Brick_Normal(纹理)

开始加载CapsuleNormalMappingTexture.prefab:
首先让我们看看加载了CapsuleNormalMappingTexture.prefab前的Asset加载情况:
NonUIPrefabLoadedBeforeProfiler
可以看出只有Shader Asset被预先加载进来了(因为我预先把所有Shader都加载进来了)
第一步:
加载CapsuleNormalMappingTexture.prefab对应的AB,因为Prefab是采用复制加引用所以这里需要返回一个通过加载Asset后Clone的一份对象。

1
2
3
4
5
6
nonuiprefabab = loadAssetBundle(abfullname);
var nonuiprefabasset = loadMainAsset(nonuiprefabab);
nonuigo = GameObject.Instantiate(nonuiprefabasset) as GameObject;
// Asest一旦加载进来,我们就可以进行缓存,相应的AB就可以释放掉了
// 后续会讲到相关AB和Asset释放API
nonuiprefabab.Unload(false);

第二步:
还原依赖材质
DPInfoMono

1
2
3
4
5
6
7
8
9
NonUIPrefabDepInfo nonuidpinfo = nonuigo.GetComponent<NonUIPrefabDepInfo>();
var dpmaterials = nonuidpinfo.mDPMaterialInfoList;
for (int i = 0; i < dpmaterials.Count; i++)
{for(int j = 0; j < dpmaterials[i].mMaterialNameList.Count; j++){MaterialResourceLoader.getInstance().addMaterial(dpmaterials[i].mRenderer, dpmaterials[i].mMaterialNameList[j]);}
}

第三步:
还原依赖材质的Shader和Texture依赖引用
MaterialAssetInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 根据材质_InfoAsset.asset进行还原材质原始Shader以及Texture信息
var materialinfoasseetname = string.Format("{0}_{1}InfoAsset.asset", materialname, ResourceHelper.CurrentPlatformPostfix);
var materialassetinfo = loadSpecificAsset(materialab, materialinfoasseetname.ToLower()) as MaterialAssetInfo;
// 加载材质依赖的Shader
var materialshader = materialassetinfo.mShaderName;
var shader = ShaderResourceLoader.getInstance().getSpecificShader(materialshader);
material.shader = shader;// 获取Shader使用的Texture信息进行还原
var materialdptextureinfo = materialassetinfo.mTextureInfoList;
for (int i = 0; i < materialdptextureinfo.Count; i++)
{// 加载指定依赖纹理Texture shadertexture = TextureResourceLoader.getInstance().loadTexture(materialdptextureinfo[i].Value);//设置材质的对应Texturematerial.SetTexture(materialdptextureinfo[i].Key, shadertexture);
}

接下让我们看看加载了CapsuleNormalMappingTexture.prefab后的Asset加载情况:
NonUIPrefabLoadedAfterProfiler
可以看出引用的材质和纹理Asest都被加载到内存里了(Shader因为我预先把所有Shader都加载进来了所以就直接重用了没有被重复加载)
第四步:
对缓存的Asset进行判定是否回收(这里以材质为例,判定方式可能多种多样,我这里是通过判定是否有有效引用)
启动一个携程判定特定Material是否不再有有效组件(所有引用组件为空或者都不再使用任何材质)时回收Material Asset

1
Resources.UnloadAsset(materialasset);

接下来让我们看看卸载实例对象后,材质被回收的情况:
MaterialRecycleAssetNumer
MaterialRecycle
可以看到没有被引用的材质Asset被回收了,但内存里的Asset数量却明显增加了。
这里多出来的是我们还没有回收的Texture以及Prefab的GameObject以及Components Asset依然还在内存里。
AfterMaterialRecyleTextureStatus
AfterMaterialRecyleGameObjectStatus
AfterMaterialRecyleTrasformStatus
第五步:
通过切换场景触发置空所有引用将还未回收的Asset变成UnsedAsset或者直接触发Texture Asset回收,然后通过Resources.UnloadUnusedAssets()回收所有未使用的Asset

1
2
3
4
5
6
7
8
9
mNonUIPrefabAssetMap = null;foreach(var texture in mTexturesUsingMap)
{unloadAsset(texture.Value);
}
mTexturesUsingMap = null;Resources.UnloadUnusedAssets();

AfterAssetsRecyleAssetNumber
AfterAssetsRecyleTextureStatus
AfterAssetsRecyleGameObjectStatus
AfterAssetsRecyleTransformStatus
可以看到所有的Texture, GameObject, Transform都被回收了,并且Asset的数量回到了最初的数值。

基于AB引用计数的AB加载管理

这是本文重点分享的部分

方案1:
核心思想:

  1. 基于AB的引用计数 + AssetBundle.Unload(true)
  2. 给每一种资源(e.g. 特效,模型,图片,窗口等)加载都编写统一的资源加载接口(父类抽象)进行自身加载使用的AB引用计数,每个资源负责自身的资源加载管理和返还(主动调用)
  3. 由一个单例管理者统一管理所有加载AB的引用计数信息,负责判定是否可回收

优点:

  1. 严格的AB引用计数加载管理和释放
  2. 可以做到对资源对象的重用减少GC
  3. 对资源对象的重用可以减少AB的重复加载

缺点:

  1. 底层管理的内容比较多(比如基于资源对象的重用),上层灵活度欠缺(相当于对象池已经写在了最底层)
  2. 需要主动去调用返还接口(针对不同资源加载释放时机都需要编写一套对应的代码)

方案2:
核心思想:

  1. 基于AB的引用计数 + AssetBundle.Unload(true)
  2. 绑定加载AB的生命周期判定到Object上(e.g. GameObject,Image,Material等),上层无需关心AB的引用计数,只需绑定AB到对应的对象上即可
  3. 通过单例管理者统一管理判定依赖使用AB的Object列表是否都为空来判定是否可以回收,无需上层管理AB引用计数返还

优点:

  1. 上层只需关心AB加载绑定,无需关心AB引用计数返还问题,上层使用灵活度高

缺点:

  1. AB的返还判定跟绑定的Object有关,Object被回收后,AB容易出现重复加载(可以在上层写部分对象池来减少AB的重复加载)

考虑到希望上层灵活度高一些,个人现在倾向于第二种方案。

接下来基于第二种方案来实战编写资源AB加载的框架。
AB打包这一块打算先使用之前自己写的一套没有增量更新但支持一定打包规则指定的打包方案。

AB加载管理框架

以下实现主要参考了下面两个开源项目:
Unity3D AssetBundle 打包与管理系统
KEngine

框架设计

支持功能:

  1. 支持AB和AssetDatabase(限编辑器)资源加载(方便快速迭代开发,开发时用AssetDatabase(也支持快速切换AB模式))
  2. 支持AB同步和异步加载(统一采用callback风格)
  3. 支持三种基本的资源加载类型(NormalLoad — 正常加载(可通过Tick检测判定正常卸载) Preload — 预加载(切场景才会卸载) PermanentLoad — 永久加载(常驻内存永不卸载))
  4. 已加载资源允许变更资源加载类型,但只允许从低往高变(NormalLoad -> Preload -> PermanentLoad),不允许从高往低(PermanentLoad -> Preload -> NormalLoad)
  5. 基于UnityEngine.Object的AB索引生命周期绑定
  6. 底层统一管理AB索引计数,管理资源加载释放
  7. 支持卸载频率,卸载帧率门槛,单次卸载数量等设置。采用Long Time Unused First Unload(越久没用越先卸载)原则卸载。
  8. 上层编写对应资源类型Manager资源加载接口(负责提供各资源类型的加载)
  9. Manager of Manager架构设计,面向接口设计Manager,通过中介者统一注册获取(ModuleManager.GetManager的形式)
  10. 底层模块之间的解耦暂时还没想好如何设计(TODO)

简单绘制一下类图:
ModuleDesign

更多学习参考:
浅谈 Unity 开发中的分层设计

类设计:
中介者解耦Manager的类:
ModuleManager(单例类)
ModuleInterface(模块接口类)
ModuleType(模块枚举类型)

资源加载类:
ABLoadMethod(资源加载方式枚举类型 — 同步 or 异步)
ABLoadState(资源加载状态 — 错误,加载中,完成之类的)
ABLoadType(资源加载类型 — 正常加载,预加载,永久加载)
ResourceModuleManager(资源加载模块统一管理类)
AssetBundleLoader(AB资源加载任务类)
AssetBundleInfo(AB信息以及加载状态类,AB访问,索引计数以及AB依赖关系抽象都在这一层)
AssetBundlePath(AB资源路径相关 — 处理多平台路径问题)
ABDebugWindow.cs(Editor运行模式下可视化查看AB加载详细信息的辅助工具窗口)

AssetBundle管理方式是索引计数+检查有效使用对象,然后AssetBundle.Unload(true)的方式。
这里的引用计数+检查有效使用对象是指:

  1. AB被依赖加载的引用计数
  2. AB被加载后绑定到特定Object上的owner对象有效性检查

包内AB加载方式:
AssetBundle.LoadFromFile()
AssetBundle.LoadFromFileAsync()

加载管理方案:

  1. 加载指定资源
  2. 加载自身AB(自身AB加载完通知资源加载层移除该AB加载任务避免重复的加载任务被创建),自身AB加载完判定是否有依赖AB
  3. 有则加载依赖AB(增加依赖AB的引用计数)(依赖AB采用和自身AB相同的加载方式(ABLoadMethod),但依赖AB统一采用ABLoadType.NormalLoad加载类型)
  4. 自身AB和所有依赖AB加载完回调通知逻辑层可以开始加载Asset资源(AB绑定对象在这一步)
  5. 判定AB是否满足引用计数为0,绑定对象为空,且为NormalLoad加载方式则卸载该AB(并释放依赖AB的计数减一)(通知资源管理层AB卸载,重用AssetBundleInfo对象)
  6. 切场景,递归判定卸载PreloadLoad加载类型AB资源

AB加载管理相关概念:

  1. 依赖AB与被依赖者采用同样的加载方式(ABLoadMethod),但加载方式依赖AB统一采用ABLoadType.NormalLoad
  2. 依赖AB通过索引计数管理,只要原始AB不被卸载,依赖AB就不会被卸载
  3. 已加载的AB资源加载类型只允许从低往高变(NormalLoad -> Preload -> PermanentLoad),不允许从高往低(PermanentLoad -> Preload -> NormalLoad)

Note:

  1. 一个AB加载任务对应一个AssetBundleLoader
  2. 一个已加载AB信息对应一个AssetBundleInfo

Demo

  1. AB依赖信息查看界面:
    AssetBundleDepInfoUI

  2. AB运行时加载管理详细信息界面:
    AssetBundleLoadManagerUI

  3. 测试界面:
    AssetBundleTestUI

  4. 点击加载窗口预制件按钮后:

    1
    2
    3
    4
    5
    6
    
    ModuleManager.Singleton.getModule<ResourceModuleManager>().requstResource("MainWindow",
    (abi) =>
    {mMainWindow = abi.instantiateAsset("MainWindow");mMainWindow.transform.SetParent(UIRootCanvas.transform, false);
    });
    

    AssetBundleLoadManagerUIAfterLoadWindow
    可以看到窗口mainwindow依赖于loadingscreen,导致我们加载窗口资源时,loadingscreen作为依赖AB被加载进来了(引用计数为1),窗口资源被绑定到实例出来的窗口对象上(绑定对象MainWindow)

  5. 点击测试异步和同步加载按钮后:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    
    if(mMainWindow == null)
    {onLoadWindowPrefab();
    }// 测试大批量异步加载资源后立刻同步加载其中一个该源
    var image = mMainWindow.transform.Find("imgBG").GetComponent<Image>();
    ModuleManager.Singleton.getModule<ResourceModuleManager>().requstResource("tutorialcellspritesheet",
    (abi) =>
    {var sp = abi.getAsset<Sprite>(image, "TextureShader");image.sprite = sp;
    },
    ABLoadType.NormalLoad,
    ABLoadMethod.Async);ModuleManager.Singleton.getModule<ResourceModuleManager>().requstResource("Ambient",
    (abi) =>
    {var sp = abi.getAsset<Sprite>(image, "Ambient");image.sprite = sp;
    },
    ABLoadType.NormalLoad,
    ABLoadMethod.Async);ModuleManager.Singleton.getModule<ResourceModuleManager>().requstResource("BasicTexture",
    (abi) =>
    {var sp = abi.getAsset<Sprite>(image, "BasicTexture");image.sprite = sp;
    },
    ABLoadType.NormalLoad,
    ABLoadMethod.Async);ModuleManager.Singleton.getModule<ResourceModuleManager>().requstResource("Diffuse",
    (abi) =>
    {var sp = abi.getAsset<Sprite>(image, "Diffuse");image.sprite = sp;
    },
    ABLoadType.NormalLoad,
    ABLoadMethod.Async);
    

    AssetBundleLoadManagerUIAfterLoadSprites
    可以看到我们切换的所有Sprite资源都被绑定到了imgBG对象上,因为不是作为依赖AB加载进来的所以每一个sprite所在的AB引用计数依然为0.

  6. 点击销毁窗口实例对象后:

    1
    
    GameObject.Destroy(mMainWindow);
    

    AssetBundleLoadManagerUIAfterDestroyWindow
    窗口销毁后可以看到之前加载的资源所有绑定对象都为空了,因为被销毁了(MainWindow和imgBG都被销毁了)
    o

  7. 等待回收检测回收后:
    AssetBundleLoadManagerUIAfterUnloadAB
    上述资源在窗口销毁后,满足了可回收的三大条件(1. 索引计数为0 2. 绑定对象为空 3. NormalLoad加载方式),最终被成功回收。

    Note:

    读者可能注意到shaderlist索引计数为0,也没绑定对象,但没有被卸载,这是因为shaderlist是被我预加载以常驻资源的形式加载进来的(PermanentLoad),所以永远不会被卸载。

    1
    2
    3
    4
    5
    6
    
    ModuleManager.Singleton.getModule<ResourceModuleManager>().requstResource("shaderlist",
    (abi) =>
    {abi.loadAllAsset<UnityEngine.Object>();
    },
    ABLoadType.PermanentLoad);          // Shader常驻
    

    可以看到上面我们正确的实现了资源加载的管理。详细的内容这里就不再多说,感兴趣的直接去下载源码吧,这里给出Git链接:
    TonyTang1990/AssetBundleLoadManager

    Note:

    1. Git上后续实现和更新了更多的内容,这里就不一一介绍了,详情参见上面的Git链接

资源辅助工具

资源辅助工具五件套:

  • AB删除判定工具

    ​ DeleteRemovedAssetBundle

  • 资源依赖查看工具

  • ​ AssetDependenciesBrowser

  • 内置资源依赖统计工具(只统计了.mat和.prefab,场景建议做成Prefab来统计)

    ​ BuildInResourceReferenceAnalyze

  • 内置资源提取工具

    ​ 

  • BuildInResourceExtraction

  • Shader变体搜集工具

    ​ ShaderVariantsCollection

AB实战总结

  1. AB打包和加载是一个相互相存的过程。
  2. Unity 5提供的增量打包只是提供了更新部分AB的打包机制,*.manifest文件里也只提供依赖的AB信息并非Asset,所以想基于Asset的重用还需要我们自己处理。
  3. Unity并非所有的Asset都是采用复制而是部分采用复制,部分采用引用的形式,只有正确掌握了这一点,我们才能确保Asset真确的重用和回收。
  4. 打包策略是根据游戏类型,根据实际情况而定。
  5. AB压缩格式选择取决于内存和加载性能以及包体大小等方面的抉择。
  6. 资源管理策略而言,主要分为基于Asset管理(AssetBundle.Unload(false))还是基于AB管理(AssetBundle.Unload(true)),前者容易出现内存中多份重复资源,后者需要确保严格的管理机制(比如索引计数))

Reference

tangzx/ABSystem
mr-kelly/KEngine
AssetBundles-Browser

【转载】AssetBundle资源打包加载管理相关推荐

  1. AssetBundle 资源打包 加载 卸载

    1 给要打包的资源设置标记,表示对应的包名: 2 Unity5 AssetBundle不需要我们来管理引用关系了 3 可以使用代码批量设置包名 AssetImporter ai = AssetImpo ...

  2. combotree 可以异步加载吗_Unity AssetBundle 资源打包,加载,本地缓存方式,安卓,PC本地加载路径问题...

    AssetBundle的定义和作用: 1,AssetBundle是一个压缩包包含模型.贴图.预制体.声音.甚至整个场景,可以在游戏运行的时候被加载. 2,AssetBundle自身保存着互相的依赖关系 ...

  3. Unity资源加载管理

    转载链接: https://bbs.gameres.com/thread_800362_1_1.html 我理解的资源管理 举一个不恰当的例子来描述我所理解的资源管理(因为我实在想不出更合适的例子了) ...

  4. 游戏项目中的资源加载管理

    简介 资源加载时的恢复与暂停以及缓存 资源加载的相关处理 资源路径 优先级处理 发布事件 资源加载未完成与完成时的处理 总结 简介 资源加载主要需要注意以下几点: 资源加载时的缓存,停止,恢复 资源加 ...

  5. 在Unity3D的网络游戏中实现资源动态加载

    用Unity3D制作基于web的网络游戏,不可避免的会用到一个技术-资源动态加载.比如想加载一个大场景的资源,不应该在游戏的开始让用户长时间等待全部资源的加载完毕.应该优先加载用户附近的场景资源,在游 ...

  6. Kanzi学习之路(7):kanzi的资源预加载

    为了便于资源文件的管理,kanzi有着一套自己的资源文件管理系统,将所有的资源文件打包进.kzb文件中.但是资源文件又很庞大,为了加快开机速度,应用程序的响应速度,很多时候我们要选择预加载资源,多线程 ...

  7. Webpack实战(九):实现资源按需加载-资源异步加载

    第八篇[<教你搞懂webpack如果实现代码分片(code splitting)>] (https://blog.csdn.net/lfcss/article/details/104099 ...

  8. 3D引擎多线程:资源异步加载

    本文原创版权归 博客园 flagship 所有,如有转载,请详细标明作者及原文出处,以示尊重! 作者:flagship 原文:3D引擎多线程:资源异步加载 资源异步加载恐怕是3D引擎中应用最为广泛的多 ...

  9. egret白鹭引擎RES资源管理模块,资源动态加载失效BUG,加载卡死BUG,完整解决方案与超详细调试漏洞过程

    我是千里马,是一位软件工程师,最近几天完成了用户中心全套内容设计和游戏中大大小小的各种bug处理解决,准备开始游戏的正式填充,突然想起来还有两件抛之脑后的事情没有做.因为之前一直都是忙碌大方向内容设计 ...

  10. 前端资源预加载并展示进度条

    我们经常会看到,一些站点在首次进入的时候会先显示一个进度条,等资源加载完毕后再呈现页面,大概像这样: 然后整个页面的操作就会非常流畅,因为之后没必要再等待加载资源了.尤其是在移动端,或者是页游中,这样 ...

最新文章

  1. 函数式编程是啥玩意?map() reduce()(reduce()函数将数字列表转换为x进制数字)闭包、装饰器、偏函数
  2. WINCE下的MINGW交叉编译环境下内存崩溃地址的查找方法。
  3. JavaScript实现dijkstra迪杰斯特拉算法(附完整源码)
  4. 意外的服务器响应pdf,服务器安全(安骑士).pdf
  5. python取字符串一部分_python,如何获取字符串中的子字符串,部分字符串
  6. 03环信好友管理 - 删除好友
  7. 杨诚 湖南科技职业技术学院计算机,2013年全国职业院校技能大赛高职组获奖名单...
  8. 计算机科学 期刊点评,COMPUTER JOURNAL
  9. 北京驾照到期后如何在郑州换证
  10. 将uniapp打包成安卓APP
  11. 在线头像制作网站FaceYourManga
  12. Ubuntu操作系统漏洞扫描和分析
  13. 【Win8自带微软输入法删除图解】
  14. 用Python中的hashlib实现md5和sha加密
  15. 软件包管理工具snap的安装及常用命令
  16. 论文解读:Exploring Graph-structured Passage Representation for Multi-hop Reading Comprehension with Grap
  17. python开发一个web项目得需要多少行代码_用Python写个迷你出门问问|10几行代码搞定...
  18. linux给文件夹及子文件(夹)授权
  19. ubuntu18安装cuda10.1
  20. 电快速瞬变脉冲群实验(内部电源设备)

热门文章

  1. 社区保密计算机使用制度,社区保密工作制度
  2. java 牙位图插件_牙医的骄傲-智能牙位图中文医疗应用app全球排名No.1
  3. 有了这两本书,学习领域驱动设计会很容易
  4. Photoshop插件-HDR(四)-脚本开发-PS插件
  5. 电力电子课设日志(已完结)
  6. fedora linux五笔输入法,教你在Fedora 14 下安装五笔输入法
  7. 疯狂Java讲义(一)
  8. android刷步工具,公益步数刷步助手
  9. 几分钟,就看到人性的几个面孔
  10. AXJ爱新机 亚马逊测评 替代软件-VMLogin反指纹超级浏览器