using UnityEngine; using UnityGameFramework.Runtime; using System.Collections.Generic; using System.Linq; using ArtResource; using GameFramework.Resource; using Spine.Unity; using System; namespace CrazyMaple { /// /// 美术资源运行时管理器(单例MonoBehaviour)- 优化版 /// 三级缓存:SO缓存 -> Sprite缓存 -> Spine缓存 /// /// 改进点: /// 1. 增加初始化完成回调,支持外部等待 /// 2. 正确实现AssetBundle资源加载(运行时) /// 3. 优化初始化时序,避免竞态条件 /// 4. 支持预加载配置(从manifest读取) /// public class ArtResourceManager : MonoBehaviour { private static ArtResourceManager instance; public static ArtResourceManager Instance { get { if (instance == null) { GameObject go = new GameObject("ArtResourceManager"); instance = go.AddComponent(); DontDestroyOnLoad(go); } return instance; } } #region 缓存结构 /// /// 常量:SO文件夹路径 /// private const string SO_ROOT_PATH = "Assets/Art_SubModule/Art_SO"; /// /// SO文件缓存(启动时一次性加载所有SO,速度快~几十毫秒) /// Key: TableId /// private Dictionary allTables = new Dictionary(); /// /// SO名称索引(支持按Name查询) /// Key: TableName /// private Dictionary tablesByName = new Dictionary(); /// /// Sprite资源缓存(重量级,按需加载) /// Key: "tableId_itemId" /// private Dictionary loadedSprites = new Dictionary(); /// /// Spine资源缓存(重量级,按需加载) /// Key: "tableId_itemId" /// private Dictionary loadedSpines = new Dictionary(); /// /// 正在加载的表资源(防止重复加载) /// Key: TableId, Value: 回调列表 /// private Dictionary>> loadingTables = new Dictionary>>(); /// /// 正在加载的Sprite(防止重复加载) /// Key: "tableId_itemId", Value: 回调列表 /// private Dictionary>> loadingSprites = new Dictionary>>(); /// /// 正在加载的Spine(防止重复加载) /// Key: "tableId_itemId", Value: 回调列表 /// private Dictionary>> loadingSpines = new Dictionary>>(); /// /// SO是否已初始化完成 /// private bool isInitialized = false; /// /// 初始化完成时的回调列表 /// private List> initializeCallbacks = new List>(); /// /// Manifest数据(包含预加载配置) /// private ArtTableManifest manifest; #endregion private void Awake() { if (instance != null && instance != this) { Destroy(gameObject); return; } instance = this; DontDestroyOnLoad(gameObject); // 不在Awake中自动初始化,等待外部调用 // 这样可以让ProcedurePreload控制初始化时机 } #region 初始化 /// /// 初始化资源管理器 /// Editor模式:延迟加载SO(避免反序列化资源引用导致卡顿) /// Runtime模式:加载所有SO(Runtime下加载SO很快,不会有性能问题) /// /// 初始化完成回调(true=成功,false=失败) public void Initialize(Action onComplete = null) { if (isInitialized) { Debug.LogWarning("[ArtResourceManager] 已初始化,直接执行回调"); onComplete?.Invoke(true); return; } // 加入回调队列 if (onComplete != null) { initializeCallbacks.Add(onComplete); } // 如果已经在加载中,直接返回 if (initializeCallbacks.Count > 1) { Debug.Log("[ArtResourceManager] 正在初始化中,已加入回调队列"); return; } var startTime = Time.realtimeSinceStartup; #if UNITY_EDITOR // Editor模式:延迟加载(避免反序列化大量资源引用) Debug.Log("[ArtResourceManager] 开始初始化 ArtResourceManager(Editor延迟加载模式)..."); LoadManifestOnly(success => { var duration = (Time.realtimeSinceStartup - startTime) * 1000f; if (success) { isInitialized = true; Debug.Log($"[ArtResourceManager] 初始化完成(Editor延迟加载): 索引了 {manifest.tablePaths.Length} 个表,耗时 {duration:F2}ms"); Debug.Log($"[ArtResourceManager] SO将在首次使用时按需加载"); // 自动预加载配置的表(如果有) AutoPreloadTables(); } else { Debug.LogError("[ArtResourceManager] 初始化失败"); } // 通知所有等待的回调 var callbacks = new List>(initializeCallbacks); initializeCallbacks.Clear(); foreach (var callback in callbacks) { callback?.Invoke(success); } }); #else // Runtime模式:直接加载所有SO(Runtime下加载SO很快,不会反序列化资源引用) Debug.Log("[ArtResourceManager] 开始初始化 ArtResourceManager(Runtime全量加载模式)..."); LoadAllTables(success => { var duration = (Time.realtimeSinceStartup - startTime) * 1000f; if (success) { isInitialized = true; Debug.Log($"[ArtResourceManager] 初始化完成(Runtime全量加载): 加载了 {allTables.Count} 个表,耗时 {duration:F2}ms"); // 自动预加载配置的表(如果有) AutoPreloadTables(); } else { Debug.LogError("[ArtResourceManager] 初始化失败"); } // 通知所有等待的回调 var callbacks = new List>(initializeCallbacks); initializeCallbacks.Clear(); foreach (var callback in callbacks) { callback?.Invoke(success); } }); #endif } /// /// 只加载manifest索引文件,不加载SO(延迟加载优化) /// private void LoadManifestOnly(Action onComplete) { const string manifestPath = "Assets/Art_SubModule/Art_SO/art_table_manifest.json"; var loadCallbacks = new LoadAssetCallbacks( // 成功回调 (assetName, asset, duration, userData) => { var manifestAsset = asset as TextAsset; if (manifestAsset == null) { Debug.LogError($"[ArtResourceManager] 索引文件格式错误: {manifestPath}"); onComplete?.Invoke(false); return; } // 解析JSON获取所有SO路径 manifest = JsonUtility.FromJson(manifestAsset.text); if (manifest == null || manifest.tablePaths == null || manifest.tablePaths.Length == 0) { Debug.LogError("[ArtResourceManager] 索引文件为空,没有找到任何SO"); onComplete?.Invoke(false); return; } Debug.Log($"[ArtResourceManager] 成功加载manifest索引: {manifest.tablePaths.Length} 个SO将按需加载"); onComplete?.Invoke(true); }, // 失败回调 (assetName, status, errorMessage, userData) => { Debug.LogError($"[ArtResourceManager] 加载索引文件失败: {manifestPath}, {errorMessage}"); Debug.LogError("[ArtResourceManager] 请在Editor下运行工具生成art_table_manifest.json"); onComplete?.Invoke(false); } ); // 使用GameEntry.Resource.LoadAsset加载索引文件 GameEntry.Resource.LoadAsset(manifestPath, typeof(TextAsset), loadCallbacks); } /// /// 尝试同步加载表(用于同步API如GetSpritePath) /// private bool TrySyncLoadTable(string tableName, out ArtTableSO table) { table = null; // 检查manifest中是否有该表 if (manifest == null || manifest.tablePaths == null) { return false; } // 查找SO路径 string soPath = null; foreach (var path in manifest.tablePaths) { if (path.Contains(tableName)) { soPath = path; break; } } if (string.IsNullOrEmpty(soPath)) { return false; } // 同步加载SO Debug.Log($"[ArtResourceManager] 同步按需加载SO: {tableName}"); #if UNITY_EDITOR // Editor模式下使用AssetDatabase同步加载 var so = UnityEditor.AssetDatabase.LoadAssetAtPath(soPath); if (so != null) { RegisterTable(so, soPath); table = so; return true; } #else // Runtime模式下无法同步加载,返回false Debug.LogWarning($"[ArtResourceManager] Runtime模式不支持同步加载表: {tableName},请使用异步API"); #endif return false; } /// /// 按需加载单个SO(如果还没加载) /// private void LoadTableSO(string tableName, Action onComplete) { // 检查是否已加载 if (tablesByName.TryGetValue(tableName, out var existingTable)) { onComplete?.Invoke(true); return; } // 从manifest中查找路径 if (manifest == null || manifest.tablePaths == null) { Debug.LogError($"[ArtResourceManager] Manifest未加载,无法加载表 {tableName}"); onComplete?.Invoke(false); return; } // 查找SO路径(通过文件名匹配) string soPath = null; foreach (var path in manifest.tablePaths) { if (path.Contains(tableName)) { soPath = path; break; } } if (string.IsNullOrEmpty(soPath)) { Debug.LogError($"[ArtResourceManager] 在manifest中找不到表 {tableName} 的路径"); onComplete?.Invoke(false); return; } // 加载SO Debug.Log($"[ArtResourceManager] 按需加载SO: {tableName}"); LoadSingleTable(soPath, (success, loadTime) => { if (success) { Debug.Log($"[ArtResourceManager] SO加载成功: {tableName}, 耗时 {loadTime:F2}ms"); } else { Debug.LogError($"[ArtResourceManager] SO加载失败: {tableName}"); } onComplete?.Invoke(success); }); } /// /// 加载所有SO文件(只加载SO本身,不加载引用的资源) /// 需要先加载索引文件 art_table_manifest.json,获取所有SO的AssetName列表 /// private void LoadAllTables(Action onComplete) { const string manifestPath = "Assets/Art_SubModule/Art_SO/art_table_manifest.json"; // 创建加载回调 var loadCallbacks = new LoadAssetCallbacks( // 成功回调 (assetName, asset, duration, userData) => { var manifestAsset = asset as TextAsset; if (manifestAsset == null) { Debug.LogError($"[ArtResourceManager] 索引文件格式错误: {manifestPath}"); onComplete?.Invoke(false); return; } // 解析JSON获取所有SO路径 manifest = JsonUtility.FromJson(manifestAsset.text); if (manifest == null || manifest.tablePaths == null || manifest.tablePaths.Length == 0) { Debug.LogError("[ArtResourceManager] 索引文件为空,没有找到任何SO"); onComplete?.Invoke(false); return; } // 遍历加载所有SO int totalCount = manifest.tablePaths.Length; int loadedCount = 0; int failedCount = 0; var soLoadStartTime = Time.realtimeSinceStartup; var soLoadTimes = new List<(string path, float ms)>(); Debug.Log($"[ArtResourceManager] 开始加载 {totalCount} 个SO文件..."); foreach (var assetPath in manifest.tablePaths) { LoadSingleTable(assetPath, (success, loadTime) => { if (success) { loadedCount++; soLoadTimes.Add((assetPath, loadTime)); } else failedCount++; // 检查是否全部加载完成 if (loadedCount + failedCount >= totalCount) { var totalDuration = (Time.realtimeSinceStartup - soLoadStartTime) * 1000f; bool allSuccess = failedCount == 0; Debug.Log($"[ArtResourceManager] SO加载完成: 成功={loadedCount}, 失败={failedCount}, 总计={totalCount}, 总耗时={totalDuration:F2}ms"); // 显示最慢的前5个SO if (soLoadTimes.Count > 0) { var slowest = soLoadTimes.OrderByDescending(x => x.ms).Take(5).ToList(); Debug.Log($"[ArtResourceManager] 最慢的{slowest.Count}个SO:"); foreach (var item in slowest) { Debug.Log($" - {item.path}: {item.ms:F2}ms"); } } onComplete?.Invoke(allSuccess); } }); } }, // 失败回调 (assetName, status, errorMessage, userData) => { Debug.LogError($"[ArtResourceManager] 加载索引文件失败: {manifestPath}, {errorMessage}"); Debug.LogError("[ArtResourceManager] 请在Editor下运行工具生成art_table_manifest.json"); onComplete?.Invoke(false); } ); // 使用GameEntry.Resource.LoadAsset加载索引文件 GameEntry.Resource.LoadAsset(manifestPath, typeof(TextAsset), loadCallbacks); } /// /// 加载单个SO文件 /// private void LoadSingleTable(string assetPath, Action onComplete) { var startTime = Time.realtimeSinceStartup; var loadCallbacks = new LoadAssetCallbacks( // 成功回调 (assetName, asset, duration, userData) => { var loadTime = (Time.realtimeSinceStartup - startTime) * 1000f; var table = asset as ArtTableSO; if (table != null) { RegisterTable(table, assetPath); onComplete?.Invoke(true, loadTime); } else { Debug.LogError($"[ArtResourceManager] 加载SO失败: {assetPath}"); onComplete?.Invoke(false, loadTime); } }, // 失败回调 (assetName, status, errorMessage, userData) => { var loadTime = (Time.realtimeSinceStartup - startTime) * 1000f; Debug.LogError($"[ArtResourceManager] 加载SO异常 [{assetPath}]: {errorMessage}"); onComplete?.Invoke(false, loadTime); } ); // 使用GameEntry.Resource.LoadAsset加载SO GameEntry.Resource.LoadAsset(assetPath, typeof(ArtTableSO), loadCallbacks); } /// /// 注册SO到缓存 /// 注意:路径已经在ArtItemData中存储,无需额外缓存 /// private void RegisterTable(ArtTableSO table, string tablePath) { if (allTables.ContainsKey(table.TableId)) { Debug.LogWarning($"[ArtResourceManager] 表ID重复: {table.TableId} ({table.TableName})"); return; } allTables[table.TableId] = table; if (!string.IsNullOrEmpty(table.TableName)) { if (tablesByName.ContainsKey(table.TableName)) { Debug.LogWarning($"[ArtResourceManager] 表名称重复: {table.TableName}"); } else { tablesByName[table.TableName] = table; } } Debug.Log($"[ArtResourceManager] 注册表: {table.TableId} - {table.TableName} ({table.Items.Count} 项)"); } /// /// 自动预加载manifest中配置的表 /// private void AutoPreloadTables() { if (manifest == null || manifest.preloadTableIds == null || manifest.preloadTableIds.Length == 0) { Debug.Log("[ArtResourceManager] 没有配置自动预加载的表"); return; } Debug.Log($"[ArtResourceManager] 开始自动预加载 {manifest.preloadTableIds.Length} 个表..."); foreach (var tableId in manifest.preloadTableIds) { LoadTable(tableId, success => { if (success) { Debug.Log($"[ArtResourceManager] 自动预加载表 {tableId} 成功"); } else { Debug.LogWarning($"[ArtResourceManager] 自动预加载表 {tableId} 失败"); } }); } } /// /// 索引文件的数据结构(包含预加载配置) /// [Serializable] private class ArtTableManifest { public string[] tablePaths; public int[] preloadTableIds; } #endregion #region 表级资源加载 /// /// 异步加载资源表的所有引用资源(Sprite + Spine) /// 适用场景:预加载关键表、界面预加载 /// /// 表ID /// 加载完成回调(true=成功,false=失败) public void LoadTable(int tableId, Action onComplete = null) { if (!isInitialized) { Debug.LogError("[ArtResourceManager] 未初始化,请先调用Initialize()并等待回调完成"); onComplete?.Invoke(false); return; } // 检查表是否存在 if (!allTables.TryGetValue(tableId, out ArtTableSO table)) { Debug.LogError($"[ArtResourceManager] LoadTable失败: 找不到表ID {tableId}"); onComplete?.Invoke(false); return; } // 检查是否正在加载 if (loadingTables.ContainsKey(tableId)) { Debug.Log($"[ArtResourceManager] 资源表 {tableId} 正在加载中,加入回调队列"); loadingTables[tableId].Add(onComplete); return; } // 开始加载 loadingTables[tableId] = new List> { onComplete }; Debug.Log($"[ArtResourceManager] 开始预加载资源表 {tableId} ({table.TableName}) 的所有资源(共 {table.Items.Count} 项)"); int totalCount = 0; int loadedCount = 0; int failedCount = 0; // 统计需要加载的资源数量 foreach (var item in table.Items) { // 检查Sprite是否需要加载(有引用或有路径) if (item.Sprite != null || !string.IsNullOrEmpty(item.SpritePath)) { totalCount++; } // 检查Spine是否需要加载(有引用或有路径) if (item.SpineAsset != null || !string.IsNullOrEmpty(item.SpineAssetPath)) { totalCount++; } } if (totalCount == 0) { Debug.LogWarning($"[ArtResourceManager] 资源表 {tableId} 没有任何Sprite或Spine资源"); NotifyLoadTableComplete(tableId, true); return; } // 加载所有资源 foreach (var item in table.Items) { // 加载Sprite if (item.Sprite != null || !string.IsNullOrEmpty(item.SpritePath)) { PreloadSprite(tableId, item.Id, item, success => { if (success) loadedCount++; else failedCount++; CheckComplete(); }); } // 加载Spine if (item.SpineAsset != null || !string.IsNullOrEmpty(item.SpineAssetPath)) { PreloadSpine(tableId, item.Id, item, success => { if (success) loadedCount++; else failedCount++; CheckComplete(); }); } } void CheckComplete() { if (loadedCount + failedCount >= totalCount) { bool allSuccess = failedCount == 0; Debug.Log($"[ArtResourceManager] 资源表 {tableId} 预加载完成: 成功={loadedCount}, 失败={failedCount}, 总计={totalCount}"); NotifyLoadTableComplete(tableId, allSuccess); } } } /// /// 按Name加载资源表 /// public void LoadTableByName(string tableName, Action onComplete = null) { // 延迟加载模式:先确保SO已加载 if (!tablesByName.TryGetValue(tableName, out ArtTableSO table)) { // SO还没加载,先按需加载SO LoadTableSO(tableName, success => { if (success && tablesByName.TryGetValue(tableName, out table)) { LoadTable(table.TableId, onComplete); } else { Debug.LogError($"[ArtResourceManager] LoadTableByName失败: 无法加载表 '{tableName}'"); onComplete?.Invoke(false); } }); return; } LoadTable(table.TableId, onComplete); } /// /// 通知加载表完成 /// private void NotifyLoadTableComplete(int tableId, bool success) { if (loadingTables.ContainsKey(tableId)) { var callbacks = loadingTables[tableId]; loadingTables.Remove(tableId); foreach (var callback in callbacks) { callback?.Invoke(success); } } } #endregion #region 资源加载(运行时按需加载) /// /// 加载Sprite资源(表ID + 资源项ID) /// public void LoadSprite(int tableId, int itemId, Action onComplete) { LoadSpriteInternal(tableId, itemId, null, onComplete); } /// /// 加载Sprite资源(表ID + 资源项Name) /// public void LoadSprite(int tableId, string itemName, Action onComplete) { LoadSpriteInternal(tableId, null, itemName, onComplete); } /// /// 加载Sprite资源(表Name + 资源项ID) /// public void LoadSprite(string tableName, int itemId, Action onComplete) { // 延迟加载模式:先确保SO已加载 if (!tablesByName.TryGetValue(tableName, out ArtTableSO table)) { // SO还没加载,先按需加载SO LoadTableSO(tableName, success => { if (success && tablesByName.TryGetValue(tableName, out table)) { LoadSpriteInternal(table.TableId, itemId, null, onComplete); } else { Debug.LogError($"[ArtResourceManager] LoadSprite失败: 无法加载表 '{tableName}'"); onComplete?.Invoke(null); } }); return; } LoadSpriteInternal(table.TableId, itemId, null, onComplete); } /// /// 加载Sprite资源(表Name + 资源项Name) /// public void LoadSprite(string tableName, string itemName, Action onComplete) { // 延迟加载模式:先确保SO已加载 if (!tablesByName.TryGetValue(tableName, out ArtTableSO table)) { // SO还没加载,先按需加载SO LoadTableSO(tableName, success => { if (success && tablesByName.TryGetValue(tableName, out table)) { LoadSpriteInternal(table.TableId, null, itemName, onComplete); } else { Debug.LogError($"[ArtResourceManager] LoadSprite失败: 无法加载表 '{tableName}'"); onComplete?.Invoke(null); } }); return; } LoadSpriteInternal(table.TableId, null, itemName, onComplete); } /// /// 内部加载Sprite实现 /// private void LoadSpriteInternal(int tableId, int? itemId, string itemName, Action onComplete) { if (!isInitialized) { Debug.LogError("[ArtResourceManager] 未初始化,请先调用Initialize()并等待回调完成"); onComplete?.Invoke(null); return; } // 从SO中查找Item if (!allTables.TryGetValue(tableId, out ArtTableSO table)) { Debug.LogError($"[ArtResourceManager] LoadSprite失败: 找不到表ID {tableId}"); onComplete?.Invoke(null); return; } ArtItemData item; if (itemId.HasValue) { item = table.Items.FirstOrDefault(x => x.Id == itemId.Value); if (item == null) { Debug.LogError($"[ArtResourceManager] LoadSprite失败: 资源表{tableId} ({table.TableName}) 中找不到ID为{itemId.Value} 的资源项"); onComplete?.Invoke(null); return; } } else { item = table.Items.FirstOrDefault(x => x.Name == itemName); if (item == null) { Debug.LogError($"[ArtResourceManager] LoadSprite失败: 资源表{tableId} ({table.TableName}) 中找不到Name为'{itemName}' 的资源项"); onComplete?.Invoke(null); return; } } string cacheKey = $"{tableId}_{item.Id}"; // 检查Sprite缓存 if (loadedSprites.TryGetValue(cacheKey, out Sprite cachedSprite)) { onComplete?.Invoke(cachedSprite); return; } // 检查是否正在加载 if (loadingSprites.ContainsKey(cacheKey)) { loadingSprites[cacheKey].Add(onComplete); return; } // 开始加载Sprite loadingSprites[cacheKey] = new List> { onComplete }; LoadSpriteAsset(item, sprite => { // 缓存结果 if (sprite != null) { loadedSprites[cacheKey] = sprite; } // 通知所有等待的回调 var callbacks = loadingSprites[cacheKey]; loadingSprites.Remove(cacheKey); foreach (var callback in callbacks) { callback?.Invoke(sprite); } }); } /// /// 加载Spine资源(表ID + 资源项ID) /// public void LoadSpine(int tableId, int itemId, Action onComplete) { LoadSpineInternal(tableId, itemId, null, onComplete); } /// /// 加载Spine资源(表ID + 资源项Name) /// public void LoadSpine(int tableId, string itemName, Action onComplete) { LoadSpineInternal(tableId, null, itemName, onComplete); } /// /// 加载Spine资源(表Name + 资源项ID) /// public void LoadSpine(string tableName, int itemId, Action onComplete) { // 延迟加载模式:先确保SO已加载 if (!tablesByName.TryGetValue(tableName, out ArtTableSO table)) { // SO还没加载,先按需加载SO LoadTableSO(tableName, success => { if (success && tablesByName.TryGetValue(tableName, out table)) { LoadSpineInternal(table.TableId, itemId, null, onComplete); } else { Debug.LogError($"[ArtResourceManager] LoadSpine失败: 无法加载表 '{tableName}'"); onComplete?.Invoke(null, ""); } }); return; } LoadSpineInternal(table.TableId, itemId, null, onComplete); } /// /// 加载Spine资源(表Name + 资源项Name) /// public void LoadSpine(string tableName, string itemName, Action onComplete) { // 延迟加载模式:先确保SO已加载 if (!tablesByName.TryGetValue(tableName, out ArtTableSO table)) { // SO还没加载,先按需加载SO LoadTableSO(tableName, success => { if (success && tablesByName.TryGetValue(tableName, out table)) { LoadSpineInternal(table.TableId, null, itemName, onComplete); } else { Debug.LogError($"[ArtResourceManager] LoadSpine失败: 无法加载表 '{tableName}'"); onComplete?.Invoke(null, ""); } }); return; } LoadSpineInternal(table.TableId, null, itemName, onComplete); } /// /// 内部加载Spine实现 /// private void LoadSpineInternal(int tableId, int? itemId, string itemName, Action onComplete) { if (!isInitialized) { Debug.LogError("[ArtResourceManager] 未初始化,请先调用Initialize()并等待回调完成"); onComplete?.Invoke(null, ""); return; } // 从SO中查找Item if (!allTables.TryGetValue(tableId, out ArtTableSO table)) { Debug.LogError($"[ArtResourceManager] LoadSpine失败: 找不到表ID {tableId}"); onComplete?.Invoke(null, ""); return; } ArtItemData item; if (itemId.HasValue) { item = table.Items.FirstOrDefault(x => x.Id == itemId.Value); if (item == null) { Debug.LogError($"[ArtResourceManager] LoadSpine失败: 资源表{tableId} ({table.TableName}) 中找不到ID为{itemId.Value} 的资源项"); onComplete?.Invoke(null, ""); return; } } else { item = table.Items.FirstOrDefault(x => x.Name == itemName); if (item == null) { Debug.LogError($"[ArtResourceManager] LoadSpine失败: 资源表{tableId} ({table.TableName}) 中找不到Name为'{itemName}' 的资源项"); onComplete?.Invoke(null, ""); return; } } string cacheKey = $"{tableId}_{item.Id}"; // 检查Spine缓存 if (loadedSpines.TryGetValue(cacheKey, out SkeletonDataAsset cachedSpine)) { onComplete?.Invoke(cachedSpine, item.SpineAnimName); return; } // 检查是否正在加载 if (loadingSpines.ContainsKey(cacheKey)) { loadingSpines[cacheKey].Add(spine => { onComplete?.Invoke(spine, item.SpineAnimName); }); return; } // 开始加载Spine loadingSpines[cacheKey] = new List> { spine => { onComplete?.Invoke(spine, item.SpineAnimName); }}; LoadSpineAsset(item, spine => { // 缓存结果 if (spine != null) { loadedSpines[cacheKey] = spine; } // 通知所有等待的回调 var callbacks = loadingSpines[cacheKey]; loadingSpines.Remove(cacheKey); foreach (var callback in callbacks) { callback?.Invoke(spine); } }); } /// /// 预加载Sprite(内部方法) /// private void PreloadSprite(int tableId, int itemId, ArtItemData item, Action onComplete) { string cacheKey = $"{tableId}_{itemId}"; if (loadedSprites.ContainsKey(cacheKey)) { onComplete?.Invoke(true); return; } LoadSpriteAsset(item, sprite => { if (sprite != null) { loadedSprites[cacheKey] = sprite; onComplete?.Invoke(true); } else { onComplete?.Invoke(false); } }); } /// /// 预加载Spine(内部方法) /// private void PreloadSpine(int tableId, int itemId, ArtItemData item, Action onComplete) { string cacheKey = $"{tableId}_{itemId}"; if (loadedSpines.ContainsKey(cacheKey)) { onComplete?.Invoke(true); return; } LoadSpineAsset(item, spine => { if (spine != null) { loadedSpines[cacheKey] = spine; onComplete?.Invoke(true); } else { onComplete?.Invoke(false); } }); } /// /// 加载Sprite资源(同时支持Editor和Runtime模式) /// Editor模式:优先使用Sprite引用 /// Runtime模式:使用SpritePath通过GameFramework加载 /// private void LoadSpriteAsset(ArtItemData item, Action onComplete) { // Editor模式:直接使用引用 if (item.Sprite != null) { onComplete?.Invoke(item.Sprite); return; } // Runtime模式:需要通过路径加载 if (string.IsNullOrEmpty(item.SpritePath)) { Debug.LogError($"[ArtResourceManager] Sprite资源引用和路径都为空: {item.Name}"); onComplete?.Invoke(null); return; } var loadCallbacks = new LoadAssetCallbacks( // 成功回调 (assetName, asset, duration, userData) => { var sprite = asset as Sprite; if (sprite != null) { onComplete?.Invoke(sprite); } else { Debug.LogError($"[ArtResourceManager] 加载Sprite失败,资源类型错误: {item.SpritePath}"); onComplete?.Invoke(null); } }, // 失败回调 (assetName, status, errorMessage, userData) => { Debug.LogError($"[ArtResourceManager] 加载Sprite异常 [{item.SpritePath}]: {errorMessage}"); onComplete?.Invoke(null); } ); GameEntry.Resource.LoadAsset(item.SpritePath, typeof(Sprite), loadCallbacks); } /// /// 加载Spine资源(同时支持Editor和Runtime模式) /// Editor模式:优先使用SpineAsset引用 /// Runtime模式:使用SpineAssetPath通过GameFramework加载 /// private void LoadSpineAsset(ArtItemData item, Action onComplete) { // Editor模式:直接使用引用 if (item.SpineAsset != null) { onComplete?.Invoke(item.SpineAsset); return; } // Runtime模式:需要通过路径加载 if (string.IsNullOrEmpty(item.SpineAssetPath)) { Debug.Log($"[ArtResourceManager] Spine资源引用和路径都为空: {item.Name}"); onComplete?.Invoke(null); return; } var loadCallbacks = new LoadAssetCallbacks( // 成功回调 (assetName, asset, duration, userData) => { var spine = asset as SkeletonDataAsset; if (spine != null) { onComplete?.Invoke(spine); } else { Debug.LogError($"[ArtResourceManager] 加载Spine失败,资源类型错误: {item.SpineAssetPath}"); onComplete?.Invoke(null); } }, // 失败回调 (assetName, status, errorMessage, userData) => { Debug.LogError($"[ArtResourceManager] 加载Spine异常 [{item.SpineAssetPath}]: {errorMessage}"); onComplete?.Invoke(null); } ); GameEntry.Resource.LoadAsset(item.SpineAssetPath, typeof(SkeletonDataAsset), loadCallbacks); } #endregion #region 查询方法 /// /// 检查是否已初始化完成 /// public bool IsInitialized => isInitialized; /// /// 获取Spine动画名称(表ID + 资源项ID) /// public string GetSpineAnimName(int tableId, int itemId) { return GetSpineAnimNameInternal(tableId, itemId, null); } /// /// 获取Spine动画名称(表ID + 资源项Name) /// public string GetSpineAnimName(int tableId, string itemName) { return GetSpineAnimNameInternal(tableId, null, itemName); } /// /// 获取Spine动画名称(表Name + 资源项ID) /// public string GetSpineAnimName(string tableName, int itemId) { if (!tablesByName.TryGetValue(tableName, out ArtTableSO table)) { Debug.LogError($"[ArtResourceManager] GetSpineAnimName失败: 找不到表名称 '{tableName}'"); return ""; } return GetSpineAnimNameInternal(table.TableId, itemId, null); } /// /// 获取Spine动画名称(表Name + 资源项Name) /// public string GetSpineAnimName(string tableName, string itemName) { if (!tablesByName.TryGetValue(tableName, out ArtTableSO table)) { Debug.LogError($"[ArtResourceManager] GetSpineAnimName失败: 找不到表名称 '{tableName}'"); return ""; } return GetSpineAnimNameInternal(table.TableId, null, itemName); } /// /// 内部获取Spine动画名称实现 /// private string GetSpineAnimNameInternal(int tableId, int? itemId, string itemName) { if (!allTables.TryGetValue(tableId, out ArtTableSO table)) { Debug.LogError($"[ArtResourceManager] GetSpineAnimName失败: 找不到表ID {tableId}"); return ""; } ArtItemData item; if (itemId.HasValue) { item = table.Items.FirstOrDefault(x => x.Id == itemId.Value); if (item == null) { Debug.LogError($"[ArtResourceManager] GetSpineAnimName失败: 资源表{tableId} ({table.TableName}) 中找不到ID为{itemId.Value} 的资源项"); return ""; } } else { item = table.Items.FirstOrDefault(x => x.Name == itemName); if (item == null) { Debug.LogError($"[ArtResourceManager] GetSpineAnimName失败: 资源表{tableId} ({table.TableName}) 中找不到Name为'{itemName}' 的资源项"); return ""; } } return item.SpineAnimName ?? ""; } #endregion #region 工具方法 /// /// 检查资源表是否存在 /// public bool HasTable(int tableId) { return allTables.ContainsKey(tableId); } /// /// 检查资源表是否存在(按Name) /// public bool HasTable(string tableName) { return tablesByName.ContainsKey(tableName); } /// /// 获取资源表信息(不加载资源) /// public ArtTableSO GetTable(int tableId) { allTables.TryGetValue(tableId, out ArtTableSO table); return table; } /// /// 获取资源表信息(按Name,不加载资源) /// public ArtTableSO GetTable(string tableName) { tablesByName.TryGetValue(tableName, out ArtTableSO table); return table; } /// /// 清空指定表的所有资源缓存(保留SO) /// public void ClearTableCache(int tableId) { int clearedCount = 0; // 清理Sprite缓存 var spriteKeys = loadedSprites.Keys.Where(k => k.StartsWith($"{tableId}_")).ToList(); foreach (var key in spriteKeys) { loadedSprites.Remove(key); clearedCount++; } // 清理Spine缓存 var spineKeys = loadedSpines.Keys.Where(k => k.StartsWith($"{tableId}_")).ToList(); foreach (var key in spineKeys) { loadedSpines.Remove(key); clearedCount++; } Debug.Log($"[ArtResourceManager] 清空资源表 {tableId} 缓存: {clearedCount} 项资源"); } /// /// 清空所有资源缓存(保留SO) /// public void ClearAllCache() { int spriteCount = loadedSprites.Count; int spineCount = loadedSpines.Count; loadedSprites.Clear(); loadedSpines.Clear(); Debug.Log($"[ArtResourceManager] 清空所有资源缓存: {spriteCount} 个Sprite, {spineCount} 个Spine(SO保留)"); } /// /// 获取缓存统计信息 /// public string GetCacheInfo() { int totalItems = allTables.Values.Sum(table => table.Items.Count); return $"SO缓存: {allTables.Count} 个表 ({totalItems} 项资源定义)\n" + $"Sprite缓存: {loadedSprites.Count} 个\n" + $"Spine缓存: {loadedSpines.Count} 个"; } /// /// 获取所有表ID /// public List GetAllTableIds() { return new List(allTables.Keys); } /// /// 获取所有表名称 /// public List GetAllTableNames() { return new List(tablesByName.Keys); } /// /// 获取Sprite资源的完整路径 /// /// 表ID /// 资源项ID /// 资源完整路径,如果找不到返回null public string GetSpritePath(int tableId, int itemId) { if (!isInitialized) { Debug.LogError("[ArtResourceManager] 未初始化,请先调用Initialize()并等待回调完成"); return null; } if (!allTables.TryGetValue(tableId, out ArtTableSO table)) { Debug.LogError($"[ArtResourceManager] GetSpritePath失败: 找不到表ID {tableId}"); return null; } var item = table.Items.FirstOrDefault(x => x.Id == itemId); if (item == null) { Debug.LogError($"[ArtResourceManager] GetSpritePath失败: 表{tableId}中找不到ID为{itemId}的资源项"); return null; } if (string.IsNullOrEmpty(item.SpritePath)) { Debug.LogWarning($"[ArtResourceManager] 资源项 {tableId}_{itemId} 没有配置Sprite路径"); return null; } return item.SpritePath; } /// /// 获取Sprite资源的完整路径(表ID + 资源项名称) /// /// 表ID /// 资源项名称 /// 资源完整路径,如果找不到返回null public string GetSpritePath(int tableId, string itemName) { if (!isInitialized) { Debug.LogError("[ArtResourceManager] 未初始化,请先调用Initialize()并等待回调完成"); return null; } if (!allTables.TryGetValue(tableId, out ArtTableSO table)) { Debug.LogError($"[ArtResourceManager] GetSpritePath失败: 找不到表ID {tableId}"); return null; } var item = table.Items.FirstOrDefault(x => x.Name == itemName); if (item == null) { Debug.LogError($"[ArtResourceManager] GetSpritePath失败: 表{tableId}中找不到名称为'{itemName}'的资源项"); return null; } if (string.IsNullOrEmpty(item.SpritePath)) { Debug.LogWarning($"[ArtResourceManager] 资源项 {tableId}/{itemName} 没有配置Sprite路径"); return null; } return item.SpritePath; } /// /// 获取Sprite资源的完整路径(表名称 + 资源项ID) /// /// 表名称 /// 资源项ID /// 资源完整路径,如果找不到返回null public string GetSpritePath(string tableName, int itemId) { if (!isInitialized) { Debug.LogError("[ArtResourceManager] 未初始化,请先调用Initialize()并等待回调完成"); return null; } if (!tablesByName.TryGetValue(tableName, out ArtTableSO table)) { // 延迟加载模式:尝试同步加载表 if (!TrySyncLoadTable(tableName, out table)) { Debug.LogError($"[ArtResourceManager] GetSpritePath失败: 找不到表名 {tableName}"); return null; } } var item = table.Items.FirstOrDefault(x => x.Id == itemId); if (item == null) { Debug.LogError($"[ArtResourceManager] GetSpritePath失败: 表{tableName}中找不到ID为{itemId}的资源项"); return null; } if (string.IsNullOrEmpty(item.SpritePath)) { Debug.LogWarning($"[ArtResourceManager] 资源项 {tableName}/{itemId} 没有配置Sprite路径"); return null; } return item.SpritePath; } /// /// 获取Sprite资源的完整路径(按名称查询) /// /// 表名称 /// 资源项名称 /// 资源完整路径,如果找不到返回null public string GetSpritePath(string tableName, string itemName) { if (!isInitialized) { Debug.LogError("[ArtResourceManager] 未初始化,请先调用Initialize()并等待回调完成"); return null; } if (!tablesByName.TryGetValue(tableName, out ArtTableSO table)) { // 延迟加载模式:尝试同步加载表 if (!TrySyncLoadTable(tableName, out table)) { Debug.LogError($"[ArtResourceManager] GetSpritePath失败: 找不到表名 {tableName}"); return null; } } var item = table.Items.FirstOrDefault(x => x.Name == itemName); if (item == null) { Debug.LogError($"[ArtResourceManager] GetSpritePath失败: 表{tableName}中找不到名称为'{itemName}'的资源项"); return null; } if (string.IsNullOrEmpty(item.SpritePath)) { Debug.LogWarning($"[ArtResourceManager] 资源项 {tableName}/{itemName} 没有配置Sprite路径"); return null; } return item.SpritePath; } /// /// 获取SkeletonDataAsset资源的完整路径 /// /// 表ID /// 资源项ID /// 资源完整路径,如果找不到返回null public string GetSpinePath(int tableId, int itemId) { if (!isInitialized) { Debug.LogError("[ArtResourceManager] 未初始化,请先调用Initialize()并等待回调完成"); return null; } if (!allTables.TryGetValue(tableId, out ArtTableSO table)) { Debug.LogError($"[ArtResourceManager] GetSpinePath失败: 找不到表ID {tableId}"); return null; } var item = table.Items.FirstOrDefault(x => x.Id == itemId); if (item == null) { Debug.LogError($"[ArtResourceManager] GetSpinePath失败: 表{tableId}中找不到ID为{itemId}的资源项"); return null; } if (string.IsNullOrEmpty(item.SpineAssetPath)) { Debug.LogWarning($"[ArtResourceManager] 资源项 {tableId}_{itemId} 没有配置Spine路径"); return null; } return item.SpineAssetPath; } /// /// 获取SkeletonDataAsset资源的完整路径(表ID + 资源项名称) /// /// 表ID /// 资源项名称 /// 资源完整路径,如果找不到返回null public string GetSpinePath(int tableId, string itemName) { if (!isInitialized) { Debug.LogError("[ArtResourceManager] 未初始化,请先调用Initialize()并等待回调完成"); return null; } if (!allTables.TryGetValue(tableId, out ArtTableSO table)) { Debug.LogError($"[ArtResourceManager] GetSpinePath失败: 找不到表ID {tableId}"); return null; } var item = table.Items.FirstOrDefault(x => x.Name == itemName); if (item == null) { Debug.LogError($"[ArtResourceManager] GetSpinePath失败: 表{tableId}中找不到名称为'{itemName}'的资源项"); return null; } if (string.IsNullOrEmpty(item.SpineAssetPath)) { Debug.LogWarning($"[ArtResourceManager] 资源项 {tableId}/{itemName} 没有配置Spine路径"); return null; } return item.SpineAssetPath; } /// /// 获取SkeletonDataAsset资源的完整路径(表名称 + 资源项ID) /// /// 表名称 /// 资源项ID /// 资源完整路径,如果找不到返回null public string GetSpinePath(string tableName, int itemId) { if (!isInitialized) { Debug.LogError("[ArtResourceManager] 未初始化,请先调用Initialize()并等待回调完成"); return null; } if (!tablesByName.TryGetValue(tableName, out ArtTableSO table)) { Debug.LogError($"[ArtResourceManager] GetSpinePath失败: 找不到表名 {tableName}"); return null; } var item = table.Items.FirstOrDefault(x => x.Id == itemId); if (item == null) { Debug.LogError($"[ArtResourceManager] GetSpinePath失败: 表{tableName}中找不到ID为{itemId}的资源项"); return null; } if (string.IsNullOrEmpty(item.SpineAssetPath)) { Debug.LogWarning($"[ArtResourceManager] 资源项 {tableName}/{itemId} 没有配置Spine路径"); return null; } return item.SpineAssetPath; } /// /// 获取SkeletonDataAsset资源的完整路径(按名称查询) /// /// 表名称 /// 资源项名称 /// 资源完整路径,如果找不到返回null public string GetSpinePath(string tableName, string itemName) { if (!isInitialized) { Debug.LogError("[ArtResourceManager] 未初始化,请先调用Initialize()并等待回调完成"); return null; } if (!tablesByName.TryGetValue(tableName, out ArtTableSO table)) { Debug.LogError($"[ArtResourceManager] GetSpinePath失败: 找不到表名 {tableName}"); return null; } var item = table.Items.FirstOrDefault(x => x.Name == itemName); if (item == null) { Debug.LogError($"[ArtResourceManager] GetSpinePath失败: 表{tableName}中找不到名称为'{itemName}'的资源项"); return null; } if (string.IsNullOrEmpty(item.SpineAssetPath)) { Debug.LogWarning($"[ArtResourceManager] 资源项 {tableName}/{itemName} 没有配置Spine路径"); return null; } return item.SpineAssetPath; } #endregion } }