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: