674 lines
16 KiB
Markdown
674 lines
16 KiB
Markdown
# 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`
|
||
- 高并发异步加载时,优先复用路径和包配置,发挥内部合并机制优势
|