使用说明 · 从零到能写业务

资源加载系统 · 使用说明

这份文档只回答一个问题:我该怎么用它?(两段路径、一行加载、引用计数保证不泄漏)

读完你能做到:3 分钟加载第一个资源 · 分清「编辑器直读」和「AB 打包」两种模式 · 知道什么时候该 Release · 出问题 3 分钟定位

〇它是干什么的

先花 30 秒建立直觉,再动手写。

只学 3 个东西就能干活(真的)
RevResManager.LoadAsync<Sprite>("UI/Bag", "icon_sword", sprite => img.sprite = sprite);  // ① 异步加载(推荐)
var prefab = RevResManager.Load<GameObject>("UI/Bag", "ItemSlot");              // ② 同步加载(小资源 / 已预热)
RevResManager.Release("UI/Bag", "icon_sword");                                    // ③ 不用了释放(引用计数减一)

更省心的写法是用 作用域:using (RevResManager.OpenScope()) { … } —— 出块时这一批全放掉。
剩下的(预加载、按组卸载、句柄、缓存统计……)都是用到再看的增值项。

人话 资源加载 = 你只给"哪个目录、哪个名字",剩下的事(走编辑器还是 AB、加载中重复请求怎么合并、谁在用、什么时候能卸载)它全包。

引用计数是核心:同一份资源被 3 个人用就记 3,谁都不用(计数归零)才会被卸载 —— 所以你拿一次就要还一次,这一条记住就不会泄漏。

它替你解决的问题怎么做的
同一份资源被多处加载内部共享缓存 + 引用计数:第二个人拿到的是同一份,不会加载两次
加载中又有人请求同一份请求合并(回调排队),不会重复走 IO
忘记卸载导致内存涨计数归零即进"待卸载"名单;FlushUnused() 主动清、RevResAutoUnloader 也可自动清
编辑器里想直接跑、不想打包编辑器直读模式:直接从 Assets 读,改完立刻生效
上线后要按包管理AB 模式:按 ResMap.txt 把逻辑路径映射到真实 AB
失败不知道为什么失败原因带枚举 RevResLoadErrorReason(路径没映射 / 包里没有 / 类型不符…)

一3 分钟跑起来(可粘贴)

不用初始化、不用挂脚本。

// ① 异步加载一张图(最常见的用法)
void Start()
{
    RevResManager.LoadAsync<Sprite>("UI/Bag", "icon_sword", sprite =>
    {
        image.sprite = sprite;                    // 回调里直接用
        // 这个界面关掉时记得:RevResManager.Release("UI/Bag", "icon_sword");
    });
}

// ② 加载预制体并实例化
RevResManager.LoadAsync<GameObject>("UI/Bag", "ItemSlot", prefab =>
{
    Instantiate(prefab, container);
});

// ③ 一批资源一起用、一起放(作用域,最省心)
using (RevResManager.OpenScope())
{
    var bg  = RevResManager.Load<Sprite>("UI/Common", "bg_main");
    var ico = RevResManager.Load<Sprite>("UI/Common", "ico_coin");
    // …这一段里用它们…
}   // 出块:上面两个资源自动 Release

// ④ 看一眼当前缓存了多少(排查用)
Debug.Log($"缓存 {RevResManager.CachedCount} 份,其中待卸载 {RevResManager.UnusedCount} 份");
就这样,没有初始化步骤 第一次用到时框架会自己选加载后端(编辑器里直读、打包后用 AB)。资源根目录由打包工具配置(菜单 Revolution.Tools / 资源 / RevAB 打包工具 → 「打包」页签 → 资源目录)。

二"我要做 X" 对照表(全部 API)

左边找需求,右边抄一行。入口统一是 RevResManager。

我想…这么写
异步加载(推荐)RevResManager.LoadAsync<T>(rootPath, resName, obj => { })
同步加载RevResManager.Load<T>(rootPath, resName)
拿句柄(自己管引用)RevResManager.LoadHandle<T>(rootPath, resName)
查已在缓存里的RevResManager.Get(rootPath, resName)(没有就是 RevResHandle.Empty)
释放一个RevResManager.Release(rootPath, resName)
一批一起放(推荐)using (RevResManager.OpenScope()) { … }
手动加减引用RevResManager.AddRef(key) / RevResManager.DecRef(key)(key 从句柄拿)
立刻清掉没人用的RevResManager.FlushUnused()
按分组卸载RevResManager.UnloadGroup(RevResGroup.Config)(force: true 连在用的一起卸)
看缓存状况RevResManager.CachedCount / RevResManager.UnusedCount / RevResManager.CachedHandles
接自己的加载策略RevResManager.RegisterPolicy(policy)(进阶:实现 IRevResPolicy)
自定义资源分组RevResGroupUtil.Custom(0)(传 0/1/2… 得到不同分组)
让资源从 Resources 文件夹加载RevResManager.Load<Sprite>("Res/UI/Bag", "icon_sword")(Res/ 前缀走 Resources 通道,见第五节)
句柄(RevResHandle)什么时候用 只有你要把"用了哪些资源"当数据传来传去时才需要它(比如一个界面的资源清单)。 一般业务用 LoadAsync + Release 就够了 —— 别为了"看起来高级"去手动 AddRef/DecRef。

三路径怎么给(唯一需要记的约定)

永远是两段:rootPath(目录)+ resName(文件名)。

实际文件这么写说明
Assets/GameRes/UI/Bag/icon_sword.png("UI/Bag", "icon_sword")相对资源根目录(打包配置里那个)
…/UI/Bag/ItemSlot.prefab("UI/Bag", "ItemSlot")不带扩展名
…/Config/Hero.txt("Config", "Hero")配置表也走同一套(见《配置表 / 导表工具》)
按包分组("UI/Bag", "icon_sword", RevResGroup.UI)分组影响 AB 归属与批量卸载
两个高频错误 ① 把整个路径当 resName:("", "Assets/GameRes/UI/Bag/icon_sword.png") ✗ —— 两段都要按上面来。
② 写扩展名:("UI/Bag", "icon_sword.png") ✗ —— 名字不带后缀。

四什么时候该释放

一句话:拿了几次就还几次;界面/场景销毁时一定要还。

临时用一下就丢(读表、图标、特效贴图)用 using (RevResManager.OpenScope()),别手写 Release
界面用的资源在 OnClose / OnRelease 里 Release(面板生命周期见《UI 系统使用说明》)
对象池里的实例由池端着引用 —— 不要自己 Release,池销毁时会处理(见《对象池使用说明》)
整块玩法结束(一局战斗、一个活动)按分组 UnloadGroup(group) 一次收干净
内存紧张调一次 FlushUnused():只清计数为 0 的,绝对安全
不确定有没有泄漏看 CachedCount 涨不涨;配合 RevAB 窗口的「体积」页签看谁大
不需要"手动卸载"到底 计数归零的资源会进待卸载名单,框架在合适时机(切换场景、主动 Flush、或自动卸载器)回收。你只管配对拿/放,不用管销毁时机。

五两种加载模式(编辑器直读 / AB)

同一行业务代码,两种模式下都不用改 —— 但行为有两处差别要知道。

⚠️ 先明确一件事:框架本体不含热更新代码 AB 默认只从本机 StreamingAssets 读(源码里就写着这句,见 RevABLoader.cs 头部注释)。 资源热更由官方扩展包 RevHotUpdate 提供:它通过本体预留的 2 个默认 null 钩子 (RevABLoader.BundlePathResolver = 包路径重定向 / RevResBootstrap.ResMapOverride = 映射表覆盖) 把加载指到持久化版本目录(Android / iOS / PC)或 CDN 的版本化 URL(小游戏)—— 不装 = 与从前逐字节一致。

装法:git clone -b hotupdate https://github.com/Yokino337088/Revolution.git Assets/Revolution.HotUpdate (框架本体走 -b package);文档见 Revolution.Document/热更新/(使用说明 / 架构解析 / 技术方案)。

想自己接:资源层对外只有 IRevResPolicy + IRevResLoader 两个接口 —— 自己实现(接你的下载/CDN 体系)或整体换成 YooAsset,上层业务代码都不用改。 方案与步骤见仓库首页 README《不做什么:热更新与远程更新》: GitHub 上打开 · 在线文档站

编辑器直读(开发期)
从 Assets 直接读文件:改动立刻生效、不用打包。
注意:不走 AB,所以"包归属""依赖是否被复制"这类问题它看不出来 —— 这类问题要用 RevAB 窗口的「依赖 / 体积 / 检查」页签看。
AB 模式(打包后 / 真机)
按 ResMap.txt(逻辑路径 → AB 与真实路径)加载。
注意:改了资源或分包后,必须先回「打包」页签点一次「仅生成映射」,否则新资源在真机上会报"没映射"。
现象原因与处理
编辑器里好好的,真机说找不到没生成映射(ResMap.txt 里没有这条路径)→ 点「仅生成映射」再打包
路径对但类型不对(图拿成 Prefab)泛型参数写错:LoadAsync<Sprite> 与文件真实类型要一致
同一个资源在两个界面各加载一次两段路径写法不一致(编辑器会当成两个资源)→ 统一约定(见第三节)

Resources 兜底通道(资源直接放进 Resources 文件夹也能加载)

支持 ✓ —— 把资源丢进任意 Resources 文件夹就能通过框架加载 ✓。它是策略链的最后一环(RevResourcesResPolicy),接两类请求:

// ① 显式指定:逻辑路径最前面加 "Res/"(前缀必须落在整条路径最开头)
//    文件:Assets/GameRes/Resources/UI/Bag/icon_sword.png
var icon = RevResManager.Load<Sprite>("Res/UI/Bag", "icon_sword");

// ② AB 回落:普通逻辑路径,ResMap 查不到 / AB 加载失败时自动转到 Resources
var icon2 = RevResManager.Load<Sprite>("UI/Bag", "icon_sword");
四条要记住的

· 路径规则就是 Resources.Load 那一套:相对任意 Resources 文件夹、不带扩展名、层级一致 —— 上面那份文件对应的逻辑路径是 UI/Bag/icon_sword。

· 编辑器“开发模式”下这条通道不注册:默认只注册编辑器直读策略,AB 与 Resources 策略都不参与 → 编辑器里写 Res/xxx 反而会找不到 ✗。想在编辑器里验证:菜单 Revolution.Tools / 资源 / AB 加载模式(编辑器) 切到 AB 模式,或到真机验证 ✓。

· 代价:Resources 下的东西会被 Unity 无条件打进包体、且无法按需卸载 ✗ —— 它是“兜底 + 极小必需资源”的定位,别把大资源丢进去(框架自己只往里放一张 ResMap.txt)。

· 仍然别手写 Resources.Load ✗ —— 经框架走(RevResManager.Load / LoadAsync)才拿得到引用计数、分组卸载与失败原因 ✓。

六新手最容易踩的 6 个坑

坑正确做法
① 加载了不释放,内存一直涨要么 using (OpenScope()),要么在销毁时 Release —— 成对出现
② 把同步 Load 用在"大资源"上大资源(模型、大图、场景)一律 LoadAsync,避免卡帧
③ 在回调里假设"马上就到"异步回调可能下一帧甚至更晚才来;回调里要判 this 是否已销毁(界面已关)
④ 同一个资源 Release 两次引用计数会变成负数(框架会告警但不背锅)→ 用作用域或配对管理
⑤ 手写 Resources.Load绕过框架就享受不到引用计数/映射/打包管理 ✗ → 一律 RevResManager
⑥ 在编辑器里用 Res/ 前缀试 Resources 通道编辑器“开发模式”只注册直读策略(AB / Resources 都不注册)→ 编辑器里测不通 ✗;切菜单「Revolution.Tools / 资源 / AB 加载模式(编辑器)」,或到真机上验证 ✓

七出问题怎么查 + 相关文档

3 分钟定位

  1. 看缓存:RevResManager.CachedCount 一直涨 → 有地方只拿不放(在界面关闭处找);UnusedCount 一直不清 → 没人触发回收,调一次 FlushUnused() 看能不能清掉。
  2. 看映射:报"找不到"先在 Assets/Revolution/Resources/ResourceSystem/ResMap.txt 里搜这条逻辑路径;搜不到就是没生成映射。
  3. 看分包:菜单 Revolution.Tools / 资源 / RevAB 打包工具 → 「分包」页签(带自动同步):确认资源有没有 AB 标记、「检查」页签有没有漏标。
  4. 看体积:「体积」页签找"哪个包突然变大";「依赖」页签看有没有资源被复制多份。

相关文档