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
}
}