# Resource ## 模块概述 Resource 模块基于 YooAsset 封装了资源包初始化、版本管理、资源查询、同步/异步加载、实例化、缓存回收与低内存处理能力。 它是多个模块的底层依赖: - `UI`:加载窗口和 Widget 预制体 - `Audio`:加载音频资源 - `Localization`:加载语言表 - `GameObjectPool`:加载池对象原始 prefab - `Scene`:主场景与附加场景的资源加载链路 从当前实现来看,`ResourceService` 还额外提供了: - 多包管理 - 同一路径并发加载合并 - 资源对象池缓存 - 低内存时统一回收入口 - 下载、版本、清单更新能力 ## 快速开始 ### 最少步骤 1. 在场景中挂载 `ResourceComponent` 2. 配置 `PlayMode`、默认包名和解密服务名 3. 游戏启动时调用 `InitPackageAsync` 4. 通过 `GameApp.Resource` 或 `AppServices.Require()` 加载资源 ### 最小示例 ```csharp using AlicizaX; using UnityEngine; public sealed class ResourceQuickStart : MonoBehaviour { private async void Start() { await GameApp.Resource.InitPackageAsync(); Texture2D icon = await GameApp.Resource.LoadAssetAsync("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 InitPackageAsync(string packageName = "", string hostServerURL = "", string fallbackHostServerURL = "")` - 可选参数:`packageName` - 可选参数:`hostServerURL` - 可选参数:`fallbackHostServerURL` - 返回值:`UniTask` 说明: - 初始化指定资源包 - 如果 `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(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 LoadAssetAsync(string location, CancellationToken cancellationToken = default, string packageName = "") where T : UnityEngine.Object` - 必填参数:`location` - 可选参数:`cancellationToken` - 可选参数:`packageName` - 返回值:`UniTask` #### `UniTask LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default, string packageName = "")` - 必填参数:`location` - 可选参数:`parent` - 可选参数:`cancellationToken` - 可选参数:`packageName` - 返回值:`UniTask` ### 六、回调式加载 #### `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(string location, string packageName = "") where T : UnityEngine.Object` #### `AssetHandle LoadAssetAsyncHandle(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("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("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` 的释放语义不同 #### `LoadAsset` - 返回的是资源对象本身 - 如有需要可以显式 `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` - 高并发异步加载时,优先复用路径和包配置,发挥内部合并机制优势