// Copyright (c) 2025 EFramework Innovation. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. using System; using System.Collections; using System.Collections.Generic; using EFramework.Unity.Utility; using UnityEngine; namespace EFramework.Unity.Asset { public partial class XAsset { /// /// XAsset.Resource 提供了 Unity 资源的加载与卸载,支持自动处理依赖资源的生命周期。 /// /// /// /// 功能特性 /// - 支持资源的加载与卸载 /// - 自动处理资源依赖关系 /// /// 使用手册 /// /// 1. 同步加载 /// /// 1.1 加载资源 /// - 功能说明:根据当前模式从 `Resources` 或 `AssetBundle` 中加载资源 /// - 函数参数: /// - `path`:资源路径(`string` 类型) /// - `type`:资源类型(`Type` 类型) /// - `resource`:是否强制从 `Resources` 加载(`bool` 类型) /// - 函数返回:加载的资源对象(`UnityEngine.Object` 类型),加载失败时返回 `null` /// - 使用示例: /// ```csharp /// var asset = XAsset.Resource.Load("Example/Test.prefab", typeof(GameObject)); /// ``` /// /// 1.2 泛型加载 /// - 功能说明:提供类型安全的资源加载方式 /// - 函数参数: /// - `path`:资源路径(`string` 类型) /// - `resource`:是否强制从 `Resources` 加载(`bool` 类型) /// - 函数返回:加载的资源对象(`T` 类型),加载失败时返回 `null` /// - 使用示例: /// ```csharp /// var asset = XAsset.Resource.Load<GameObject>("Example/Test.prefab"); /// ``` /// /// 2. 异步加载 /// /// 2.1 加载资源 /// - 功能说明:异步加载资源,适合加载大型资源 /// - 函数参数: /// - `path`:资源路径(`string` 类型) /// - `type`:资源类型(`Type` 类型) /// - `callback`:加载完成时的回调函数(`Callback` 类型) /// - `resource`:是否强制从 `Resources` 加载(`bool` 类型) /// - 函数返回:用于跟踪加载进度的 `Handler` 对象 /// - 使用示例: /// ```csharp /// XAsset.Resource.LoadAsync("Example/Test.prefab", typeof(GameObject), (asset) => /// { /// Debug.Log("加载完成:" + asset.name); /// }); /// ``` /// /// 2.2 泛型加载 /// - 功能说明:提供类型安全的异步加载方式 /// - 函数参数: /// - `path`:资源路径(`string` 类型) /// - `callback`:加载完成时的类型安全回调(`Action<T>` 类型) /// - `resource`:是否强制从 `Resources` 加载(`bool` 类型) /// - 函数返回:用于跟踪加载进度的 `Handler` 对象 /// - 使用示例: /// ```csharp /// XAsset.Resource.LoadAsync<GameObject>("Example/Test.prefab", (asset) => /// { /// Debug.Log("加载完成:" + asset.name); /// }); /// ``` /// /// 3. 卸载资源 /// - 功能说明:卸载指定路径的资源 /// - 函数参数: /// - `path`:资源路径(`string` 类型) /// - 使用示例: /// ```csharp /// XAsset.Resource.Unload("Example/Test.prefab"); /// ``` /// /// 更多信息请参考模块文档。 /// public partial class Resource { /// /// Task 是异步加载的任务,用于跟踪单个资源的加载状态。 /// 处理来自 Resources 和 AssetBundle 的异步加载请求。 /// internal class Task { /// /// Name 是正在加载的资源路径。 /// internal string Name; /// /// Request 是 Unity 的异步加载操作对象,可能是 ResourceRequest 或 AssetBundleRequest。 /// internal AsyncOperation Request; } /// /// Loading 是当前正在进行的资源加载任务列表,用于处理并发加载请求,避免同一资源被重复加载。 /// internal static readonly Dictionary Loading = new(); /// /// Refer 用于跟踪资源(Prefab)实例(GameObject)的使用情况,确保资源依赖包被正确释放。 /// internal class Refer : MonoBehaviour { /// /// Source 是资源在 Bundle 中的原始路径,用于定位资源包实例。 /// [SerializeField] internal string Source; /// /// Label 是用于调试的对象标签,格式为"对象名@哈希码"。 /// 即使对象被销毁也能保持唯一标识。 /// internal string Label; /// /// GenLabel 用于构造用于调试的对象标签。 /// internal void GenLabel() { Label = $"{name}@{GetHashCode()}"; } /// /// Awake 在 Unity 对象初始化时调用。 /// internal void Awake() { if (Constants.ReferMode) { GenLabel(); // 提前初始化标签,避免切换场景时因源实例销毁引起的空异常 var bundle = Bundle.Find(Source); bundle?.Retain(Constants.DebugMode ? $"[Resource.Refer.Awake: {Label}]" : ""); } } /// /// OnDestroy 在 Unity 对象销毁时的调用。 /// internal void OnDestroy() { if (Constants.ReferMode) { var bundle = Bundle.Find(Source); bundle?.Release(Constants.DebugMode ? $"[Resource.Refer.OnDestroy: {Label}]" : ""); } } /// /// Watch 监听指定游戏对象的生命周期,确保对象在被销毁前进行资源包释放。 /// /// 要监听的游戏对象。 /// 资源包名称,用于定位资源包实例。 internal static Refer Watch(GameObject originalObject, string bundleName) { if (originalObject && Constants.ReferMode) { if (!originalObject.TryGetComponent(out var refer)) refer = originalObject.AddComponent(); refer.Source = bundleName; refer.GenLabel(); // 提前初始化标签,避免切换场景时因源实例销毁引起的空异常 return refer; } return null; } } /// /// Load 同步加载指定类型的资源。 /// 根据当前模式和参数,从 Resources 目录或 AssetBundle 文件中加载资源。 /// 在 Bundle 模式下会自动处理资源包的加载和依赖关系。 /// /// 资源在项目中的相对路径 /// 要加载的资源类型 /// 是否强制从 Resources 加载 /// 是否保留对 AssetBundle 的引用 /// 加载的资源对象,加载失败时返回null public static UnityEngine.Object Load(string path, Type type, bool resource = false, bool retain = true) { try { Event.Notify(EventType.OnPreLoadResource, path); } catch (Exception e) { XLog.Panic(e); } UnityEngine.Object asset = null; try { var resourceIndex = path.IndexOf("Resources/"); if (!Constants.BundleMode || resource || !Bundle.Manifest) { if (resourceIndex >= 0) path = path[(resourceIndex + 10)..]; asset = Resources.Load(path, type); } else { if (resourceIndex < 0) path = "Resources/" + path; var lastPart = path.LastIndexOf("/"); var assetName = path[(lastPart + 1)..]; var bundleName = Constants.GetName(path); var bundleInfo = Bundle.Load(bundleName); if (bundleInfo != null) { asset = bundleInfo.Source.LoadAsset(assetName, type); // 如果是自动引用模式且资源为游戏对象,则监控它的生命周期并自动引用与释放 if (asset is GameObject gameObject && Constants.ReferMode) Refer.Watch(gameObject, bundleName); // 如果指示保留资源,则增加对该资源的引用计数并由业务层自行释放 if (retain) bundleInfo.Retain(Constants.DebugMode ? $"[Resource.Load: {path}]" : ""); } else XLog.Error("XAsset.Resource.Load: sync load error: {0}", path); } } catch (Exception e) { throw e; } finally { try { Event.Notify(EventType.OnPostLoadResource, path); } catch (Exception e) { XLog.Panic(e); } } return asset; } /// /// Load 同步加载指定类型的资源。 /// 泛型版本的加载方法,提供更方便的类型安全的资源加载方式。 /// /// 要加载的资源类型 /// 资源在项目中的相对路径 /// 是否强制从 Resources 加载 /// 是否保留对 AssetBundle 的引用 /// 加载的资源对象,加载失败时返回null public static T Load(string path, bool resource = false, bool retain = true) where T : UnityEngine.Object { return Load(path, typeof(T), resource, retain) as T; } /// /// LoadAsync 异步加载资源。 /// 提供非阻塞的资源加载方式,适合加载大型资源。 /// 可以通过返回的Handler监控加载进度,并在加载完成时得到通知。 /// /// 资源在项目中的相对路径 /// 要加载的资源类型 /// 资源加载完成时的回调函数 /// 是否强制从 Resources 加载 /// 是否保留对 AssetBundle 的引用 /// 用于跟踪加载进度的Handler对象 public static Handler LoadAsync(string path, Type type, Callback callback = null, bool resource = false, bool retain = true) { var handler = new Handler(); XLoom.RunCoroutine(LoadAsync(path, type, callback, handler, resource, retain)); return handler; } /// /// LoadAsync 异步加载指定类型的资源。 /// 泛型版本的异步加载方法,提供类型安全的回调方式。 /// /// 要加载的资源类型 /// 资源在项目中的相对路径 /// 资源加载完成时的类型安全回调 /// 是否强制从 Resources 加载 /// 是否保留对 AssetBundle 的引用 /// 用于跟踪加载进度的Handler对象 public static Handler LoadAsync(string path, Action callback = null, bool resource = false, bool retain = true) where T : UnityEngine.Object { return LoadAsync(path, typeof(T), (asset) => callback?.Invoke(asset as T), resource, retain); } /// /// LoadAsync 异步加载资源的内部实现。 /// 处理实际的资源加载流程,包括依赖资源的加载、加载状态的跟踪和回调的触发。 /// /// 加载流程: /// 1. 根据当前模式选择加载方式(Resources/AssetBundle) /// 2. 处理并发加载请求,避免重复加载 /// 3. 在 Bundle 模式下处理依赖关系 /// 4. 触发加载完成事件和回调 /// internal static IEnumerator LoadAsync(string path, Type type, Callback callback, Handler handler, bool resource = false, bool retain = true) { try { Event.Notify(EventType.OnPreLoadResource, path); } catch (Exception e) { XLog.Panic(e); } UnityEngine.Object asset = null; var resourceIndex = path.IndexOf("Resources/"); if (!Constants.BundleMode || resource || !Bundle.Manifest) { if (resourceIndex >= 0) path = path[(resourceIndex + 10)..]; if (!Loading.TryGetValue(path, out var task)) { var request = Resources.LoadAsync(path, type); task = new Task() { Name = path, Request = request }; handler.Request = request; handler.InvokePreload(); Loading.Add(path, task); yield return new WaitUntil(() => task.Request.isDone); Loading.Remove(path); } else { handler.Request = task.Request; handler.InvokePreload(); yield return new WaitUntil(() => task.Request.isDone); } asset = (task.Request as ResourceRequest).asset; // 加载错误时仍旧回调,业务层可根据 handler.Error 判断是否加载成功 handler.Error = asset == null; handler.InvokePostload(); } else { handler.totalCount++; // Load任务 if (resourceIndex < 0) path = "Resources/" + path; var lastPart = path.LastIndexOf("/"); var assetName = path[(lastPart + 1)..]; var bundleName = Constants.GetName(path); yield return XLoom.RunCoroutine(Bundle.LoadAsync(bundleName, handler)); var bundleInfo = Bundle.Find(bundleName); if (bundleInfo != null) { if (!Loading.TryGetValue(path, out var task)) { var request = bundleInfo.Source.LoadAssetAsync(assetName, type); task = new Task() { Name = path, Request = request }; handler.Request = request; handler.InvokePreload(); Loading.Add(path, task); yield return request; asset = request.asset; Loading.Remove(path); handler.doneCount++; // 如果是自动引用模式且资源为游戏对象,则监控它的生命周期并自动引用与释放 if (asset is GameObject gameObject && Constants.ReferMode) Refer.Watch(gameObject, bundleName); // 如果指示保留资源,则增加对该资源的引用计数并由业务层自行释放 if (retain) bundleInfo.Retain(Constants.DebugMode ? $"[Resource.LoadAsync.1: {path}]" : ""); handler.InvokePostload(); } else { handler.Request = task.Request; handler.InvokePreload(); yield return new WaitUntil(() => task.Request.isDone); handler.doneCount++; // 如果是自动引用模式且资源为游戏对象,则监控它的生命周期并自动引用与释放 if (asset is GameObject gameObject && Constants.ReferMode) Refer.Watch(gameObject, bundleName); // 如果指示保留资源,则增加对该资源的引用计数并由业务层自行释放 if (retain) bundleInfo.Retain(Constants.DebugMode ? $"[Resource.LoadAsync.2: {path}]" : ""); handler.InvokePostload(); } asset = (task.Request as AssetBundleRequest).asset; } else { XLog.Error("XAsset.Resource.LoadAsync: async load error: {0}", path); // 加载错误时仍旧回调,业务层可根据 handler.Error 判断是否加载成功 handler.Error = true; handler.InvokePostload(); } } try { Event.Notify(EventType.OnPostLoadResource, path); } catch (Exception e) { XLog.Panic(e); } try { callback?.Invoke(asset); } catch (Exception e) { XLog.Panic(e); } } /// /// Unload 卸载指定路径的资源。 /// 在 Bundle 模式下,会卸载对应的资源包。 /// 注意:这个操作可能会影响到共享同一资源包的其他资源。 /// /// 要卸载的资源路径 public static void Unload(string path) { if (Constants.BundleMode && Bundle.Manifest) { var resourceIndex = path.IndexOf("Resources/"); if (resourceIndex < 0) path = "Resources/" + path; var bundleName = Constants.GetName(path); var bundleInfo = Bundle.Find(bundleName); bundleInfo?.Release(Constants.DebugMode ? $"[Resource.Unload: {path}]" : ""); } } /// /// IsLoading 检查资源的加载状态。 /// /// 资源路径 /// 是否正在加载 public static bool IsLoading(string path) { if (string.IsNullOrEmpty(path)) return false; else return Loading.ContainsKey(path); } } } }