Art_SubModule/Art_Scripts/ArtResourceManager.cs
2026-02-01 15:19:34 +08:00

1648 lines
60 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
using UnityGameFramework.Runtime;
using System.Collections.Generic;
using System.Linq;
using ArtResource;
using GameFramework.Resource;
using Spine.Unity;
using System;
namespace CrazyMaple
{
/// <summary>
/// 美术资源运行时管理器单例MonoBehaviour- 优化版
/// 三级缓存SO缓存 -> Sprite缓存 -> Spine缓存
///
/// 改进点:
/// 1. 增加初始化完成回调,支持外部等待
/// 2. 正确实现AssetBundle资源加载运行时
/// 3. 优化初始化时序,避免竞态条件
/// 4. 支持预加载配置从manifest读取
/// </summary>
public class ArtResourceManager : MonoBehaviour
{
private static ArtResourceManager instance;
public static ArtResourceManager Instance
{
get
{
if (instance == null)
{
GameObject go = new GameObject("ArtResourceManager");
instance = go.AddComponent<ArtResourceManager>();
DontDestroyOnLoad(go);
}
return instance;
}
}
#region
/// <summary>
/// 常量SO文件夹路径
/// </summary>
private const string SO_ROOT_PATH = "Assets/Art_SubModule/Art_SO";
/// <summary>
/// SO文件缓存启动时一次性加载所有SO速度快~几十毫秒)
/// Key: TableId
/// </summary>
private Dictionary<int, ArtTableSO> allTables = new Dictionary<int, ArtTableSO>();
/// <summary>
/// SO名称索引支持按Name查询
/// Key: TableName
/// </summary>
private Dictionary<string, ArtTableSO> tablesByName = new Dictionary<string, ArtTableSO>();
/// <summary>
/// Sprite资源缓存重量级按需加载
/// Key: "tableId_itemId"
/// </summary>
private Dictionary<string, Sprite> loadedSprites = new Dictionary<string, Sprite>();
/// <summary>
/// Spine资源缓存重量级按需加载
/// Key: "tableId_itemId"
/// </summary>
private Dictionary<string, SkeletonDataAsset> loadedSpines = new Dictionary<string, SkeletonDataAsset>();
/// <summary>
/// 正在加载的表资源(防止重复加载)
/// Key: TableId, Value: 回调列表
/// </summary>
private Dictionary<int, List<Action<bool>>> loadingTables = new Dictionary<int, List<Action<bool>>>();
/// <summary>
/// 正在加载的Sprite防止重复加载
/// Key: "tableId_itemId", Value: 回调列表
/// </summary>
private Dictionary<string, List<Action<Sprite>>> loadingSprites = new Dictionary<string, List<Action<Sprite>>>();
/// <summary>
/// 正在加载的Spine防止重复加载
/// Key: "tableId_itemId", Value: 回调列表
/// </summary>
private Dictionary<string, List<Action<SkeletonDataAsset>>> loadingSpines = new Dictionary<string, List<Action<SkeletonDataAsset>>>();
/// <summary>
/// SO是否已初始化完成
/// </summary>
private bool isInitialized = false;
/// <summary>
/// 初始化完成时的回调列表
/// </summary>
private List<Action<bool>> initializeCallbacks = new List<Action<bool>>();
/// <summary>
/// Manifest数据包含预加载配置
/// </summary>
private ArtTableManifest manifest;
#endregion
private void Awake()
{
if (instance != null && instance != this)
{
Destroy(gameObject);
return;
}
instance = this;
DontDestroyOnLoad(gameObject);
// 不在Awake中自动初始化等待外部调用
// 这样可以让ProcedurePreload控制初始化时机
}
#region
/// <summary>
/// 初始化资源管理器
/// Editor模式延迟加载SO避免反序列化资源引用导致卡顿
/// Runtime模式加载所有SORuntime下加载SO很快不会有性能问题
/// </summary>
/// <param name="onComplete">初始化完成回调true=成功false=失败)</param>
public void Initialize(Action<bool> 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] 开始初始化 ArtResourceManagerEditor延迟加载模式...");
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<Action<bool>>(initializeCallbacks);
initializeCallbacks.Clear();
foreach (var callback in callbacks)
{
callback?.Invoke(success);
}
});
#else
// Runtime模式直接加载所有SORuntime下加载SO很快不会反序列化资源引用
Debug.Log("[ArtResourceManager] 开始初始化 ArtResourceManagerRuntime全量加载模式...");
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<Action<bool>>(initializeCallbacks);
initializeCallbacks.Clear();
foreach (var callback in callbacks)
{
callback?.Invoke(success);
}
});
#endif
}
/// <summary>
/// 只加载manifest索引文件不加载SO延迟加载优化
/// </summary>
private void LoadManifestOnly(Action<bool> 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<ArtTableManifest>(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);
}
/// <summary>
/// 尝试同步加载表用于同步API如GetSpritePath
/// </summary>
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<ArtTableSO>(soPath);
if (so != null)
{
RegisterTable(so, soPath);
table = so;
return true;
}
#else
// Runtime模式下无法同步加载返回false
Debug.LogWarning($"[ArtResourceManager] Runtime模式不支持同步加载表: {tableName}请使用异步API");
#endif
return false;
}
/// <summary>
/// 按需加载单个SO如果还没加载
/// </summary>
private void LoadTableSO(string tableName, Action<bool> 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);
});
}
/// <summary>
/// 加载所有SO文件只加载SO本身不加载引用的资源
/// 需要先加载索引文件 art_table_manifest.json获取所有SO的AssetName列表
/// </summary>
private void LoadAllTables(Action<bool> 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<ArtTableManifest>(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);
}
/// <summary>
/// 加载单个SO文件
/// </summary>
private void LoadSingleTable(string assetPath, Action<bool, float> 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);
}
/// <summary>
/// 注册SO到缓存
/// 注意路径已经在ArtItemData中存储无需额外缓存
/// </summary>
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} 项)");
}
/// <summary>
/// 自动预加载manifest中配置的表
/// </summary>
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} 失败");
}
});
}
}
/// <summary>
/// 索引文件的数据结构(包含预加载配置)
/// </summary>
[Serializable]
private class ArtTableManifest
{
public string[] tablePaths;
public int[] preloadTableIds;
}
#endregion
#region
/// <summary>
/// 异步加载资源表的所有引用资源Sprite + Spine
/// 适用场景:预加载关键表、界面预加载
/// </summary>
/// <param name="tableId">表ID</param>
/// <param name="onComplete">加载完成回调true=成功false=失败)</param>
public void LoadTable(int tableId, Action<bool> 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<Action<bool>> { 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);
}
}
}
/// <summary>
/// 按Name加载资源表
/// </summary>
public void LoadTableByName(string tableName, Action<bool> 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);
}
/// <summary>
/// 通知加载表完成
/// </summary>
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
/// <summary>
/// 加载Sprite资源表ID + 资源项ID
/// </summary>
public void LoadSprite(int tableId, int itemId, Action<Sprite> onComplete)
{
LoadSpriteInternal(tableId, itemId, null, onComplete);
}
/// <summary>
/// 加载Sprite资源表ID + 资源项Name
/// </summary>
public void LoadSprite(int tableId, string itemName, Action<Sprite> onComplete)
{
LoadSpriteInternal(tableId, null, itemName, onComplete);
}
/// <summary>
/// 加载Sprite资源表Name + 资源项ID
/// </summary>
public void LoadSprite(string tableName, int itemId, Action<Sprite> 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);
}
/// <summary>
/// 加载Sprite资源表Name + 资源项Name
/// </summary>
public void LoadSprite(string tableName, string itemName, Action<Sprite> 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);
}
/// <summary>
/// 内部加载Sprite实现
/// </summary>
private void LoadSpriteInternal(int tableId, int? itemId, string itemName, Action<Sprite> 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<Action<Sprite>> { 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);
}
});
}
/// <summary>
/// 加载Spine资源表ID + 资源项ID
/// </summary>
public void LoadSpine(int tableId, int itemId, Action<SkeletonDataAsset, string> onComplete)
{
LoadSpineInternal(tableId, itemId, null, onComplete);
}
/// <summary>
/// 加载Spine资源表ID + 资源项Name
/// </summary>
public void LoadSpine(int tableId, string itemName, Action<SkeletonDataAsset, string> onComplete)
{
LoadSpineInternal(tableId, null, itemName, onComplete);
}
/// <summary>
/// 加载Spine资源表Name + 资源项ID
/// </summary>
public void LoadSpine(string tableName, int itemId, Action<SkeletonDataAsset, string> 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);
}
/// <summary>
/// 加载Spine资源表Name + 资源项Name
/// </summary>
public void LoadSpine(string tableName, string itemName, Action<SkeletonDataAsset, string> 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);
}
/// <summary>
/// 内部加载Spine实现
/// </summary>
private void LoadSpineInternal(int tableId, int? itemId, string itemName, Action<SkeletonDataAsset, string> 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<Action<SkeletonDataAsset>> { 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);
}
});
}
/// <summary>
/// 预加载Sprite内部方法
/// </summary>
private void PreloadSprite(int tableId, int itemId, ArtItemData item, Action<bool> 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);
}
});
}
/// <summary>
/// 预加载Spine内部方法
/// </summary>
private void PreloadSpine(int tableId, int itemId, ArtItemData item, Action<bool> 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);
}
});
}
/// <summary>
/// 加载Sprite资源同时支持Editor和Runtime模式
/// Editor模式优先使用Sprite引用
/// Runtime模式使用SpritePath通过GameFramework加载
/// </summary>
private void LoadSpriteAsset(ArtItemData item, Action<Sprite> 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);
}
/// <summary>
/// 加载Spine资源同时支持Editor和Runtime模式
/// Editor模式优先使用SpineAsset引用
/// Runtime模式使用SpineAssetPath通过GameFramework加载
/// </summary>
private void LoadSpineAsset(ArtItemData item, Action<SkeletonDataAsset> 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
/// <summary>
/// 检查是否已初始化完成
/// </summary>
public bool IsInitialized => isInitialized;
/// <summary>
/// 获取Spine动画名称表ID + 资源项ID
/// </summary>
public string GetSpineAnimName(int tableId, int itemId)
{
return GetSpineAnimNameInternal(tableId, itemId, null);
}
/// <summary>
/// 获取Spine动画名称表ID + 资源项Name
/// </summary>
public string GetSpineAnimName(int tableId, string itemName)
{
return GetSpineAnimNameInternal(tableId, null, itemName);
}
/// <summary>
/// 获取Spine动画名称表Name + 资源项ID
/// </summary>
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);
}
/// <summary>
/// 获取Spine动画名称表Name + 资源项Name
/// </summary>
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);
}
/// <summary>
/// 内部获取Spine动画名称实现
/// </summary>
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
/// <summary>
/// 检查资源表是否存在
/// </summary>
public bool HasTable(int tableId)
{
return allTables.ContainsKey(tableId);
}
/// <summary>
/// 检查资源表是否存在按Name
/// </summary>
public bool HasTable(string tableName)
{
return tablesByName.ContainsKey(tableName);
}
/// <summary>
/// 获取资源表信息(不加载资源)
/// </summary>
public ArtTableSO GetTable(int tableId)
{
allTables.TryGetValue(tableId, out ArtTableSO table);
return table;
}
/// <summary>
/// 获取资源表信息按Name不加载资源
/// </summary>
public ArtTableSO GetTable(string tableName)
{
tablesByName.TryGetValue(tableName, out ArtTableSO table);
return table;
}
/// <summary>
/// 清空指定表的所有资源缓存保留SO
/// </summary>
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} 项资源");
}
/// <summary>
/// 清空所有资源缓存保留SO
/// </summary>
public void ClearAllCache()
{
int spriteCount = loadedSprites.Count;
int spineCount = loadedSpines.Count;
loadedSprites.Clear();
loadedSpines.Clear();
Debug.Log($"[ArtResourceManager] 清空所有资源缓存: {spriteCount} 个Sprite, {spineCount} 个SpineSO保留");
}
/// <summary>
/// 获取缓存统计信息
/// </summary>
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} 个";
}
/// <summary>
/// 获取所有表ID
/// </summary>
public List<int> GetAllTableIds()
{
return new List<int>(allTables.Keys);
}
/// <summary>
/// 获取所有表名称
/// </summary>
public List<string> GetAllTableNames()
{
return new List<string>(tablesByName.Keys);
}
/// <summary>
/// 获取Sprite资源的完整路径
/// </summary>
/// <param name="tableId">表ID</param>
/// <param name="itemId">资源项ID</param>
/// <returns>资源完整路径如果找不到返回null</returns>
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;
}
/// <summary>
/// 获取Sprite资源的完整路径表ID + 资源项名称)
/// </summary>
/// <param name="tableId">表ID</param>
/// <param name="itemName">资源项名称</param>
/// <returns>资源完整路径如果找不到返回null</returns>
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;
}
/// <summary>
/// 获取Sprite资源的完整路径表名称 + 资源项ID
/// </summary>
/// <param name="tableName">表名称</param>
/// <param name="itemId">资源项ID</param>
/// <returns>资源完整路径如果找不到返回null</returns>
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;
}
/// <summary>
/// 获取Sprite资源的完整路径按名称查询
/// </summary>
/// <param name="tableName">表名称</param>
/// <param name="itemName">资源项名称</param>
/// <returns>资源完整路径如果找不到返回null</returns>
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;
}
/// <summary>
/// 获取SkeletonDataAsset资源的完整路径
/// </summary>
/// <param name="tableId">表ID</param>
/// <param name="itemId">资源项ID</param>
/// <returns>资源完整路径如果找不到返回null</returns>
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;
}
/// <summary>
/// 获取SkeletonDataAsset资源的完整路径表ID + 资源项名称)
/// </summary>
/// <param name="tableId">表ID</param>
/// <param name="itemName">资源项名称</param>
/// <returns>资源完整路径如果找不到返回null</returns>
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;
}
/// <summary>
/// 获取SkeletonDataAsset资源的完整路径表名称 + 资源项ID
/// </summary>
/// <param name="tableName">表名称</param>
/// <param name="itemId">资源项ID</param>
/// <returns>资源完整路径如果找不到返回null</returns>
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;
}
/// <summary>
/// 获取SkeletonDataAsset资源的完整路径按名称查询
/// </summary>
/// <param name="tableName">表名称</param>
/// <param name="itemName">资源项名称</param>
/// <returns>资源完整路径如果找不到返回null</returns>
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
}
}