24 KiB
资源加载最佳实践指南
AIBIS Dream 项目资源加载规范
快速参考表
| 场景 | 推荐方式 | 备注 |
|---|---|---|
| 场景初始化 | ResourceSystem.LoadAsync + yield return |
配合 PreloadByLabel 可减少 IO 等待 |
| 运行时加载 | ResourceSystem.LoadAsync |
SceneResourceLoader 自动缓存同 Key 资源 |
| 常驻资源 | ResourceSystem.LoadPersistentAsync |
加载到 Persistent Loader,不随场景卸载 |
| Timeline 等大资源 | LoadUncachedAsync + EarlyRelease |
播放完立即释放 |
| 配置数据 | PreloadByLabel 批量预加载 |
在 Loading 界面期间完成,预热 Bundle 缓存 |
| 禁止 | Resources.Load / 直接调用 Addressables |
统一走 ResourceSystem |
1. 基本原则
1.1 统一入口
所有资源加载通过 ResourceSystem 静态门面,禁止直接使用 Resources.Load 或 Addressables API。
例外:场景加载/卸载。SceneLoader 内部直接调用 Addressables.LoadSceneAsync / UnloadSceneAsync,因为场景操作有独立的生命周期语义(返回 SceneInstance),不适合纳入通用资源缓存体系。除此之外的资源加载均应走 ResourceSystem。
旧的 ResourceKit 仅在过渡期保留,新代码不应使用。
1.2 异步优先
运行时资源加载使用异步方式(协程 yield return 或回调),避免主线程阻塞。
1.3 场景作用域管理
资源 Handle 由 SceneResourceLoader 统一持有,场景卸载时自动批量释放。开发者无需手动管理大多数资源的生命周期。
1.4 两级 Loader
- Persistent Loader:挂在 Persistence 场景,管理全局常驻资源(Actor prefab、通用 UI 等),游戏退出时释放
- Scene Loader:每个业务场景各一个,场景卸载前由
SceneLoader主动释放
1.5 大资源提前释放
Timeline 等大资源应使用 LoadUncachedAsync 加载,播放完后通过 EarlyRelease 立即释放,不必等到场景卸载。
2. API 使用指南
2.1 三种路由方式
| 方法 | 目标 Loader | 适用场景 |
|---|---|---|
ResourceSystem.LoadAsync<T>(key) |
自动(场景优先,回退持久化) | 大部分业务代码的默认选择 |
ResourceSystem.LoadPersistentAsync<T>(key) |
持久化 Loader | Actor prefab、Bubble prefab 等常驻资源 |
ResourceSystem.LoadSceneAsync<T>(key) |
场景 Loader | 明确只属于当前场景的资源 |
2.2 注意事项
禁止在 Awake 中加载资源:SceneResourceLoader 在 Awake 中注册自身到 ResourceSystem,而 Unity 的 Awake 执行顺序是不确定的。如果在另一个组件的 Awake 中调用 ResourceSystem.LoadAsync,此时当前场景的 SceneResourceLoader 可能尚未完成注册,ResourceSystem 会因找不到 Scene Loader 而回退到 Persistent Loader 加载资源。这会导致:
- 资源被错误地缓存到 Persistent Loader,永远不会随场景卸载释放;
- 后续在协程中再次加载同 Key 资源时,由于缓存命中,返回的是 Persistent Loader 中的 Handle,进一步掩盖问题。
解决方案:所有资源加载必须在协程(如 Start 或 IEnumerator 流程)中执行,确保 SceneResourceLoader.Awake() 已完成执行。
- ActiveLoader 回退风险:
LoadAsync(自动路由)在 Scene Loader 不存在时会静默回退到 Persistent Loader。这意味着在场景切换间隙(旧场景已卸载、新场景SceneResourceLoader.Awake尚未执行)调用LoadAsync,资源会被加载到 Persistent Loader 中,永远不会随场景卸载释放。避免在此时间窗口内调用LoadAsync;若有明确场景归属,优先使用LoadSceneAsync或LoadPersistentAsync。
同一 Key 只能以一种类型加载:SceneResourceLoader 的缓存以 Key 字符串为索引。如果先调用 LoadAsync<Sprite>(key) 再调用 LoadAsync<Texture2D>(key),第二次会返回已缓存的 Handle 并尝试类型转换,可能导致 InvalidCastException。确保同一 Key 在整个场景生命周期内只以一种类型加载。
2.3 协程风格(推荐)
IEnumerator Example()
{
var handle = ResourceSystem.LoadAsync<Sprite>("Memory/mem_001");
yield return handle;
if (handle.Status == AsyncOperationStatus.Succeeded)
_image.sprite = handle.Result;
}
2.4 回调风格
ResourceSystem.LoadAsync<Sprite>("Memory/mem_001",
sprite => _image.sprite = sprite,
error => Debug.LogError(error));
2.5 Timeline 等大资源
// 加载(不走缓存,获得独立 Handle)
var handle = ResourceSystem.CurrentSceneLoader.LoadUncachedAsync<PlayableAsset>(timelineKey);
yield return handle;
director.playableAsset = handle.Result;
// 播放完后立即释放
ResourceSystem.EarlyRelease(handle);
2.6 批量预加载
var handle = ResourceSystem.PreloadByLabel<Sprite>("scene_clinic");
yield return handle;
// PreloadByLabel 会预热 Addressables 底层 Bundle 缓存(Bundle 已加载到内存),
// 后续 LoadAsync 同 Bundle 内的资源时无需再等 IO,但仍会经过一次 Addressables
// 内部解析。注意:预加载的结果不会写入 SceneResourceLoader 的 _cache 字典,
// 后续 LoadAsync 会创建新的 Handle 并缓存。
3. SceneResourceLoader 配置
在场景中挂载 SceneResourceLoader 组件:
- Persistence 场景:勾选
isPersistent,在 Awake 中自动注册为 Persistent Loader - 业务场景:不勾选
isPersistent,在 Awake 中自动注册为 Scene Loader,OnDestroy 时自动注销
SceneLoader 会在场景卸载前主动调用 ReleaseAll(),确保在 UnloadSceneAsync 之前完成资源释放,避免 OnDestroy 执行顺序不确定的问题。
ReleaseAll 幂等性:SceneLoader 先显式调用 ReleaseAll(),随后场景卸载时 SceneResourceLoader.OnDestroy() 会再次调用。这是安全的——第二次调用时 _cache 和 _uncachedHandles 已被清空,不会重复释放。这一设计是有意为之:显式调用保证时序正确,OnDestroy 兜底保证即使漏调也不泄漏。
4. Addressable 命名与分类规范
核心思路
- Group 对齐加载生命周期 — 同一场景/同一时机加载的资源放进同一个 Group(= 同一个 Bundle)。
- Key 保证可预测可构造 — 固定
Category/Path格式,代码中可拼接得出。- Label 仅用于跨 Group 聚合 — 当预加载批次横跨多个 Group 时才创建 Label;若某场景的动态资源全在一个 Group 内,不需要额外 Label。
4.1 Addressable Group
命名格式
{分类}_{PascalCase标识}
分类体系
| 分类 | 命名格式 | 说明 |
|---|---|---|
| 核心常驻 | Core |
游戏全程常驻的资源,对应 Persistent Loader |
| 场景专属 | Scene_{场景名} |
某场景独占的资源,场景卸载时释放 |
| 功能模块 | Feature_{模块名} |
可在多个场景复用的游戏功能模块资源 |
| 共享资源 | Shared_{类别名} |
多场景共用、按需加载的资源 |
| 本地化 | Localization-* |
由 Localization 包自动管理,不手动修改 |
| 内建 | Built In Data |
Unity 系统管理,不手动修改 |
分组原则
- 按加载时机而非资源类型分组:同一场景的动画、精灵、Timeline 放在同一个 Group,而非按类型拆分到不同 Group。这样一次 IO 即可加载该场景所需的全部资源。
- 从不同时加载的资源必须分开:例如火山场景、酒吧场景、梦境场景不要放进同一个 Group,否则进入火山时会加载酒吧和梦境的无用资源。
- 可复用的玩法逻辑资源独立为 Feature 组:如
BlockPuzzle/BlockShapePrefab 可能在多个维修场景复用,放入Feature_BlockPuzzle;但该模块在石头维修场景中使用的缩略图、部件图等美术资源,放入Scene_StoneFix。
当前项目分组方案
| Group | 包含内容 | 依据 |
|---|---|---|
Core |
ActorAnima/ActorAnimaEx 等常驻 Prefab、BubbleSprite、SceneSO、PipelinePrefab | 在 Persistence 场景加载后常驻,全程不释放 |
Scene_ClinicOut |
ClinicOut 场景、诊所外角色动画、Mobile 对象 | 主场景,进入频率最高,资源量适中 |
Scene_SubWay |
SubWay/Bridge 场景及窗外背景 | 地铁+天桥紧密关联,总是一起出现 |
Scene_PeipeiFix |
PeipeiFixScene、OpenFixScene、佩佩动画/精灵 | 佩佩维修线专属 |
Scene_StoneFix |
ExpressFixScene、石头动画、BlockPuzzle 美术资源(缩略图/部件图)、石头线 Timeline | 石头维修线专属;BP 美术只在此场景使用 |
Scene_HuoShanFix |
HuoShanFixScene 及火山专属资源 | 火山维修线专属 |
Scene_Bar |
酒吧场景及专属角色动画 | 酒吧场景专属 |
Scene_Dream |
梦境场景及专属资源 | 梦境场景专属 |
Shared_Actor |
在多个场景出现的角色动画(如医生在诊所外/酒吧/地铁均出现) | 不属于单一场景,按需加载 |
Shared_CG |
CG/插图(由 Yarn 对话触发,可能跨场景使用) | 体积较大,独立管理 |
Feature_BlockPuzzle |
BlockPuzzle/BlockShape Prefab、BlockPuzzleValidator | 纯玩法逻辑资源,可跨场景复用 |
Feature_WhackMole |
WhackMoleData | 打地鼠关卡数据 |
4.2 Addressable Key
格式
{Category}/{Path}
- Category:资源类型前缀,PascalCase
- Path:资源路径,PascalCase,用
/分隔层级,不超过 3 级
例外 — 场景 Key 使用裸名:场景通过 Addressables.LoadSceneAsync 加载,Key 直接使用 PascalCase 场景名,不加类型前缀。原因:场景名被 TalkSceneSO.firstSceneName、Yarn <<load_scene>> 命令、GameManager 硬编码等深度引用,保持裸名是最务实的选择。
Category 列表
| Category | 资源类型 | Key 示例 | 代码构造方式 |
|---|---|---|---|
Animation |
AnimatorController / OverrideController | Animation/Peipei |
$"Animation/{actorName}" |
AnimationConfig |
动画配置 ScriptableObject | AnimationConfig/Peipei |
$"AnimationConfig/{actorName}" |
Sprite |
精灵图(含子对象) | Sprite/Peipei[Normal] |
$"Sprite/{actorName}[{state}]" |
CG |
CG / 插图 | CG/Opening1 |
$"CG/{imagePath}" |
Timeline |
PlayableAsset | Timeline/BlockPuzzle/PanelOpen |
$"Timeline/{module}/{action}" |
BubbleSprite |
对话气泡精灵 | BubbleSprite/Normal |
$"BubbleSprite/{name}" |
Prefab |
预制体 | Prefab/ActorAnima |
$"Prefab/{name}" |
BlockPuzzle |
拼图专属资源 | BlockPuzzle/BlockShape |
$"BlockPuzzle/{path}" |
WhackMole |
打地鼠数据 | WhackMole/Level1 |
$"WhackMole/{levelName}" |
Mobile |
移动物体 | Mobile/Car |
$"Mobile/{name}" |
Config |
配置数据 / SO | Config/SceneSO |
直接引用 |
命名规则
| 规则 | 正确 | 错误 | 依据 |
|---|---|---|---|
| Key 允许中英文混合 | Animation/Peipei、Animation/石头 |
— | 团队习惯优先,UTF-8 编码无技术障碍 |
| 角色名用英文代号 | Animation/ClinicOutPeipei |
Animation/诊所外佩佩 |
统一使用项目角色代号 |
| 不含空格 | Prefab/TaskItem |
Task Item Temp |
空格在 URL、命令行中易出错 |
| 不含文件扩展名 | WhackMole/Level1 |
WhackMoleData/Level1.asset |
Key 是逻辑地址,不是文件路径 |
| 层级不超过 3 级 | Timeline/BlockPuzzle/PanelOpen |
Timeline/Fix/Stone/BP/PanelOpen |
层级过深增加拼接复杂度 |
子对象用 [] 语法 |
Sprite/Peipei[Normal] |
Sprite/Peipei/Normal |
Addressables 原生子对象语法 |
| 场景 Key 裸名 PascalCase | ClinicOut |
Scene/ClinicOut |
兼容 TalkSceneSO 和 LoadSceneAsync |
角色命名参考
中英文均可,建议同一类别保持一致:
| 中文 | 英文 |
|---|---|
| 佩佩 | Peipei |
| 石头 | Stone |
| 火山 | HuoShan |
| 医生 | Doctor |
| 英理 | Yingli |
| 调酒妹 | Bartender |
命名建议:若某类别已存在中文命名(如 Animation/石头),新增资源保持中文风格(Animation/医生);若已存在英文命名,保持英文风格。不要在同一类别中混用。
Timeline Key 约定
DirectorHandler 对 Timeline Key 会自动添加 Timeline/ 前缀。传入的 ref 格式为 {Module}/{Action},实际 Addressable Key 为 Timeline/{Module}/{Action}。
| Module | 中文对应 | Key 示例 |
|---|---|---|
Snap |
卡扣 | Timeline/Snap/Lock, Timeline/Snap/Unlock |
BlockPuzzle |
引擎仓面板 | Timeline/BlockPuzzle/PanelOpen |
Cable |
插线面板 | Timeline/Cable/PanelOpen |
Engine |
引擎模块 | Timeline/Engine/Open |
Clinic |
维修间 | Timeline/Clinic/Enter |
4.3 Addressable Label
设计原则
- Group 决定打包粒度(哪些资源进同一个 Bundle)。
- Label 决定预加载批次(哪些资源在同一时机一起预热)。
- Label 的价值在于横切 Group 边界:把来自不同 Bundle、但需要在同一时刻预热的资源聚合起来。
- 若某场景的全部动态资源恰好都在同一个 Group 内,不需要为它创建 Label。
Label 格式
全小写,单词间用连字符连接。仅在确实需要跨 Group 聚合时才创建。
当前项目 Label
| Label | 横切的 Group | 用途 | 预加载时机 |
|---|---|---|---|
persistent |
Core + Shared_Actor(常驻角色) |
启动时预加载所有常驻资源 | 游戏启动 Loading |
fix-common |
Core(维修通用 Timeline)+ 各 Scene_*Fix 共用的维修 UI |
进入任意维修场景前预加载通用维修资源 | 维修场景 Loading |
preload:stone-fix |
Scene_StoneFix + Feature_BlockPuzzle |
石头维修的动态资源跨两个 Group | 进入 ExpressFixScene 前 |
preload:huoshan-fix |
Scene_HuoShanFix + Feature_WhackMole(如有) |
火山维修的动态资源可能跨 Group | 进入 HuoShanFixScene 前 |
SceneSO |
Core(保持兼容) |
GameManager 启动时用 LoadAssetsAsync("SceneSO") 加载所有章节配置 |
游戏启动 |
什么时候不需要 Label
| 场景 | 原因 |
|---|---|
| ClinicOut | 动态资源全在 Scene_ClinicOut 一个 Group 内 |
| SubWay / Bridge | 动态资源全在 Scene_SubWay 内 |
| Dream | 动态资源全在 Scene_Dream 内 |
| Bar | 动态资源全在 Scene_Bar 内 |
这些场景若需要预加载,直接在 SceneLoader.PreloadSceneAssets 中按 Key 列表逐个加载即可,无需 Label。
4.4 Key / Label / Group 对照表
| 用途 | Key | Group | Label(仅跨 Group 时) |
|---|---|---|---|
| 常驻 Actor Prefab | Prefab/ActorAnima(旧:ActorAnima) |
Core |
persistent |
| 对话气泡精灵 | BubbleSprite/Normal(旧:BubbleSprite/大对话框) |
Core |
persistent |
| 章节配置 | Config/SceneSO(旧:SceneSO) |
Core |
SceneSO(兼容) |
| 诊所外场景 | ClinicOut |
Scene_ClinicOut |
— |
| 诊所外角色动画 | Animation/ClinicOutPeipei |
Scene_ClinicOut |
— |
| 地铁场景 | SubWay |
Scene_SubWay |
— |
| 佩佩维修场景 | PeipeiFixScene |
Scene_PeipeiFix |
— |
| 佩佩动画 | Animation/Peipei |
Scene_PeipeiFix |
— |
| 石头维修场景 | ExpressFixScene |
Scene_StoneFix |
preload:stone-fix |
| 石头动画 | Animation/Stone |
Scene_StoneFix |
preload:stone-fix |
| 石头线 Timeline | Timeline/BlockPuzzle/PanelOpen |
Scene_StoneFix |
preload:stone-fix |
| BP Prefab | BlockPuzzle/BlockShape |
Feature_BlockPuzzle |
preload:stone-fix |
| BP Validator | BlockPuzzle/Validator |
Feature_BlockPuzzle |
preload:stone-fix |
| 打地鼠数据 | WhackMole/Level1 |
Feature_WhackMole |
preload:huoshan-fix(如有) |
| 跨场景角色(医生) | Animation/BarDoctor |
Shared_Actor |
persistent 或按需 |
| CG 插图 | CG/Opening1 |
Shared_CG |
— |
4.5 历史资源兼容策略
现状问题
- 裸 Key:
ActorAnima、NormalBubble、ClinicOut、BlockPuzzleValidtor等 - 含空格:
Task Item Temp、Screen Text(需修正) - 含扩展名:
WhackMoleData/xxx.asset(需修正) - 拼写错误:
ScriptalObjects、BlockPuzzleValidtor(需修正)
迁移优先级
| 优先级 | 动作 | 涉及范围 | 依据 |
|---|---|---|---|
| P0 立即 | 为资源打 Label,重组 Group | 仅改 Addressable 配置,零代码改动 | 立即启用预加载能力 |
| P1 短期 | 新增资源严格按规范命名 Key | 新资源 | 防止问题扩大 |
| P2 中期 | 迁移 ConstRef 中的裸 Key(添加 Prefab/ 前缀) |
~15 个常量 + 引用处 | 改动集中在 ConstRef.cs |
| 无需迁移 | Timeline、Animation 的中文 Key | — | 规范已允许中文命名 |
| 无需迁移 | 场景裸 Key(ClinicOut、SubWay 等) |
— | 裸名本身符合场景 Key 规范 |
迁移步骤
- 重组 Group:将现有组重命名/合并为
Core、Scene_*、Feature_*、Shared_*。 - 打 Label:为需要跨 Group 预加载的资源打上
persistent、fix-common、preload:*等 Label。 - 新资源强制新规:所有新增资源必须使用
Category/Path格式,禁止裸 Key。 - 代码全面切到
ResourceSystem后,再评估旧 Key 重命名。
4.6 中文 Key 使用建议
- 同一类别保持一致:若
Animation/石头已存在,新增动画也用中文(Animation/医生),不要混用Animation/Doctor - 避免特殊字符:仅限中英文、数字、下划线、斜杠、方括号。避免空格、
!@#$%^&*()等符号 - 代码中引用:若某资源频繁在代码中引用,建议在
ConstRef中定义英文常量,如ConstRef.TimelineSnapLock = "Timeline/卡扣锁定" - Group 命名保持英文:Bundle 文件名使用英文 Group 名,避免在某些文件系统/CI 环境出现编码问题
示例与反例
符合规范:
Animation/Peipei— 类型前缀 + 英文角色名Timeline/BlockPuzzle/PanelOpen— 不超过 3 级Sprite/Peipei[Normal]— 子对象用[]语法ClinicOut— 场景裸名,符合场景 Key 例外规则WhackMole/Level1— 不含扩展名
反例:
BlockShape— 裸 Key,缺类型前缀Task Item Temp— 含空格(应改为TaskItemTemp)WhackMoleData/Level1.asset— 不应包含扩展名(应改为WhackMole/Level1)Scene/ClinicOut— 场景 Key 不需要前缀(直接用ClinicOut)
5. 性能优化建议
| 问题 | 后果 | 解决方案 |
|---|---|---|
| 同步加载大资源 | 卡顿 | 使用 ResourceSystem.LoadAsync |
| 重复加载相同资源 | 内存浪费 | SceneResourceLoader 自动缓存同 Key |
| 不释放 Handle | 内存泄漏 | 场景卸载时 Loader 自动批量释放 |
| 大资源持有到场景结束 | 内存浪费 | 用 LoadUncachedAsync + EarlyRelease |
| 混用多种加载方式 | 管理混乱 | 统一使用 ResourceSystem |
6. 错误处理与降级策略
6.1 协程风格的错误处理
IEnumerator LoadWithFallback()
{
var handle = ResourceSystem.LoadAsync<Sprite>("Sprite/Peipei[Normal]");
yield return handle;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
_image.sprite = handle.Result;
}
else
{
Debug.LogError($"[MySystem] 加载失败: {handle.OperationException}");
_image.sprite = _fallbackSprite; // 使用预设的占位资源
}
}
6.2 处理原则
| 情况 | 策略 |
|---|---|
| UI 图片加载失败 | 显示占位图/透明,不影响交互流程 |
| Prefab 加载失败 | 记录错误日志,跳过实例化,避免 NullReferenceException |
| Timeline 加载失败 | 跳过播放,直接推进到下一个流程节点 |
| 配置数据加载失败 | 视为致命错误,中断当前流程并提示 |
| 批量预加载部分失败 | 记录警告,允许后续按需加载时重试(Addressables 会重新发起请求) |
6.3 避免静默失败
SceneResourceLoader 内部已在加载失败时输出 LogError。业务代码不应忽略 handle.Status,至少要在失败时 log 上下文信息(哪个系统、为什么需要这个资源),方便排查。
7. Prefab 加载:LoadAsync vs InstantiateAsync
7.1 两种方式对比
LoadAsync<GameObject> + Instantiate |
Addressables.InstantiateAsync |
|
|---|---|---|
| 加载结果 | 获得 Prefab 引用,需手动 Instantiate | 直接返回实例化的 GameObject |
| 引用计数 | 由 SceneResourceLoader 的 Handle 管理 | 由 Addressables 内部管理,需调用 ReleaseInstance |
| 多次实例化 | 加载一次 Prefab,多次 Instantiate(推荐) | 每次调用产生独立 Handle |
| 与本系统集成 | 完全兼容,走 ResourceSystem 统一管理 | 绕过 ResourceSystem,需自行管理释放 |
| 适用场景 | 大部分 Prefab(UI 面板、Actor 等) | 不推荐,除非有特殊的引用计数需求 |
7.2 推荐用法
本项目统一使用 LoadAsync<GameObject> + 手动 Instantiate,配合 SceneResourceLoader 缓存和自动释放:
IEnumerator SpawnActor()
{
var handle = ResourceSystem.LoadPersistentAsync<GameObject>("Prefab/ActorAnima");
yield return handle;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
var instance = Object.Instantiate(handle.Result, parent);
// Prefab Handle 由 Persistent Loader 管理,无需手动释放
// 实例化的 GameObject 需在不用时 Destroy
}
}
不推荐直接使用 Addressables.InstantiateAsync,因为它绕过了 ResourceSystem 的统一管理。
8. 调试与内存监控
8.1 运行时查看资源持有量
SceneResourceLoader 提供了以下属性,可在 Inspector 或自定义调试面板中查看:
| 属性 | 含义 |
|---|---|
CachedCount |
当前缓存的资源 Handle 数量(按 Key 去重) |
UncachedCount |
未缓存的独立 Handle 数量(LoadUncachedAsync / PreloadByLabel) |
TotalHandleCount |
总持有 Handle 数 |
可在开发阶段写一个简单的 Debug UI 显示这些数值:
void OnGUI()
{
var scene = ResourceSystem.CurrentSceneLoader;
var persistent = ResourceSystem.PersistentLoader;
GUILayout.Label($"Scene: {scene?.TotalHandleCount ?? 0} handles");
GUILayout.Label($"Persistent: {persistent?.TotalHandleCount ?? 0} handles");
}
8.2 Addressables Event Viewer
Unity 编辑器中可通过 Window > Asset Management > Addressables > Event Viewer 查看:
- 每个 Handle 的加载/释放时间线
- 当前活跃的 Handle 数量
- Bundle 加载/卸载事件
需要在 AddressableAssetSettings 中启用 Send Profiler Events 选项。
8.3 常见泄漏排查
| 现象 | 可能原因 | 排查方式 |
|---|---|---|
| 场景切换后内存不降 | 资源被加载到 Persistent Loader(ActiveLoader 回退) | 检查 PersistentLoader.CachedCount 是否异常增长 |
| 同一场景内存持续增长 | 重复调用 LoadUncachedAsync 未释放 |
检查 UncachedCount 是否持续增长 |
| 退出场景后 Addressables 报 Handle 泄漏警告 | SceneResourceLoader 未被正确销毁 |
确认场景中挂载了 SceneResourceLoader 且 SceneLoader 正确调用了卸载流程 |
9. 代码审查清单
- 没有使用
Resources.Load - 没有直接调用
AddressablesAPI(应通过 ResourceSystem,场景加载除外) - 运行时资源加载使用异步方式
- 加载结果检查了
handle.Status,失败时有降级处理或日志(§6) - Timeline 等大资源使用
LoadUncachedAsync+EarlyRelease - 常驻资源通过
LoadPersistentAsync加载 - Prefab 使用
LoadAsync<GameObject>+Instantiate,不使用InstantiateAsync(§7) - 同一 Key 未以不同类型重复加载(§2.2)
- 新增 Addressable Key 符合
Category/Path命名规范(§4.2) - 新增资源放入了正确的 Addressable Group(§4.1)
- 跨 Group 预加载的资源已打上对应 Label(§4.3)
- 业务场景中挂载了
SceneResourceLoader组件
文档版本: 3.1 更新日期: 2026-03-11