工具
- Unity版本(2022.3.62f3)
- Visual Studio 2022 或 Visual Studio Code
基于索引计数+组件绑定的AssetBundle加载管理框架。(参考: tangzx/ABSystem思路)
资源加载管理设计:
- 支持Asset名字(含后缀)的资源名加载方式。
- 面向Asset级别加载管理,支持Asset和AssetBundle级别的同步异步加载。
- 支持资源导入后配置打包策略+更新EditorAssetInfoAsset后AssetDatabase模式马上就能通过Asset名(含后缀)代码加载
- 资源加载类型只提供普通和常驻两种(且不支持运行时切换相同Asset或AssetBundle的加载类型,一旦第一次加载设定了类型,除非卸载后再次加载否则无法修改资源加载类型(常驻资源推荐一开始就手动加载)。提供统一的加载管理策略,细节管理策略由上层自己设计(比如对象池,预加载)
- 异步加载准备采用监听回调的方式来实现,保证流程清晰易懂
- 设计请求UID(通过资源请求句柄封装)的概念来支持加载打断设计(仅逻辑层面的打断,资源加载不会打断,当所有逻辑回调都取消时,加载完成时会返还索引计数确保资源正确卸载)
- 设计上支持动态AB下载(未支持,未来填坑)
- 加载流程重新设计,让代码更清晰
- 保留索引计数(Asset和AssetBundle级别)+对象绑定的设计(Asset和AssetBundle级别)+按AssetBundle级别卸载(依赖还原的Asset无法准确得知所以无法直接卸载Asset)+加载触发就提前计数(避免异步加载或异步加载打断情况下资源管理异常)
- 支持非回调式的同步加载返回(通过抽象Loader支持LoadImmediately的方式实现)
Note:
- 一直以来设计上都是加载完成后才添加索引计数和对象绑定,这样对于异步加载以及异步打断的资源管理来说是有漏洞的,资源加载管理准备设计成提前添加索引计数,等加载完成后再考虑是否返还计数的方式确保异步加载以及异步加载打断的正确资源管理
加载流程设计主要参考:
对象绑定加索引计数设计主要参考:
加载管理方案:
- 加载指定资源名(含后缀)
- 加载自身AB(自身AB加载完通知资源加载层移除该AB加载任务避免重复的加载任务被创建),自身AB加载完判定是否有依赖AB
- 有则加载依赖AB(增加依赖AB的引用计数)(依赖AB采用和自身AB相同的加载方式(ResourceLoadMethod),但依赖AB统一采用ResourceLoadType.NormalLoad加载类型)
- 自身AB和所有依赖AB加载完回调通知逻辑层可以开始加载Asset资源(AB绑定对象在这一步)
- 判定AB是否满足引用计数为0,绑定对象为空,且为NormalLoad加载方式则卸载该AB(并释放依赖AB的计数减一)(通知资源管理层AB卸载,重用AssetBundleInfo对象)
- 切场景,递归判定卸载NormalLoad加载类型AB资源(上层使用逻辑调用接口触发)
相关设计:
- 依赖AB与被依赖者采用同样的加载方式(ResourceLoadMethod),但加载方式依赖AB统一采用ResourceLoadType.NormalLoad
- 依赖AB通过索引计数管理,只要原始AB不被卸载,依赖AB就不会被卸载
- 已加载的AB资源加载类型不允许改变(直到加载后被卸载再次加载才能切换)
上层逻辑资源加载计数,资源释放和请求取消统一封装设计:
- 提供上层特定上下文资源加载计数统计和统一的资源释放和资源加载请求取消机制(ResourceScope类)。
先打开资源调试工具
Tools->Debug->资源调试工具
-
AB依赖信息查看界面
-
AB运行时加载管理详细信息界面
-
加载器信息查看界面
-
测试界面
-
点击加载窗口预制件按钮后:
ResourceManager.Singleton.getPrefabInstance( "MainWindow.prefab", (prefabInstance, assetRequestHandle) => { mMainWindow = prefabInstance; mMainWindow.transform.SetParent(UIRootCanvas.transform, false); }, mResourceScope );
可以看到窗口mainwindow依赖于loadingscreen,导致我们加载窗口资源时,loadingscreen作为依赖AB被加载进来了(引用计数为1),窗口资源被绑定到实例出来的窗口对象上(绑定对象MainWindow) -
点击测试异步转同步加载窗口
/// <summary>
/// 测试异步转同步窗口加载
/// </summary>
public void onAsynToSyncLoadWindow()
{
DIYLog.Log("onAsynToSyncLoadWindow()");
if (mMainWindow == null)
{
onDestroyWindowInstance();
}
AssetLoader assetLoader;
var assetRequestHandle = ResourceManager.Singleton.getPrefabInstanceAsync(
"MainWindow.prefab",
out assetLoader,
(prefabInstance, assetRequestHandle) =>
{
mMainWindow = prefabInstance;
mMainWindow.transform.SetParent(UIRootCanvas.transform, false);
},
mResourceScope
);
// 将异步转同步加载
assetLoader.loadImmediately();
}- 点击销毁窗口实例对象后
/// <summary>
/// 销毁窗口实例对象
/// </summary>
public void onDestroyWindowInstance()
{
DIYLog.Log("onDestroyWindowInstance()");
GameObject.Destroy(mMainWindow);
}
窗口销毁后可以看到之前加载的资源所有绑定对象都为空了,因为被销毁了(MainWindow被销毁了)Note:
读者可能注意到shaderlist索引计数为0,也没绑定对象,但没有被卸载,这是因为shaderlist是被我预加载以常驻资源的形式加载进来的(PermanentLoad),所以永远不会被卸载。
/// <summary>
/// 加载常驻Shader
/// </summary>
public void onLoadPermanentShaderList()
{
DIYLog.Log("onLoadPermanentShaderList()");
ResourceManager.Singleton.loadAllShader(() =>
{
},
mResourceScope,
ResourceLoadType.PermanentLoad);
}AB打包主要参考MotionFramework里的AB打包思路,细节部分个人做了一些改动和扩展。
AB打包设计:
- 打包AB的策略由抽象的目录打包策略设定决定
- 打包后的AB保留目录结构,确保AB模式和AssetDatabase模式加载都面向Asset路径保持一致性
- 支持打包策略级别的AB压缩格式设置(Note: 仅限使用ScriptableBuildPipeline打包模式)。老版AB打包流程AB压缩格式默认由打包面板压缩格式设置决定。
- 不支持AB变体功能(ScriptableBuildPipeline也不支持变体功能),AB后缀名统一由打包和加载平台统一添加
- 老版AB依赖信息采用原始打包输出的*Manifest文件。新版ScriptableBuildPipeline采用自定义输出打包的CompatibilityAssetBundleManifest文件。
- 打包面板里支持了是否支持代码加载的勾选(用于支持文件名(含后缀)的资源加载和优化需要支持代码主动加载而生成的AssetBuildInfoAsset.asset(AB模式)和EditorAssetInfoAsset.asset(AssetDatabase模式)的数据量问题)
-
按目录打包(打包策略递归子目录判定)
-
按文件打包(打包策略递归子目录判定)
-
按固定名字打包(扩展支持固定名字打包--比如所有Shader打包到shaderlist)(打包策略递归子目录判定)
-
按文件或子目录打包(打包策略递归子目录判定,设定目录按文件打包,其他下层目录按目录打包)
-
不参与打包(打包策略递归子目录判定)
这里先简单的看下新的AB搜集和打包界面:
AB模式下关于Asset名,Asset路径和AB路径关联信息存在一个叫AssetBuildInfoAndroid.asset(不同平台名字不一样)的ScriptableObejct里(单独打包到assetBuildInfo的AB里),通过Asset名如何加载到对应AB的关键就在这里。这里和MotionFramework自定义Manifest文件输出不一样,AssetBuildInfoAndroid.asset只记录Asset名,Asset路径和AB相关信息映射,不记录AB依赖信息,依赖信息依然采用AB打包生成的*Manifest文件,同时AssetBuildInfoAndroid.asset采用打包AB的方式(方便和热更新AB走一套机制)
让我们先来看下大致数据信息结构:
AssetDatabase模式下关于Asset名,Asset路径信息存储在EditorAssetInfoAsset.asset的ScriptableObject里(通过Tools>AssetBundle->更新EditorAssetInfoAsset触发更新)
2022/1/26支持了资源打包后缀名黑名单可视化配置+资源名黑名单可视化配置
2023/2/8底层支持了新版ScriptableBuildPipeline打包工具打包,加快打包速度(默认使用SBP,修改成老版打包需添加OLD_ASSET_BUILD_PIPELINE宏)
Note:
- 注意将Assets/Res/assetbuildinfo目录设置成不参与打包的打包策略,此文件会在打包时独立设置打包。
热更类:
- HotUpdateModuleManager.cs(热更新管理模块单例类)
- TWebRequest.cs(资源下载http抽象类)版本信息类:
- VersionConfigModuleManager.cs(版本管理模块单例类)
- VersionConfig.cs(版本信息抽象类)- 支持游戏内版本强更(完成 -- 暂时限Android,IOS待测试)
- 支持游戏内资源热更(完成 -- 暂时限Android, IOS待测试)
- 支持游戏内代码热更(未支持,HybridCLR待学习)
使用阿里的ISS静态资源服务器做了一个网络端的资源服务器。
版本强更流程:
- 比较包内版本信息和包外版本信息检查是否强更过版本
- 如果强更过版本清空包外相关信息目录
- 通过资源服务器下载最新服务器版本信息(ServerVersionConfig.json)和本地版本号作对比,决定是否强更版本
- 结合最新版本号和资源服务器地址(Json配置)拼接出最终热更版本所在的资源服务器地址
- 下载对应版本号下的强更包并安装
- 安装完成,退出游戏重进
资源热更流程:
-
初始化本地热更过的资源列表信息(暂时存储在:Application.persistentDataPath + "/ResourceUpdateList/ResourceUpdateList.txt"里)
-
通过资源服务器下载最新服务器版本信息(ServerVersionConfig.json)和本地资源版本号作对比,决定是否资源热更
-
结合最新版本号,最新资源版本号和资源服务器地址(Json配置)拼接出最终资源热更所在的资源服务器地址
-
下载对应地址下的AssetBundleMD5.txt(里面包含了对应详细资源MD5信息)
AssetBundleMD5.txt
assetbuildinfo.bundle|ca830d174533e87efad18f1640e5301d shaderlist.bundle|2ac2d75f7d91fda7880f447e21b2e289 ******
-
根据比较对应地址下的AssetBundleMD5.txt里的资源MD5信息和本地资源MD5信息(优先包外的MD5文件)得出需要更新下载的资源列表
-
根据得出的需要更新的资源列表下载对应资源地址下的资源并存储在包外(Application.persistentDataPath + "/Android/"),同时写入最新的资源MD5信息文件(本地AssetBundleMD5.txt)到本地
-
直到所有资源热更完成,退出重进游戏
问题:
- 上述方案包外AssetBundleMD5.txt文件可能被篡改,其次热更下载的AB文件可能出现损坏但被记录到包外AssetBundleMD5.txt的情况,这会导致热更AB出现不可逆的热更问题。
解决方案:
- 包外资源采用实时计算MD5,然后结合包内AssetBundleMD5.txt数据与热更AssetBundleMD5.txt文件进行对比决定哪些资源需要热更。(TODO)
Tools->HotUpdate->热更新操作工具
-
主要分为以下2个阶段:
-
热更新准备阶段:
- 每次资源打包会在包内Resource目录生成一个AssetBundleMd5.txt文件用于记录和对比哪些资源需要热更
- 执行热更新准备操作,生成热更新所需服务器最新版本信息文件(ServerVersionConfig.json)并将包内对应平台资源拷贝到热更新准备目录
-
热更新判定阶段
-
初始化包内(AssetBundleMd5.txt)和包外(AssetBundleMd5.txt)热更新的AssetBundle MD5信息(先读包内后读包外以包外为准)
-
游戏运行拉去服务器版本和资源版本信息进行比较是否需要版本强更或资源热更新
-
需要资源热更新则拉去对应最新资源版本的资源MD5信息文件(AssetBundleMD5.txt)进行和本地资源MD5信息进行比较判定哪些资源需要热更新
-
拉去所有需要热更新的资源并写入最新的资源MD5信息到包外,完成后进入游戏
-
Note:
- 每次打包版本时会拷贝一份AssetBundleMD5.txt到打包输出目录(保存一份方便查看每个版本的资源MD5信息)
-
PersistentAsset -> HotUpdate -> Platform(资源热更新目录)
PersistentAsset -> HotUpdate -> AssetBundleMd5.txt(记录热更新的AssetBundle路径和MD5信息--兼顾进游戏前资源热更和动态资源热更)(格式:热更AB路径:热更AB的MD5/n热更AB路径:热更AB的MD5******)
PersistentAsset -> HotUpdate-> Config -> VersionConfig.json(包外版本信息--用于进游戏前强更和热更判定)
PersistentAsset -> HotUpdate -> Download -> 版本强更包
- 支持资源依赖统计(不限资源类型)
- 支持内置资源引用分析
- 支持内置资源提取(限材质和纹理,不包含Shader是考虑到Shader可以自行下载)
- 支持shader变体搜集(半成品)
资源辅助工具五件套:
Tools->AssetBundle->AssetBundle操作工具
Tools->Assets->Asset相关处理
-
资源依赖查看工具
-
内置资源依赖统计工具(只统计了*.mat和*.prefab,场景建议做成Prefab来统计)
-
内置资源提取工具
-
Shader变体搜集工具(接入YooAsset的变体搜集工具)
核心思想是搜集所有需要参与打包的材质球(含被动依赖不在打包策略内的),然后通过单独的场景和摄像机去照射让ShaderVariantCollection去搜集
- Unity Hub启动时记着Window添加-force-gles命令,因为默认是打包的Android平台AB,不然AB模式会显示粉色
- AB打包和加载默认使用SBP,修改成老版打包需添加OLD_ASSET_BUILD_PIPELINE宏
- 老版AB打包只支持Asset小写全路径,所以针对老版AB打包和加载Asset本人都统一成了小写处理
- 为了支持SpriteAtla和Sprite都能直接通过代码加载,需要将SpriteAtlas和参与打包的Sprite打包到一起
- TImage和TRawImage提供了快速设置Sprite和Texture的相关接口(比如TImage.SetSingleSprite()和TRawImage.SetRawImage())
- 修复资源打包在2020和2021版本会报错(BuildPipeline error is thrown when building Asset Bundles](https://issuetracker.unity3d.com/issues/buildpipeline-error-is-thrown-when-building-asset-bundles))问题(2022/06/03)
- 将面向Asset路径加载的方式改造成支持Asset名(含后缀)的加载方式(2026/07/14)
- 支持SubAsset的加载(比如Multiple Sprite)通过计数和对象绑定到主Asset实现SubAsset的加载和计数,详情参考AtlasManager.SetTImageSubSprite()方法(2026/07/21)
- 支持了特定上下文(比如窗口生命周期)的资源加载计数统计+资源计数释放+资源请求取消机制(ResourceScope类)。(2026/07/25)
1. 支持类似Multiple Sprite这种SubAsset的加载(设计之初考虑的不够全面(无论是打包还是加载都是面向Asset级别的,导致SubAsset这种无论是打包还是加载都给不出有效Asset路径),导致SubAsset这种资源无法主动加载到)⭐⭐⭐⭐⭐
大框架不改的前提下,目前想到的最快速的方案是AssetLoader和AssetInfo都支持获取SubAsset的相关同步接口,将计数和对象绑定都绑在主Asset身上
2. 支持真机代码热更(Lua + XLua)
3. 热更新资源正确性校验(MD5校验)
详细的博客记录学习:
感谢tangzx/ABSystem作者的无私分享,tangzx/ABSystem的Github链接:
感谢MotionFramework作者的无私分享,MotionFramework的Github链接:
感谢XAsset作者的无私分享,XAsset的GitHub链接:















