AlicizaX/Client/Assets/Books/Framework/Runtime/Resource.md
2026-04-01 13:20:06 +08:00

674 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Resource
## 模块概述
Resource 模块基于 YooAsset 封装了资源包初始化、版本管理、资源查询、同步/异步加载、实例化、缓存回收与低内存处理能力。
它是多个模块的底层依赖:
- `UI`:加载窗口和 Widget 预制体
- `Audio`:加载音频资源
- `Localization`:加载语言表
- `GameObjectPool`:加载池对象原始 prefab
- `Scene`:主场景与附加场景的资源加载链路
从当前实现来看,`ResourceService` 还额外提供了:
- 多包管理
- 同一路径并发加载合并
- 资源对象池缓存
- 低内存时统一回收入口
- 下载、版本、清单更新能力
## 快速开始
### 最少步骤
1. 在场景中挂载 `ResourceComponent`
2. 配置 `PlayMode`、默认包名和解密服务名
3. 游戏启动时调用 `InitPackageAsync`
4. 通过 `GameApp.Resource``AppServices.Require<IResourceService>()` 加载资源
### 最小示例
```csharp
using AlicizaX;
using UnityEngine;
public sealed class ResourceQuickStart : MonoBehaviour
{
private async void Start()
{
await GameApp.Resource.InitPackageAsync();
Texture2D icon = await GameApp.Resource.LoadAssetAsync<Texture2D>("UI/Common/Icon");
Debug.Log(icon != null);
}
}
```
## 架构说明
```text
ResourceComponent
└─ ResourceService
├─ YooAsset PackageMap
├─ AssetInfo Cache
├─ AssetObject Pool
├─ Loading Operation Map
├─ Download / Version / Manifest
└─ ResourceExtComponent / AssetsReference
```
### 关键协作关系
- `ResourceComponent`:负责注册服务、配置运行模式并接管低内存回收节奏
- `ResourceService`:负责包初始化、加载、缓存和释放
- `AssetsReference`:负责把实例对象与源资源句柄关联起来,在对象销毁时自动释放源资源引用
- `ResourceExtComponent`:负责追踪资源绑定目标并分帧回收无引用资源
### 运行模式
当前实现支持以下 `PlayMode`
- `EditorSimulateMode`
- `OfflinePlayMode`
- `HostPlayMode`
- `WebPlayMode`
不同模式决定初始化参数:
- **EditorSimulateMode**:编辑器模拟构建目录
- **OfflinePlayMode**:本地内置资源
- **HostPlayMode**:内置资源 + 缓存 + 远端下载
- **WebPlayMode**Web 环境资源拉取方式
### 并发加载合并机制
`ResourceService` 内部使用 `_assetLoadingOperations` 合并同一路径的并发加载请求。
这意味着:
- 多个系统同时加载同一个资源时,不会重复发起多次底层加载
- 后来的请求会等待第一条加载链路完成,再复用结果
这对 UI 和公共图集资源特别有价值。
## 核心类与接口
### `IResourceService`
该接口同时覆盖:
- 包初始化
- 包版本与清单管理
- 资源存在性检查
- 同步/异步加载
- 场景对象实例化
- 缓存清理
- 低内存回收
### `ResourceComponent`
它是 Resource 模块在场景中的入口组件,主要职责:
- 注册 `ResourceService`
- 配置默认包名 `PackageName`
- 设置运行模式 `PlayMode`
- 设置解密服务名 `decryptionServices`
- 设置资源对象池参数
- 接管 `Application.lowMemory` 事件
### `ResourceService`
从当前实现看,`ResourceService` 维护以下关键状态:
- `DefaultPackageName`
- `PlayMode`
- `PackageMap`
- `_assetInfoMap`
- `_assetLoadingOperations`
- `_assetPool`
### `AssetsReference`
这是实例对象与源资源句柄的桥接组件。
作用:
- 当你通过 `LoadGameObject` / `LoadGameObjectAsync` 实例化资源时
- 框架会在实例对象上自动挂一个 `AssetsReference`
- 当该实例对象销毁时,对应源资源引用会自动 `UnloadAsset`
这就是为什么:
- `LoadGameObject` 返回的实例对象通常不需要你手动 `UnloadAsset`
### `ResourceExtComponent`
主要用于:
- 追踪已绑定给 UI、Sprite 或其他目标对象的资源
- 自动清理已经失去引用的资源包装对象
- 以分帧方式做回收,降低一次性遍历成本
## API 参考
以下内容按使用频率分组说明。
### 一、初始化与运行配置
#### `void Initialize()`
- 参数:无
- 返回值:无
- 说明:初始化 YooAsset、默认包与资产池
- 备注:通常由 `ResourceComponent.Start()` 自动调用
#### `UniTask<bool> InitPackageAsync(string packageName = "", string hostServerURL = "", string fallbackHostServerURL = "")`
- 可选参数:`packageName`
- 可选参数:`hostServerURL`
- 可选参数:`fallbackHostServerURL`
- 返回值:`UniTask<bool>`
说明:
- 初始化指定资源包
- 如果 `packageName` 为空,则使用 `DefaultPackageName`
- 在 Host/Web 模式下通常需要传入远端地址
异常与边界:
- 如果同一个包正在初始化或已成功初始化,会记录错误并直接返回失败结果
- 初始化失败时任务会抛出异常
#### `string DefaultPackageName { get; set; }`
- 说明:默认资源包名
- 推荐:整个项目统一默认包名约定
#### `EPlayMode PlayMode { get; set; }`
- 说明:资源系统运行模式
- 注意Editor 与真机配置往往不同
#### `string DecryptionServices { get; set; }`
- 说明:解密服务类型名
- 注意:该值应是可被反射创建的类型全名
#### `bool AutoUnloadBundleWhenUnused { get; set; }`
- 说明:初始化参数中的自动卸载选项
### 二、版本与下载管理
#### `string GetPackageVersion(string customPackageName = "")`
- 可选参数:`customPackageName`
- 返回值:包版本字符串
#### `RequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks = false, int timeout = 60, string customPackageName = "")`
- 可选参数:`appendTimeTicks`
- 可选参数:`timeout`
- 可选参数:`customPackageName`
- 返回值:`RequestPackageVersionOperation`
#### `UpdatePackageManifestOperation UpdatePackageManifestAsync(string packageVersion, int timeout = 60, string customPackageName = "")`
- 必填参数:`packageVersion`
- 可选参数:`timeout`
- 可选参数:`customPackageName`
- 返回值:`UpdatePackageManifestOperation`
#### `ResourceDownloaderOperation CreateResourceDownloader(string customPackageName = "")`
- 可选参数:`customPackageName`
- 返回值:`ResourceDownloaderOperation`
说明:
- 会使用当前 `DownloadingMaxNum``FailedTryAgain`
### 三、资源查询
#### `HasAssetResult HasAsset(string location, string packageName = "")`
- 必填参数:`location`
- 可选参数:`packageName`
- 返回值:`HasAssetResult`
说明:
- 用于判断资源是否存在、是否在本地、是否需要远端下载
注意:
- 如果 `location` 为空会抛异常
#### `bool CheckLocationValid(string location, string packageName = "")`
- 必填参数:`location`
- 可选参数:`packageName`
- 返回值:`bool`
#### `AssetInfo GetAssetInfo(string location, string packageName = "")`
- 必填参数:`location`
- 可选参数:`packageName`
- 返回值:`AssetInfo`
说明:
- 内部带有 `_assetInfoMap` 缓存
- 同一路径重复获取成本较低
### 四、同步加载
#### `T LoadAsset<T>(string location, string packageName = "") where T : UnityEngine.Object`
- 必填参数:`location`
- 可选参数:`packageName`
- 返回值:`T`
- 泛型约束:`T : UnityEngine.Object`
说明:
- 同步加载资源
- 首次加载会进入资源池缓存
- 重复获取相同资源时会优先从 `_assetPool` 中复用
#### `GameObject LoadGameObject(string location, Transform parent = null, string packageName = "")`
- 必填参数:`location`
- 可选参数:`parent`
- 可选参数:`packageName`
- 返回值:`GameObject`
说明:
- 同步加载 prefab 并实例化
- 实例对象会自动挂 `AssetsReference`
- 销毁实例时会自动释放源资源引用
### 五、异步加载
#### `UniTask<T> LoadAssetAsync<T>(string location, CancellationToken cancellationToken = default, string packageName = "") where T : UnityEngine.Object`
- 必填参数:`location`
- 可选参数:`cancellationToken`
- 可选参数:`packageName`
- 返回值:`UniTask<T>`
#### `UniTask<GameObject> LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default, string packageName = "")`
- 必填参数:`location`
- 可选参数:`parent`
- 可选参数:`cancellationToken`
- 可选参数:`packageName`
- 返回值:`UniTask<GameObject>`
### 六、回调式加载
#### `UniTask LoadAssetAsync(string location, int priority, LoadAssetCallbacks loadAssetCallbacks, object userData, string packageName = "")`
#### `UniTask LoadAssetAsync(string location, Type assetType, int priority, LoadAssetCallbacks loadAssetCallbacks, object userData, string packageName = "")`
`LoadAssetCallbacks` 中的回调签名:
- 成功:`(string assetName, object asset, float duration, object userData)`
- 失败:`(string assetName, LoadResourceStatus status, string errorMessage, object userData)`
- 进度:`(string assetName, float progress, object userData)`
适合:
- 旧式回调链
- 想同时观察进度与错误
- 不方便直接 `await` 的场景
### 七、句柄接口
#### `AssetHandle LoadAssetSyncHandle<T>(string location, string packageName = "") where T : UnityEngine.Object`
#### `AssetHandle LoadAssetAsyncHandle<T>(string location, string packageName = "") where T : UnityEngine.Object`
适合:
- 需要直接管理底层句柄
- 需要和 YooAsset 句柄 API 配合
### 八、释放与回收
#### `void UnloadAsset(object asset)`
- 必填参数:`asset`
- 返回值:无
说明:
- 本质上是把资源对象从 `_assetPool``Unspawn`
#### `void UnloadUnusedAssets()`
- 返回值:无
说明:
- 释放资源对象池中未使用的对象
- 并对所有已成功初始化的包执行 `UnloadUnusedAssetsAsync`
#### `void ForceUnloadAllAssets()`
- 返回值:无
说明:
- 强制卸载所有资源
- WebGL 下会直接警告并退出
#### `void ForceUnloadUnusedAssets(bool performGCCollect)`
- 必填参数:`performGCCollect`
- 返回值:无
说明:
- 委托给 `ResourceComponent` 控制实际的系统资源回收时机
## 常见用法
### 1. 初始化默认包
```csharp
using AlicizaX;
using AlicizaX.Resource.Runtime;
using UnityEngine;
public sealed class ResourceInitExample : MonoBehaviour
{
private async void Start()
{
bool ok = await GameApp.Resource.InitPackageAsync();
Debug.Log($"Init default package success: {ok}");
}
}
```
### 2. 初始化远端包
```csharp
using AlicizaX;
using UnityEngine;
public sealed class HostModeInitExample : MonoBehaviour
{
private async void Start()
{
await GameApp.Resource.InitPackageAsync(
packageName: "DefaultPackage",
hostServerURL: "https://cdn.example.com/game",
fallbackHostServerURL: "https://backup.example.com/game");
}
}
```
### 3. 同步加载配置资源
```csharp
using AlicizaX;
using UnityEngine;
public sealed class SyncLoadExample : MonoBehaviour
{
private void Start()
{
TextAsset config = GameApp.Resource.LoadAsset<TextAsset>("Config/GameBalance");
Debug.Log(config != null ? config.text : "Config missing");
}
}
```
### 4. 异步加载图片资源
```csharp
using AlicizaX;
using UnityEngine;
public sealed class AsyncLoadExample : MonoBehaviour
{
private async void Start()
{
Sprite sprite = await GameApp.Resource.LoadAssetAsync<Sprite>("UI/Common/Atlas/Icon_Star");
Debug.Log(sprite != null);
}
}
```
### 5. 加载并实例化 GameObject
```csharp
using AlicizaX;
using UnityEngine;
public sealed class InstantiateExample : MonoBehaviour
{
private GameObject _hero;
private async void Start()
{
_hero = await GameApp.Resource.LoadGameObjectAsync("Character/Hero.prefab", transform);
}
private void OnDestroy()
{
if (_hero != null)
{
Destroy(_hero);
}
}
}
```
说明:
- 这里不需要对 `_hero` 再调用 `UnloadAsset`
- 销毁实例时 `AssetsReference` 会自动释放源资源引用
### 6. 使用回调式加载并监听进度
```csharp
using AlicizaX;
using AlicizaX.Resource.Runtime;
using UnityEngine;
public sealed class CallbackLoadExample : MonoBehaviour
{
private async void Start()
{
var callbacks = new LoadAssetCallbacks(
(assetName, asset, duration, userData) =>
{
Debug.Log($"Load success: {assetName}, duration: {duration:F3}s");
},
(assetName, status, errorMessage, userData) =>
{
Debug.LogError($"Load failed: {assetName}, {status}, {errorMessage}");
},
(assetName, progress, userData) =>
{
Debug.Log($"Progress {assetName}: {progress:P0}");
});
await GameApp.Resource.LoadAssetAsync(
"UI/Common/Icon",
priority: 0,
loadAssetCallbacks: callbacks,
userData: null);
}
}
```
### 7. 下载资源版本并更新清单
```csharp
using AlicizaX;
using UnityEngine;
public sealed class UpdateManifestExample : MonoBehaviour
{
private async void Start()
{
var versionOp = GameApp.Resource.RequestPackageVersionAsync();
await versionOp.ToUniTask();
if (versionOp.Status == YooAsset.EOperationStatus.Succeed)
{
string version = versionOp.PackageVersion;
var manifestOp = GameApp.Resource.UpdatePackageManifestAsync(version);
await manifestOp.ToUniTask();
Debug.Log($"Manifest updated to: {version}");
}
}
}
```
### 8. 创建下载器
```csharp
using AlicizaX;
using UnityEngine;
public sealed class DownloaderExample : MonoBehaviour
{
private async void Start()
{
var downloader = GameApp.Resource.CreateResourceDownloader();
downloader.BeginDownload();
await downloader.ToUniTask();
Debug.Log($"Download status: {downloader.Status}");
}
}
```
## 运行行为细节
### 1. 重复加载同一路径会优先复用缓存
当前实现通过 `_assetPool` 保存 `AssetObject`,同一路径的资源会复用池内对象。
好处:
- 减少重复加载
- 降低频繁生成/销毁底层句柄的开销
### 2. 并发同路径加载会合并请求
如果同一路径资源正在加载中:
- 后续请求不会重复发起底层加载
- 会等待首个请求完成后再复用结果
### 3. `LoadGameObject` 与 `LoadAsset<T>` 的释放语义不同
#### `LoadAsset<T>`
- 返回的是资源对象本身
- 如有需要可以显式 `UnloadAsset(asset)`
#### `LoadGameObject`
- 返回的是实例化后的场景对象
- 源 prefab 资源句柄通过 `AssetsReference` 绑定到实例对象
- 通常直接 `Destroy(instance)` 即可
### 4. 低内存行为
触发 `OnLowMemory()` 时:
- 会调用 `_forceUnloadUnusedAssetsAction`
- 实际清理由 `ResourceComponent` 驱动
### 5. `AssetInfo` 有内部缓存
多次 `GetAssetInfo` 会命中 `_assetInfoMap`
适合:
- 频繁查询资源合法性
- UI 打开前做路径校验
### 6. 失败行为
以下情况通常会导致异常或错误日志:
- 资源路径为空
- 包不存在
- 资源定位无效
- 初始化失败
## 最佳实践
- 统一资源路径规范,避免大小写和目录命名混乱
- 默认优先异步加载
- 把“包初始化 / 版本更新 / 资源下载 / 资源使用”分成不同阶段处理
- UI、音频、图集等高复用资源尽量走缓存复用
- 对需要长期驻留的资源,业务层要明确生命周期,不要频繁反复加载
## 常见错误
### 1. 包未初始化就加载资源
现象:
- 资源查找失败
- 返回空对象或直接异常
规避:
- 确保启动流程中先执行 `InitPackageAsync`
### 2. 默认包名与实际包名不一致
现象:
- 路径正确但查不到资源
规避:
- 统一 `DefaultPackageName`
- 多包场景显式传入 `packageName`
### 3. 手动 `UnloadAsset` 已实例化场景对象
现象:
- 语义不清晰
- 可能导致资源生命周期混乱
规避:
- `LoadGameObject` 返回的实例优先 `Destroy`
### 4. 解密服务类型名错误
现象:
- 初始化时反射失败
规避:
- `DecryptionServices` 必须是可反射创建的完整类型名
## 性能注意事项
- 避免在主线程大量同步加载
- 常用资源优先缓存复用
- 合理设置:
- `AssetAutoReleaseInterval`
- `AssetCapacity`
- `AssetExpireTime`
- `AssetPriority`
- 高并发异步加载时,优先复用路径和包配置,发挥内部合并机制优势