当前位置: 首页 > news >正文

MyFramework:ResourceManager 资源加载体系解析

MyFramework 中的ResourceManager不是简单封装LoadAsset

它主要负责统一几件事:

编辑器加载 AssetBundle 加载 资源路径规则 资源引用管理 异步加载回调 AssetBundle 依赖 资源下载 资源卸载

项目地址:

https://github.com/ZHOURUIH/MyFramework


一、定位

ResourceManager是框架中的资源入口。

外部加载资源时,不直接关心当前资源来自哪里。

编辑器下可以走AssetDatabase

打包后强制走AssetBundle

调用层只需要使用统一接口:

loadGameResource<T>() loadGameResourceAsync<T>() loadSubGameResource<T>() getGameResource<T>() unload<T>() unloadPath()

这样业务层不会到处写AssetDatabase.LoadAssetAtPathResources.LoadAssetBundle.LoadAsset

加载源变化时,业务代码不用改。


二、加载源

ResourceManager内部有两个加载器:

protected AssetDataBaseLoader mAssetDataBaseLoader = new(); protected AssetBundleLoader mAssetBundleLoader = new();

编辑器下根据配置选择加载源:

mLoadSource = isEditor() ? GameEntryBase.getInstance().mFrameworkParam.mLoadSource : LOAD_SOURCE.ASSET_BUNDLE;

打包后固定使用AssetBundle

这点很重要。

编辑器阶段可以为了开发效率走AssetDatabase,但最终运行环境必须按真实资源包流程验证。


三、路径规则

MyFramework 中资源路径统一使用GameResources下的相对路径。

例如:

UI/UIPrefab/UILogin.prefab Texture/Icon/item_1001.png Audio/BGM/main.mp3

路径要求:

必须带后缀 不能传绝对路径 不能以 Assets 开头 不能以 Assets/GameResources 开头 不能使用反斜杠

对应检查在checkRelativePath中完成。

这样做的目的很明确:

业务代码只使用工程内统一的资源逻辑路径,不直接依赖 Unity 工程路径。

资源最终来自AssetDatabase还是AssetBundle,由ResourceManager决定。


四、初始化

如果加载源是AssetBundle,框架启动时会先初始化资源清单。

入口是:

preInitAsync()

逻辑是:

ResourceManager.preInitAsync ↓ AssetBundleLoader.initAssets ↓ 加载 StreamingAssets 配置文件 ↓ 解析 AssetBundle、Asset、依赖关系 ↓ 建立资源名到 AssetBundle 的索引

如果不是AssetBundle加载,直接回调完成。

所以资源系统初始化阶段最关键的事情是:

先把资源清单读出来。

否则后面通过资源名加载时,无法知道这个资源在哪个 AssetBundle 里。


五、资源清单

AssetBundleLoader中有几个核心索引:

protected Dictionary<string, AssetBundleInfo> mAssetBundleInfoList = new(); protected Dictionary<string, AssetInfo> mAssetToBundleInfo = new();

含义是:

mAssetBundleInfoList AssetBundle 名 -> AssetBundleInfo mAssetToBundleInfo 资源路径 -> AssetInfo

初始化时会解析配置文件。

配置文件中记录:

AssetBundle 名 AssetBundle 包含的资源列表 AssetBundle 依赖项

解析完成后,还会调用:

findAllDependence()

把依赖关系从名字转换成AssetBundleInfo引用。

这样后续加载资源时,可以直接从资源路径找到所属 AssetBundle,再处理依赖加载。


六、同步加载

同步加载入口是:

public ResourceRef<T> loadGameResource<T>(string name, bool errorIfNull = true) where T : UObject

内部流程:

检查路径 ↓ 根据加载源选择加载器 ↓ AssetDatabase 加载或 AssetBundle 加载 ↓ 加载成功后创建 ResourceRef<T> ↓ 返回资源引用对象

AssetBundle模式下,资源加载过程是:

资源路径 ↓ mAssetToBundleInfo 找到 AssetInfo ↓ AssetInfo 找到 AssetBundleInfo ↓ 先加载依赖包 ↓ 加载当前 AssetBundle ↓ LoadAssetWithSubAssets ↓ 返回主资源

同步加载不只是加载单个资源。

它会确保资源所属的 AssetBundle 已经加载,并且依赖包也已经加载。


七、异步加载

异步加载入口有多种重载:

loadGameResourceAsync<T>(string name, Action<ResourceRef<T>> callback) loadGameResourceAsync<T>(string name, Action<ResourceRef<T>, string> callback) loadGameResourceAsync<T>(string name, AssetRefLoadCallback<T> callback)

底层统一走:

loadGameResourceAsyncInternal<T>()

AssetBundle模式下异步流程:

资源路径 ↓ 找到 AssetInfo ↓ 找到 AssetBundleInfo ↓ 如果 AssetBundle 未加载,先异步加载依赖 ↓ 等待依赖加载完成 ↓ 加载当前 AssetBundle ↓ 加载 Asset ↓ 回调 ResourceRef<T>

AssetBundleInfo中用mLoadAsyncList保存资源包未加载完成时发起的资源请求。

资源包加载完成后,再统一触发这些资源的异步加载。


八、安全加载

异步加载最常见的问题是:

资源还没加载完,对象已经销毁

MyFramework 提供了安全加载接口:

loadGameResourceAsyncSafe<T>(IRecyclable relatedObj, string name, Action<ResourceRef<T>> callback)

它会记录对象当前的AssignID

回调时再次比较:

加载发起时的 AssignID 是否等于 回调时对象当前 AssignID

如果不一致,说明对象已经被销毁或复用。

这时不会执行回调,并且会卸载已经加载出的资源。

这个设计适合 UI、角色、特效等对象的异步资源加载。

对象销毁后,不需要在每个回调里手动判断一堆状态。


九、资源引用

MyFramework 没有直接把UnityEngine.Object裸返回给业务层,而是返回:

ResourceRef<T>

ResourceRef<T>中保存:

protected T mResource; protected long mToken;

资源加载成功后,会调用:

mResourceManager.addReference(mResource)

生成一个引用凭证token

ResourceManager内部记录:

protected Dictionary<int, HashSet<long>> mReferenceTokenList = new(); protected Dictionary<int, UObject> mInstanceIDToUObject = new();

这里使用的是GetInstanceID(),不是直接使用UObject做 Key。

原因是 Unity 的Object重载了==

外部如果把资源卸载掉,可能出现对象引用状态异常,但GetHashCode不变。

所以这里使用InstanceID来追踪资源引用。


十、引用释放

资源释放方式是:

mResourceManager.unload(ref resRef);

它内部会回收ResourceRef<T>

ResourceRef<T>.destroy()中会调用:

mResourceManager.removeReference(mResource, ref mToken)

资源引用凭证移除后,ResourceManager会定时检查:

protected const float CHECK_REF_INTERVAL = 3.0f;

当某个资源的引用凭证列表为空,就会调用内部卸载逻辑。

这套设计的重点是:

资源卸载不依赖业务层直接传 Unity 对象,而是依赖 ResourceRef 的生命周期。

资源谁持有,谁释放。

引用都释放后,资源才进入卸载流程。


十一、AssetBundle 依赖

AssetBundleInfo中保存两类依赖:

protected Dictionary<string, AssetBundleInfo> mParents = new(); protected Dictionary<string, AssetBundleInfo> mChildren = new();

含义是:

mParents 当前包依赖的 AssetBundle mChildren 依赖当前包的 AssetBundle

加载当前包之前,会先加载所有依赖包。

同步加载时:

foreach (var item in mParents) { item.Value.loadAssetBundle(); }

异步加载时:

loadParentAsync();

卸载时也要看依赖关系。

一个 AssetBundle 只有在满足两个条件时才能卸载:

当前包内资源没有正在使用 没有其他正在使用的 AssetBundle 依赖自己

对应逻辑在canUnload()中。

这可以避免依赖包被提前卸载,导致其他包中的资源引用异常。


十二、延迟卸载

AssetBundleInfo中有一个延迟卸载时间:

protected const float UNLOAD_DELAY_TIME = 5.0f;

当包内资源没有引用时,不是立刻卸载,而是延迟 5 秒。

原因是资源可能很快又被重新加载。

立即卸载会造成频繁 Load / Unload。

延迟卸载可以减少抖动。

逻辑大致是:

资源引用清空 ↓ 设置 mWillUnloadTime = 5 秒 ↓ update 中倒计时 ↓ 倒计时结束后再次检查 canUnload ↓ 满足条件才真正卸载

这比引用一清空就立刻卸载更稳。


十三、资源下载

AssetBundleLoader支持资源包动态下载。

当本地找不到某个 AssetBundle 文件时,会从下载地址请求:

mDownloadURL + bundleFileName

下载完成后,如果不是 WebGL,会写入本地持久化目录:

PersistentAssets

并更新本地文件列表:

mAssetVersionSystem.addPersistentFile(fileInfo); writeFileList(...)

这样下载后的资源包可以进入本地资源版本管理。

下载流程主要服务热更新资源。


十四、编辑器加载

编辑器加载由AssetDataBaseLoader负责。

编辑器下使用:

loadAssetAtPath<T>() loadAllAssetsAtPath()

非编辑器下备用Resources.Load,但打包后ResourceManager默认强制使用AssetBundle

AssetDataBaseLoader也会缓存已加载资源:

protected Dictionary<string, Dictionary<string, AssetDataBaseLoadInfo>> mLoadedPath = new(); protected Dictionary<UObject, AssetDataBaseLoadInfo> mLoadedObjects = new();

这样编辑器模式和 AssetBundle 模式都能走ResourceManager的统一接口。

开发阶段不需要每次都打 AssetBundle。

打包环境又可以按真实 AssetBundle 流程运行。


十五、子资源

有些资源不只是一个主资源。

例如:

SpriteAtlas FBX 包含多个子资源的文件

MyFramework 提供:

loadSubGameResource<T>()

它会返回子资源数组,同时返回主资源引用:

UObject[] loadSubGameResource<T>(string name, out ResourceRef<UObject> mainAsset)

这里主资源使用ResourceRef管理生命周期。

子资源跟随主资源,不单独生成引用对象。

这适合图集、FBX 这类资源。


十六、回调处理

异步加载可能出现多个地方同时请求同一个资源。

AssetInfo中保存回调列表:

protected List<AssetLoadCallback> mCallback = new(); protected List<string> mLoadPath = new();

资源加载完成后统一回调:

callbackAll()

回调前会先把列表移动到临时列表中。

这样可以避免回调过程中再次修改回调列表,造成遍历错误。

这是 MyFramework 很常见的处理方式:

回调列表先转移 再遍历执行 避免执行期间修改原列表

十七、外部接口

对业务层来说,常用接口主要是这些:

ResourceRef<T> loadGameResource<T>(string name) CustomAsyncOperation loadGameResourceAsync<T>( string name, Action<ResourceRef<T>> callback ) CustomAsyncOperation loadGameResourceAsyncSafe<T>( IRecyclable relatedObj, string name, Action<ResourceRef<T>> callback ) T getGameResource<T>(string name) bool isGameResourceLoaded<T>(string name) void unload<T>(ref ResourceRef<T> res) void unloadPath(string path)

调用层只关心资源名和类型。

加载源、依赖、缓存、引用、卸载都由ResourceManager处理。


十八、设计取舍

这套资源系统没有直接使用 Unity 的Addressables

MyFramework 的做法更偏向自己控制完整流程:

资源路径自己定义 资源清单自己生成 AssetBundle 依赖自己管理 资源引用自己维护 下载和版本系统自己接入 卸载时机自己控制

这样做比直接接入现成方案更麻烦。

但好处是每个环节都能按项目规则调整。

对于长期项目,尤其是需要热更新、资源版本、服务器下载、资源检查和自定义打包规则的项目,这种方式更容易和整套框架配合。


十九、适用场景

这套ResourceManager更适合下面这些项目:

需要 AssetBundle 热更新 需要编辑器和运行时加载方式统一 资源路径需要统一规则 需要资源引用管理 需要控制 AssetBundle 卸载时机 需要动态下载资源包 需要资源版本系统配合 需要加载 SpriteAtlas、FBX 等子资源

如果只是很小的 Demo,直接Resources.Load或简单封装就够了。

MyFramework 的资源系统更适合中大型项目和长期维护项目。


总结

ResourceManager的核心不是封装一层LoadAsset

它真正做的是把资源加载相关流程统一起来:

统一资源路径 统一编辑器加载和 AssetBundle 加载 统一同步和异步接口 统一资源引用 统一 AssetBundle 依赖 统一下载逻辑 统一卸载流程 统一子资源加载

业务层只使用GameResources下的相对路径。

框架内部根据加载源决定走AssetDatabase还是AssetBundle

资源加载成功后通过ResourceRef管理引用。

引用释放后再进入卸载流程。

AssetBundle 卸载时还会检查资源引用和依赖关系。

这就是 MyFramework 中ResourceManager的主要设计。

http://www.jsqmd.com/news/1051090/

相关文章:

  • 终极Lens日志监控指南:3步实现Kubernetes应用高效运维
  • 微信数据安全迁移终极方案:告别聊天记录丢失的三大关键步骤
  • 2026年美国留学申请哪家服务好:五家优选品牌深度解析 - 科技焦点
  • 2026年值得信赖英国留学机构推荐:五家优选深度解析 - 科技焦点
  • AI 辅助开发的工程体系:从定规则到基础设施
  • 2026年全系列工业仪器仪表源头厂家:五家优选品牌解析 - 科技焦点
  • 老邮票长效保存科普,养护到位有效保值 - 深鉴新闻
  • 2026年EVA泡棉胶粘制品厂家甄选:缓冲防护材料与精密模切胶粘材料源头工厂精选与采购维度 - 海棠依旧大
  • [智能体-478]:Coze(扣子)三种官方鉴权令牌完整详解
  • PIC单片机集成运放:开关电源数字控制与模拟调理的片上融合方案
  • Magnolia与Scala 3新特性:利用内置泛型推导提升开发效率
  • DiskGenius 彻底清除扇区数据
  • 独立开发实践:基于本地存储的像素地图 App「雁过留痕」架构设计与迭代思考
  • 2026年东莞线切割精密加工厂家选购参考指南:快走丝、慢走丝、电火花精密线切割优质厂商汇总 - 海棠依旧大
  • Perseus:5分钟解锁碧蓝航线全皮肤的神奇工具
  • Steam游戏自动破解工具完整指南:如何安全解除DRM限制
  • 2026年导波雷达物位计国产替代推荐:五家优选深度解析 - 科技焦点
  • 进阶 WireShark 流量分析|完整业务流量数据包深度解析实训
  • VisionPro之位置修正
  • 如何快速无损转换B站缓存视频:m4s转MP4终极方案
  • 2026年靠谱美国留学机构哪家好:五家优选深度解析 - 科技焦点
  • 大湾区医疗健康EMBA实测解析与科学选型指南
  • 如何用25美元打造AI智能眼镜:OpenGlass开源项目深度解析与实战指南
  • 2026年美国留学申请机构推荐:五家优选品牌深度解析 - 科技焦点
  • 抖音兼职主播加入公会推荐 - 资讯速览
  • 如何彻底清理Mac磁盘空间:终极macOS应用卸载工具指南
  • 昇腾NPU上5分钟部署DeepSeek-R1:绕过图编译与Docker的极简实践
  • emWin抗锯齿与Unicode多语言支持:嵌入式GUI专业级开发实战
  • 基于 Harmony 6.0 应用的共享单车寻车应用首页实现
  • 江苏本地 GEO 服务商避坑全攻略:6 月最新更新,识别套路认准落地靠谱机构 - 936品牌测评网