From 622b12aa049398fd6d3a42396105dbb33561c44d Mon Sep 17 00:00:00 2001 From: Ding Yuntian <1491671119@qq.com> Date: Thu, 12 Mar 2026 17:44:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(resource-kit):=20=E6=B7=BB=E5=8A=A0=20Reso?= =?UTF-8?q?urceSystem=20=E5=92=8C=20SceneResourceLoader=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E5=9C=BA=E6=99=AF=E7=BA=A7=E8=B5=84=E6=BA=90=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Framework/ResourceKit/ResourceKit.cs | 119 +------------- .../Framework/ResourceKit/ResourceSystem.cs | 134 ++++++++++++++++ .../ResourceKit/ResourceSystem.cs.meta | 11 ++ .../ResourceKit/SceneResourceLoader.cs | 149 ++++++++++++++++++ .../ResourceKit/SceneResourceLoader.cs.meta | 11 ++ 5 files changed, 310 insertions(+), 114 deletions(-) create mode 100644 Assets/Scripts/Framework/ResourceKit/ResourceSystem.cs create mode 100644 Assets/Scripts/Framework/ResourceKit/ResourceSystem.cs.meta create mode 100644 Assets/Scripts/Framework/ResourceKit/SceneResourceLoader.cs create mode 100644 Assets/Scripts/Framework/ResourceKit/SceneResourceLoader.cs.meta diff --git a/Assets/Scripts/Framework/ResourceKit/ResourceKit.cs b/Assets/Scripts/Framework/ResourceKit/ResourceKit.cs index 3da8bec7a..3d80aee4b 100644 --- a/Assets/Scripts/Framework/ResourceKit/ResourceKit.cs +++ b/Assets/Scripts/Framework/ResourceKit/ResourceKit.cs @@ -1,67 +1,16 @@ using System; -using System.Linq; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; -using AibisDream.Utility; -using UnityEditor.U2D.PSD; - - -#if UNITY_EDITOR -using UnityEditor; -#endif namespace AibisDream.Framework { + /// + /// 旧版资源加载工具类。新代码请使用 ResourceSystem / SceneResourceLoader。 + /// 保留供过渡期使用,后续迁移完成后将移除。 + /// public static class ResourceKit { - public static string SaveSprite(Sprite sprite) - { -#if UNITY_EDITOR - if (!sprite) return ""; - - // 先读Psd的路径 - string psdPath = AssetDatabase.GetAssetPath(sprite); - var psdResPath = GetResourcePath(psdPath); - // 去后缀 - // 找到最后一个点的位置 - int lastDotIndex = psdResPath.LastIndexOf('.'); - // 如果没有点,说明没有扩展名 - if (lastDotIndex == -1) - { - return psdResPath; - } - - // 提取文件名(不包括扩展名) - var purePath = psdResPath.Substring(0, lastDotIndex); - - // 添加后缀 - return purePath + "/" + sprite.name; -#else - return null; -#endif - } - - private static string GetResourcePath(string absolutePath) - { - const string resourcesFolderName = "Resources/"; - var index = absolutePath.IndexOf(resourcesFolderName, StringComparison.Ordinal); - - return index >= 0 ? absolutePath[(index + resourcesFolderName.Length)..] : absolutePath; - } - - public static Sprite LoadSpriteFromPsd(string path) - { - int lastIndex = path.LastIndexOf('/'); - string spriteName = path[(lastIndex + 1)..]; - string resPath = path[..lastIndex]; - - // 加载 - var sprites = Resources.LoadAll(resPath); - - return sprites.FirstOrDefault(sprite => sprite.name == spriteName); - } - public static T LoadAssetSync(string key) where T : UnityEngine.Object { var operation = Addressables.LoadAssetAsync(key); @@ -118,63 +67,5 @@ namespace AibisDream.Framework if (handle.IsValid()) Addressables.Release(handle); } - -#if UNITY_EDITOR - public static bool GetAssetAddressableKey(T asset, out string addressableKey) where T : UnityEngine.Object - { - string path = AssetDatabase.GetAssetPath(asset); - - // 如果是Sprite,并且是Multiple模式,则单独处理 - if (asset is Sprite sprite && AssetImporter.GetAtPath(path) is TextureImporter importer) - { - if (importer.textureType == TextureImporterType.Sprite && importer.spriteImportMode == SpriteImportMode.Multiple) - { - if (TryGetAddressableKey(path, out var addressableKeyPrefix)) - { - addressableKey = $"{addressableKeyPrefix}[{sprite.name}]"; - return true; - } - else - { - addressableKey = addressableKeyPrefix; - return false; - } - } - } - else if (asset is Sprite texture && AssetImporter.GetAtPath(path) is PSDImporter psdImporter) - { - if (TryGetAddressableKey(path, out var addressableKeyPrefix)) - { - addressableKey = $"{addressableKeyPrefix}[{texture.name}]"; - return true; - } - else - { - addressableKey = addressableKeyPrefix; - return false; - } - } - // 其他情况正常输出 - return TryGetAddressableKey(path, out addressableKey); - } - - private static bool TryGetAddressableKey(string path, out string addressableKey) - { - // 获取其在Addressables里的路径 - var settings = UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.Settings; - if (settings != null) - { - var entry = settings.FindAssetEntry(AssetDatabase.AssetPathToGUID(path)); - if (entry != null) - { - addressableKey = entry.address; - return true; - } - } - Debug.Log($"资源{path}不存在于Addressables中"); - addressableKey = path; - return false; - } -#endif } -} \ No newline at end of file +} diff --git a/Assets/Scripts/Framework/ResourceKit/ResourceSystem.cs b/Assets/Scripts/Framework/ResourceKit/ResourceSystem.cs new file mode 100644 index 000000000..406ee2f65 --- /dev/null +++ b/Assets/Scripts/Framework/ResourceKit/ResourceSystem.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.ResourceManagement.AsyncOperations; +using Object = UnityEngine.Object; + +namespace AibisDream.Framework +{ + /// + /// 资源加载系统静态门面。管理 Persistent / Scene 两级 Loader,提供面向业务的统一 API。 + /// 调用方可通过 LoadAsync(自动路由)、LoadPersistentAsync(显式持久化)、LoadSceneAsync(显式场景级)选择目标。 + /// + public static class ResourceSystem + { + private static SceneResourceLoader _persistentLoader; + private static SceneResourceLoader _sceneLoader; + + #region Loader Registration + + public static void RegisterPersistentLoader(SceneResourceLoader loader) + { + if (_persistentLoader != null && _persistentLoader != loader) + Debug.LogWarning("[ResourceSystem] Replacing existing persistent loader."); + _persistentLoader = loader; + } + + public static void UnregisterPersistentLoader(SceneResourceLoader loader) + { + if (_persistentLoader == loader) + _persistentLoader = null; + } + + public static void RegisterSceneLoader(SceneResourceLoader loader) + { + if (_sceneLoader != null && _sceneLoader != loader) + Debug.LogWarning("[ResourceSystem] Replacing existing scene loader. Was the previous scene properly unloaded?"); + _sceneLoader = loader; + } + + public static void UnregisterSceneLoader(SceneResourceLoader loader) + { + if (_sceneLoader == loader) + _sceneLoader = null; + } + + public static void UnregisterSceneLoader() + { + _sceneLoader = null; + } + + #endregion + + #region Loader Access + + public static SceneResourceLoader PersistentLoader => _persistentLoader; + public static SceneResourceLoader CurrentSceneLoader => _sceneLoader; + + private static SceneResourceLoader ActiveLoader + { + get + { + if (_sceneLoader != null) return _sceneLoader; + if (_persistentLoader != null) return _persistentLoader; + Debug.LogError("[ResourceSystem] No loader registered. Ensure a SceneResourceLoader exists in the scene."); + return null; + } + } + + #endregion + + #region Auto-Routed API (uses ActiveLoader: scene first, falls back to persistent) + + public static AsyncOperationHandle LoadAsync(string key) where T : Object + => ActiveLoader.LoadAsync(key); + + public static void LoadAsync(string key, Action onComplete, Action onError = null) where T : Object + => ActiveLoader.LoadAsync(key, onComplete, onError); + + #endregion + + #region Persistent Loader API + + public static AsyncOperationHandle LoadPersistentAsync(string key) where T : Object + { + EnsureLoader(_persistentLoader, "persistent"); + return _persistentLoader.LoadAsync(key); + } + + public static void LoadPersistentAsync(string key, Action onComplete, Action onError = null) + where T : Object + { + EnsureLoader(_persistentLoader, "persistent"); + _persistentLoader.LoadAsync(key, onComplete, onError); + } + + #endregion + + #region Scene Loader API + + public static AsyncOperationHandle LoadSceneAsync(string key) where T : Object + { + EnsureLoader(_sceneLoader, "scene"); + return _sceneLoader.LoadAsync(key); + } + + public static void LoadSceneAsync(string key, Action onComplete, Action onError = null) + where T : Object + { + EnsureLoader(_sceneLoader, "scene"); + _sceneLoader.LoadAsync(key, onComplete, onError); + } + + #endregion + + #region Preload & Release + + public static AsyncOperationHandle> PreloadByLabel(string label) where T : Object + => ActiveLoader.PreloadByLabel(label); + + public static void EarlyRelease(string key) + => ActiveLoader.EarlyRelease(key); + + public static void EarlyRelease(AsyncOperationHandle handle) + => ActiveLoader.EarlyRelease(handle); + + #endregion + + private static void EnsureLoader(SceneResourceLoader loader, string loaderName) + { + if (loader == null) + Debug.LogError($"[ResourceSystem] No {loaderName} loader registered."); + } + } +} diff --git a/Assets/Scripts/Framework/ResourceKit/ResourceSystem.cs.meta b/Assets/Scripts/Framework/ResourceKit/ResourceSystem.cs.meta new file mode 100644 index 000000000..3bfe5549f --- /dev/null +++ b/Assets/Scripts/Framework/ResourceKit/ResourceSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a5d17f61bc57f284794ace405f478222 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Framework/ResourceKit/SceneResourceLoader.cs b/Assets/Scripts/Framework/ResourceKit/SceneResourceLoader.cs new file mode 100644 index 000000000..1d95ccbd8 --- /dev/null +++ b/Assets/Scripts/Framework/ResourceKit/SceneResourceLoader.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement.AsyncOperations; +using Object = UnityEngine.Object; + +namespace AibisDream.Framework +{ + public class SceneResourceLoader : MonoBehaviour + { + [SerializeField] private bool isPersistent; + + private readonly Dictionary _cache = new(); + private readonly List _uncachedHandles = new(); + + private void Awake() + { + if (isPersistent) + ResourceSystem.RegisterPersistentLoader(this); + else + ResourceSystem.RegisterSceneLoader(this); + } + + private void OnDestroy() + { + ReleaseAll(); + + if (isPersistent) + ResourceSystem.UnregisterPersistentLoader(this); + else + ResourceSystem.UnregisterSceneLoader(this); + } + + /// + /// 异步加载资源(带缓存,同 Key 不重复加载)。 + /// 返回的 Handle 可在协程中 yield return。 + /// + public AsyncOperationHandle LoadAsync(string key) where T : Object + { + if (_cache.TryGetValue(key, out var cached) && cached.IsValid()) + return cached.Convert(); + + var handle = Addressables.LoadAssetAsync(key); + _cache[key] = handle; + handle.Completed += op => + { + if (op.Status != AsyncOperationStatus.Succeeded) + Debug.LogError($"[SceneResourceLoader] Failed to load '{key}': {op.OperationException}"); + }; + return handle; + } + + /// + /// 异步加载资源(回调风格,带缓存)。 + /// + public void LoadAsync(string key, Action onComplete, Action onError = null) where T : Object + { + var handle = LoadAsync(key); + handle.Completed += op => + { + if (op.Status == AsyncOperationStatus.Succeeded) + onComplete?.Invoke(op.Result); + else + onError?.Invoke($"Failed to load '{key}': {op.OperationException}"); + }; + } + + /// + /// 异步加载资源(不走缓存,每次产生独立 Handle)。 + /// 适用于 Timeline 等需要独立生命周期管理的资源。 + /// + public AsyncOperationHandle LoadUncachedAsync(string key) where T : Object + { + var handle = Addressables.LoadAssetAsync(key); + _uncachedHandles.Add(handle); + handle.Completed += op => + { + if (op.Status != AsyncOperationStatus.Succeeded) + Debug.LogError($"[SceneResourceLoader] Failed to load '{key}': {op.OperationException}"); + }; + return handle; + } + + /// + /// 按 Label 批量预加载资源。返回的 Handle 可在协程中 yield return。 + /// + public AsyncOperationHandle> PreloadByLabel(string label) where T : Object + { + var handle = Addressables.LoadAssetsAsync(label, null); + _uncachedHandles.Add(handle); + handle.Completed += op => + { + if (op.Status != AsyncOperationStatus.Succeeded) + Debug.LogError($"[SceneResourceLoader] Failed to preload label '{label}': {op.OperationException}"); + }; + return handle; + } + + /// + /// 提前释放指定 Key 的缓存资源。 + /// + public void EarlyRelease(string key) + { + if (_cache.TryGetValue(key, out var handle)) + { + if (handle.IsValid()) + Addressables.Release(handle); + _cache.Remove(key); + } + } + + /// + /// 提前释放指定的非缓存 Handle。 + /// + public void EarlyRelease(AsyncOperationHandle handle) + { + if (handle.IsValid()) + { + Addressables.Release(handle); + _uncachedHandles.Remove(handle); + } + } + + /// + /// 释放所有持有的资源 Handle。由 SceneLoader 在卸载场景前调用,或 OnDestroy 时自动调用。 + /// + public void ReleaseAll() + { + foreach (var kvp in _cache) + { + if (kvp.Value.IsValid()) + Addressables.Release(kvp.Value); + } + _cache.Clear(); + + foreach (var handle in _uncachedHandles) + { + if (handle.IsValid()) + Addressables.Release(handle); + } + _uncachedHandles.Clear(); + } + + public int CachedCount => _cache.Count; + public int UncachedCount => _uncachedHandles.Count; + public int TotalHandleCount => _cache.Count + _uncachedHandles.Count; + } +} diff --git a/Assets/Scripts/Framework/ResourceKit/SceneResourceLoader.cs.meta b/Assets/Scripts/Framework/ResourceKit/SceneResourceLoader.cs.meta new file mode 100644 index 000000000..85fc710d0 --- /dev/null +++ b/Assets/Scripts/Framework/ResourceKit/SceneResourceLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef81dc021856ce1458b39c19b69cfc9b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: