yyl
2025-12-03 b1b9a387a63e1ce77cf387b90ec170c7a05bb53b
125 战斗 替换加载蓝队逻辑 统一替换引用计数
4个文件已修改
4个文件已删除
1108 ■■■■ 已修改文件
Main/System/Battle/BattleResources/BattleAudioResLoader.cs 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleResources/BattleAudioResLoader.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleResources/BattleCacheManager.cs 590 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleResources/BattlePreloadManager.cs 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleResources/BattleResManager.cs 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleResources/BattleSpineResLoader.cs 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleResources/BattleSpineResLoader.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleResources/BattleUnloadManager.cs 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleResources/BattleAudioResLoader.cs
File was deleted
Main/System/Battle/BattleResources/BattleAudioResLoader.cs.meta
File was deleted
Main/System/Battle/BattleResources/BattleCacheManager.cs
@@ -5,423 +5,115 @@
public class BattleCacheManager
{
    /// <summary>
    /// 资源引用信息
    /// 资源引用信息(战场 + 角色维度)
    /// </summary>
    private class ResourceReference
    {
        public BattleResCache.CachedResource CachedResource;
        public HashSet<string> OwnerIds = new HashSet<string>(); // 使用该资源的角色ID集合
        // 记录哪些战场的哪些角色在使用这个资源
        // Key: battleGuid, Value: ownerIds
        public Dictionary<string, HashSet<string>> BattlefieldOwners = new Dictionary<string, HashSet<string>>();
        
        public int RefCount => OwnerIds.Count;
        public void AddOwner(string ownerId)
        public int RefCount
        {
            OwnerIds.Add(ownerId);
            get
            {
                int count = 0;
                foreach (var owners in BattlefieldOwners.Values)
                {
                    count += owners.Count;
                }
                return count;
            }
        }
        
        public void RemoveOwner(string ownerId)
        /// <summary>
        /// 添加战场+角色的引用
        /// </summary>
        public void AddBattlefieldOwner(string battleGuid, string ownerId)
        {
            OwnerIds.Remove(ownerId);
            if (!BattlefieldOwners.ContainsKey(battleGuid))
            {
                BattlefieldOwners[battleGuid] = new HashSet<string>();
            }
            BattlefieldOwners[battleGuid].Add(ownerId);
        }
        /// <summary>
        /// 移除整个战场的所有引用
        /// </summary>
        public void RemoveBattlefield(string battleGuid)
        {
            BattlefieldOwners.Remove(battleGuid);
        }
    }
    
    // ===== 红队资源:全局共享,按引用计数管理 =====
    private static Dictionary<string, ResourceReference> globalRedTeamSpineCache =
    // ===== 全局统一资源池(不再区分红蓝队)=====
    private static Dictionary<string, ResourceReference> globalSpineCache =
        new Dictionary<string, ResourceReference>();
    
    private static Dictionary<string, ResourceReference> globalRedTeamAudioCache =
    private static Dictionary<string, ResourceReference> globalAudioCache =
        new Dictionary<string, ResourceReference>();
    
    // ===== 蓝队资源:按战场GUID隔离 =====
    private static Dictionary<string, Dictionary<string, BattleResCache.CachedResource>> blueTeamSpineCacheDict =
        new Dictionary<string, Dictionary<string, BattleResCache.CachedResource>>();
    private static Dictionary<string, Dictionary<string, BattleResCache.CachedResource>> blueTeamAudioCacheDict =
        new Dictionary<string, Dictionary<string, BattleResCache.CachedResource>>();
    // 需要添加的字段
    private static Dictionary<string, HashSet<string>> battlefieldRedTeamOwners =
        new Dictionary<string, HashSet<string>>();  // <battleGuid, ownerIds>
    /// <summary>
    /// 获取Spine缓存(红队全局,蓝队按战场隔离)
    /// 注册战场资源需求(红队+蓝队一起注册)
    /// </summary>
    public Dictionary<string, BattleResCache.CachedResource> GetSpineCache(bool isPersistent, string battleGuid = "")
    public void RegisterBattlefieldResources(string battleGuid,
        List<BattleResCache.ResourceIdentifier> spineResources,
        List<BattleResCache.ResourceIdentifier> audioResources)
    {
        if (isPersistent)
        {
            // 红队:将引用字典转换为普通缓存字典(兼容加载器)
            var cache = new Dictionary<string, BattleResCache.CachedResource>();
            foreach (var kvp in globalRedTeamSpineCache)
            {
                cache[kvp.Key] = kvp.Value.CachedResource;
            }
            return cache;
        }
        else
        {
            // 蓝队:返回战场专属缓存
            if (!blueTeamSpineCacheDict.ContainsKey(battleGuid))
            {
                blueTeamSpineCacheDict[battleGuid] = new Dictionary<string, BattleResCache.CachedResource>();
            }
            return blueTeamSpineCacheDict[battleGuid];
        }
    }
    /// <summary>
    /// 获取音频缓存(红队全局,蓝队按战场隔离)
    /// </summary>
    public Dictionary<string, BattleResCache.CachedResource> GetAudioCache(bool isPersistent, string battleGuid = "")
    {
        if (isPersistent)
        {
            // 红队:将引用字典转换为普通缓存字典
            var cache = new Dictionary<string, BattleResCache.CachedResource>();
            foreach (var kvp in globalRedTeamAudioCache)
            {
                cache[kvp.Key] = kvp.Value.CachedResource;
            }
            return cache;
        }
        else
        {
            // 蓝队:返回战场专属缓存
            if (!blueTeamAudioCacheDict.ContainsKey(battleGuid))
            {
                blueTeamAudioCacheDict[battleGuid] = new Dictionary<string, BattleResCache.CachedResource>();
            }
            return blueTeamAudioCacheDict[battleGuid];
        }
    }
    /// <summary>
    /// 添加红队资源引用(由加载器调用)
    /// </summary>
    public void AddRedTeamSpineReference(string key, BattleResCache.CachedResource resource, string ownerId)
    {
        if (!globalRedTeamSpineCache.ContainsKey(key))
        {
            globalRedTeamSpineCache[key] = new ResourceReference
            {
                CachedResource = resource
            };
        }
        globalRedTeamSpineCache[key].AddOwner(ownerId);
    }
    /// <summary>
    /// 添加红队音频引用
    /// </summary>
    public void AddRedTeamAudioReference(string key, BattleResCache.CachedResource resource, string ownerId)
    {
        if (!globalRedTeamAudioCache.ContainsKey(key))
        {
            globalRedTeamAudioCache[key] = new ResourceReference
            {
                CachedResource = resource
            };
        }
        globalRedTeamAudioCache[key].AddOwner(ownerId);
    }
    /// <summary>
    /// 获取Spine资源(未命中时自动加载并缓存)
    /// </summary>
    public SkeletonDataAsset GetSpineResource(string directory, string assetName, string battleGuid = "", bool autoLoadIfMissing = true)
    {
        string key = $"{directory}/{assetName}";
        // 优先从红队全局缓存查找
        if (globalRedTeamSpineCache.TryGetValue(key, out var redRef))
        {
            return redRef.CachedResource.Asset as SkeletonDataAsset;
        }
        // 再从蓝队战场专属缓存查找
        if (!string.IsNullOrEmpty(battleGuid) && blueTeamSpineCacheDict.TryGetValue(battleGuid, out var blueCache))
        {
            if (blueCache.TryGetValue(key, out var blueRes))
            {
                return blueRes.Asset as SkeletonDataAsset;
            }
        }
        // ===== 缓存未命中时自动加载 =====
        if (autoLoadIfMissing)
        {
            Debug.LogWarning($"BattleCacheManager: Spine cache miss for {key}, loading on-demand...");
            SkeletonDataAsset asset = ResManager.Instance.LoadAsset<SkeletonDataAsset>(directory, assetName);
            if (asset != null)
            {
                var identifier = new BattleResCache.ResourceIdentifier
                {
                    Directory = directory,
                    AssetName = assetName,
                    Type = BattleResCache.ResourceType.Spine,
                    IsPersistent = string.IsNullOrEmpty(battleGuid)
                };
                var cachedRes = new BattleResCache.CachedResource(identifier, asset, identifier.IsPersistent);
                if (string.IsNullOrEmpty(battleGuid))
                {
                    // 红队:添加引用(未知所有者,用特殊标识)
                    AddRedTeamSpineReference(key, cachedRes, "OnDemand");
                    Debug.Log($"BattleCacheManager: Added to global red cache: {key}");
                }
                else
                {
                    // 蓝队:直接加入战场缓存
                    if (!blueTeamSpineCacheDict.ContainsKey(battleGuid))
                    {
                        blueTeamSpineCacheDict[battleGuid] = new Dictionary<string, BattleResCache.CachedResource>();
                    }
                    blueTeamSpineCacheDict[battleGuid][key] = cachedRes;
                    Debug.Log($"BattleCacheManager: Added to blue cache (BF={battleGuid}): {key}");
                }
                return asset;
            }
        }
        return null;
    }
    /// <summary>
    /// 获取音频资源(未命中时自动加载并缓存)
    /// </summary>
    public AudioClip GetAudioResource(string directory, string assetName, string battleGuid = "", bool autoLoadIfMissing = true)
    {
        string key = $"{directory}/{assetName}";
        // 优先从红队全局缓存查找
        if (globalRedTeamAudioCache.TryGetValue(key, out var redRef))
        {
            return redRef.CachedResource.Asset as AudioClip;
        }
        // 再从蓝队战场专属缓存查找
        if (!string.IsNullOrEmpty(battleGuid) && blueTeamAudioCacheDict.TryGetValue(battleGuid, out var blueCache))
        {
            if (blueCache.TryGetValue(key, out var blueRes))
            {
                return blueRes.Asset as AudioClip;
            }
        }
        // ===== 缓存未命中时自动加载 =====
        if (autoLoadIfMissing)
        {
            Debug.LogWarning($"BattleCacheManager: Audio cache miss for {key}, loading on-demand...");
            AudioClip asset = ResManager.Instance.LoadAsset<AudioClip>(directory, assetName, false);
            if (asset != null)
            {
                var identifier = new BattleResCache.ResourceIdentifier
                {
                    Directory = directory,
                    AssetName = assetName,
                    Type = BattleResCache.ResourceType.Audio,
                    IsPersistent = string.IsNullOrEmpty(battleGuid)
                };
                var cachedRes = new BattleResCache.CachedResource(identifier, asset, identifier.IsPersistent);
                if (string.IsNullOrEmpty(battleGuid))
                {
                    // 红队:添加引用
                    AddRedTeamAudioReference(key, cachedRes, "OnDemand");
                    Debug.Log($"BattleCacheManager: Added to global red audio cache: {key}");
                }
                else
                {
                    // 蓝队:直接加入战场缓存
                    if (!blueTeamAudioCacheDict.ContainsKey(battleGuid))
                    {
                        blueTeamAudioCacheDict[battleGuid] = new Dictionary<string, BattleResCache.CachedResource>();
                    }
                    blueTeamAudioCacheDict[battleGuid][key] = cachedRes;
                    Debug.Log($"BattleCacheManager: Added to blue audio cache (BF={battleGuid}): {key}");
                }
                return asset;
            }
        }
        return null;
    }
    /// <summary>
    /// 移除指定角色的红队资源引用
    /// </summary>
    public void RemoveRedTeamReferences(List<string> ownerIds)
    {
        if (ownerIds == null || ownerIds.Count == 0)
            return;
        int removedSpineCount = 0;
        int removedAudioCount = 0;
        // 处理Spine资源
        var spineKeysToRemove = new List<string>();
        foreach (var kvp in globalRedTeamSpineCache)
        {
            foreach (var ownerId in ownerIds)
            {
                kvp.Value.RemoveOwner(ownerId);
            }
            // 如果没有引用了,标记删除
            if (kvp.Value.RefCount == 0)
            {
                spineKeysToRemove.Add(kvp.Key);
            }
        }
        foreach (var key in spineKeysToRemove)
        {
            var resource = globalRedTeamSpineCache[key].CachedResource;
            ResManager.Instance.UnloadAsset(
                resource.Identifier.Directory.ToLower(),
                resource.Identifier.AssetName.ToLower()
            );
            globalRedTeamSpineCache.Remove(key);
            removedSpineCount++;
        }
        // 处理音频资源
        var audioKeysToRemove = new List<string>();
        foreach (var kvp in globalRedTeamAudioCache)
        {
            foreach (var ownerId in ownerIds)
            {
                kvp.Value.RemoveOwner(ownerId);
            }
            if (kvp.Value.RefCount == 0)
            {
                audioKeysToRemove.Add(kvp.Key);
            }
        }
        foreach (var key in audioKeysToRemove)
        {
            var resource = globalRedTeamAudioCache[key].CachedResource;
            ResManager.Instance.UnloadAsset(
                resource.Identifier.Directory.ToLower(),
                resource.Identifier.AssetName.ToLower()
            );
            globalRedTeamAudioCache.Remove(key);
            removedAudioCount++;
        }
        Debug.Log($"BattleCacheManager: Removed {ownerIds.Count} owner(s), freed {removedSpineCount} spine + {removedAudioCount} audio resources");
    }
    /// <summary>
    /// 清空指定战场的蓝队缓存
    /// </summary>
    public void ClearBlueTeamCache(string battleGuid)
    {
        if (blueTeamSpineCacheDict.ContainsKey(battleGuid))
        {
            blueTeamSpineCacheDict.Remove(battleGuid);
        }
        if (blueTeamAudioCacheDict.ContainsKey(battleGuid))
        {
            blueTeamAudioCacheDict.Remove(battleGuid);
        }
        Debug.Log($"BattleCacheManager: Cleared blue team cache for battlefield {battleGuid}");
    }
    /// <summary>
    /// 清空红队全局缓存(玩家重置阵容时调用)
    /// </summary>
    public void ClearRedTeamCache()
    {
        globalRedTeamSpineCache.Clear();
        globalRedTeamAudioCache.Clear();
        Debug.Log("BattleCacheManager: Cleared red team global cache");
    }
    /// <summary>
    /// 获取缓存统计信息
    /// </summary>
    public string GetCacheStats(string battleGuid = "")
    {
        int blueSpineCount = blueTeamSpineCacheDict.ContainsKey(battleGuid) ? blueTeamSpineCacheDict[battleGuid].Count : 0;
        int blueAudioCount = blueTeamAudioCacheDict.ContainsKey(battleGuid) ? blueTeamAudioCacheDict[battleGuid].Count : 0;
        return $"Red Spine: {globalRedTeamSpineCache.Count}, Red Audio: {globalRedTeamAudioCache.Count}, " +
               $"Blue Spine (BF={battleGuid}): {blueSpineCount}, Blue Audio (BF={battleGuid}): {blueAudioCount}";
    }
    // ========== BattleCacheManager.cs 新增方法 ==========
    /// <summary>
    /// 记录战场的红队资源需求(增加引用)
    /// </summary>
    public void RegisterBattlefieldRedTeam(string battleGuid, List<BattleResCache.ResourceIdentifier> spineResources, List<BattleResCache.ResourceIdentifier> audioResources)
    {
        // 记录这个战场使用的红队资源的OwnerIds
        var ownerIds = new HashSet<string>();
        // 注册Spine资源
        foreach (var res in spineResources)
        {
            if (!string.IsNullOrEmpty(res.OwnerId))
            if (string.IsNullOrEmpty(res.OwnerId))
                continue;
            string key = res.GetKey();
            if (!globalSpineCache.ContainsKey(key))
            {
                ownerIds.Add(res.OwnerId);
                globalSpineCache[key] = new ResourceReference
                {
                    CachedResource = new BattleResCache.CachedResource(res, null, false)
                };
            }
            globalSpineCache[key].AddBattlefieldOwner(battleGuid, res.OwnerId);
        }
        
        // 注册Audio资源
        foreach (var res in audioResources)
        {
            if (!string.IsNullOrEmpty(res.OwnerId))
            {
                ownerIds.Add(res.OwnerId);
            }
        }
        if (!battlefieldRedTeamOwners.ContainsKey(battleGuid))
        {
            battlefieldRedTeamOwners[battleGuid] = ownerIds;
        }
    }
    /// <summary>
    /// 注销战场的红队资源需求(减少引用)
    /// </summary>
    public void UnregisterBattlefieldRedTeam(string battleGuid)
    {
        if (!battlefieldRedTeamOwners.ContainsKey(battleGuid))
            return;
            if (string.IsNullOrEmpty(res.OwnerId))
                continue;
            string key = res.GetKey();
            
        var ownerIds = battlefieldRedTeamOwners[battleGuid];
            if (!globalAudioCache.ContainsKey(key))
            {
                globalAudioCache[key] = new ResourceReference
                {
                    CachedResource = new BattleResCache.CachedResource(res, null, false)
                };
            }
            globalAudioCache[key].AddBattlefieldOwner(battleGuid, res.OwnerId);
        }
        
        // 从所有红队资源中移除这些OwnerIds的引用
        RemoveOwnersFromRedTeamCache(ownerIds);
        battlefieldRedTeamOwners.Remove(battleGuid);
        Debug.Log($"BattleCacheManager: Registered battlefield {battleGuid} - Spine: {spineResources.Count}, Audio: {audioResources.Count}");
    }
    private void RemoveOwnersFromRedTeamCache(HashSet<string> ownerIds)
    /// <summary>
    /// 注销战场(卸载该战场的所有资源引用)
    /// </summary>
    public void UnregisterBattlefield(string battleGuid)
    {
        // 处理Spine资源
        var spineKeysToRemove = new List<string>();
        foreach (var kvp in globalRedTeamSpineCache)
        foreach (var kvp in globalSpineCache)
        {
            foreach (var ownerId in ownerIds)
            {
                kvp.Value.RemoveOwner(ownerId);
            }
            kvp.Value.RemoveBattlefield(battleGuid);
            
            // 引用计数为0时真正卸载
            if (kvp.Value.RefCount == 0)
            {
                spineKeysToRemove.Add(kvp.Key);
@@ -430,20 +122,23 @@
        
        foreach (var key in spineKeysToRemove)
        {
            var res = globalRedTeamSpineCache[key];
            ResManager.Instance.UnloadAsset(res.CachedResource.Identifier.Directory, res.CachedResource.Identifier.AssetName);
            globalRedTeamSpineCache.Remove(key);
            Debug.Log($"BattleCacheManager: Unloaded red team spine (refCount=0): {key}");
            var res = globalSpineCache[key];
            if (res.CachedResource.Asset != null)
            {
                ResManager.Instance.UnloadAsset(
                    res.CachedResource.Identifier.Directory,
                    res.CachedResource.Identifier.AssetName
                );
            }
            globalSpineCache.Remove(key);
            Debug.Log($"BattleCacheManager: Unloaded spine (refCount=0): {key}");
        }
        
        // 处理Audio资源
        var audioKeysToRemove = new List<string>();
        foreach (var kvp in globalRedTeamAudioCache)
        foreach (var kvp in globalAudioCache)
        {
            foreach (var ownerId in ownerIds)
            {
                kvp.Value.RemoveOwner(ownerId);
            }
            kvp.Value.RemoveBattlefield(battleGuid);
            
            if (kvp.Value.RefCount == 0)
            {
@@ -453,10 +148,115 @@
        
        foreach (var key in audioKeysToRemove)
        {
            var res = globalRedTeamAudioCache[key];
            ResManager.Instance.UnloadAsset(res.CachedResource.Identifier.Directory, res.CachedResource.Identifier.AssetName);
            globalRedTeamAudioCache.Remove(key);
            Debug.Log($"BattleCacheManager: Unloaded red team audio (refCount=0): {key}");
            var res = globalAudioCache[key];
            if (res.CachedResource.Asset != null)
            {
                ResManager.Instance.UnloadAsset(
                    res.CachedResource.Identifier.Directory,
                    res.CachedResource.Identifier.AssetName
                );
            }
            globalAudioCache.Remove(key);
            Debug.Log($"BattleCacheManager: Unloaded audio (refCount=0): {key}");
        }
        Debug.Log($"BattleCacheManager: Unregistered battlefield {battleGuid}");
    }
    /// <summary>
    /// 获取或加载Spine资源
    /// </summary>
    public SkeletonDataAsset GetSpineResource(string directory, string assetName, string battleGuid = "")
    {
        string key = $"{directory}/{assetName}";
        if (globalSpineCache.TryGetValue(key, out var refInfo))
        {
            return refInfo.CachedResource.Asset as SkeletonDataAsset;
        }
        // 自动加载
        Debug.LogWarning($"BattleCacheManager: Spine cache miss for {key}, loading on-demand...");
        var asset = ResManager.Instance.LoadAsset<SkeletonDataAsset>(directory, assetName);
        if (asset != null)
        {
            var identifier = new BattleResCache.ResourceIdentifier
            {
                Directory = directory,
                AssetName = assetName,
                Type = BattleResCache.ResourceType.Spine
            };
            globalSpineCache[key] = new ResourceReference
            {
                CachedResource = new BattleResCache.CachedResource(identifier, asset, false)
            };
        }
        return asset;
    }
    /// <summary>
    /// 获取或加载Audio资源
    /// </summary>
    public AudioClip GetAudioResource(string directory, string assetName, string battleGuid = "")
    {
        string key = $"{directory}/{assetName}";
        if (globalAudioCache.TryGetValue(key, out var refInfo))
        {
            return refInfo.CachedResource.Asset as AudioClip;
        }
        // 自动加载
        Debug.LogWarning($"BattleCacheManager: Audio cache miss for {key}, loading on-demand...");
        var asset = ResManager.Instance.LoadAsset<AudioClip>(directory, assetName, false);
        if (asset != null)
        {
            var identifier = new BattleResCache.ResourceIdentifier
            {
                Directory = directory,
                AssetName = assetName,
                Type = BattleResCache.ResourceType.Audio
            };
            globalAudioCache[key] = new ResourceReference
            {
                CachedResource = new BattleResCache.CachedResource(identifier, asset, false)
            };
        }
        return asset;
    }
    /// <summary>
    /// 更新已加载资源的引用(由加载器调用)
    /// </summary>
    public void UpdateResourceReference(string key, BattleResCache.CachedResource resource, string battleGuid, string ownerId)
    {
        if (resource.Identifier.Type == BattleResCache.ResourceType.Spine)
        {
            if (globalSpineCache.ContainsKey(key))
            {
                globalSpineCache[key].CachedResource = resource;
            }
        }
        else if (resource.Identifier.Type == BattleResCache.ResourceType.Audio)
        {
            if (globalAudioCache.ContainsKey(key))
            {
                globalAudioCache[key].CachedResource = resource;
            }
        }
    }
    public string GetCacheStats(string battleGuid = "")
    {
        int spineTotal = globalSpineCache.Count;
        int audioTotal = globalAudioCache.Count;
        return $"Spine: {spineTotal}, Audio: {audioTotal}";
    }
}
Main/System/Battle/BattleResources/BattlePreloadManager.cs
@@ -1,11 +1,10 @@
using UnityEngine;
using System;
using System.Collections.Generic;
using Spine.Unity;
public class BattlePreloadManager
{
    private BattleSpineResLoader spineLoader = new BattleSpineResLoader();
    private BattleAudioResLoader audioLoader = new BattleAudioResLoader();
    private BattleCacheManager cacheManager = new BattleCacheManager();
    private BattleUnloadManager unloadManager = new BattleUnloadManager();
    
@@ -31,10 +30,18 @@
        var redTeamInfo = AnalyzeTeamList(redTeamList, true);
        var blueTeamInfo = AnalyzeTeamList(blueTeamList, false);
        
        // ===== 新增:注册战场红队资源需求 =====
        cacheManager.RegisterBattlefieldRedTeam(battleGuid, redTeamInfo.SpineResources, redTeamInfo.AudioResources);
        // ===== 合并红蓝队资源,统一注册 =====
        var allSpineResources = new List<BattleResCache.ResourceIdentifier>();
        var allAudioResources = new List<BattleResCache.ResourceIdentifier>();
        
        StartPreload(redTeamInfo, blueTeamInfo, battleGuid, progressCallback, () =>
        allSpineResources.AddRange(redTeamInfo.SpineResources);
        allSpineResources.AddRange(blueTeamInfo.SpineResources);
        allAudioResources.AddRange(redTeamInfo.AudioResources);
        allAudioResources.AddRange(blueTeamInfo.AudioResources);
        cacheManager.RegisterBattlefieldResources(battleGuid, allSpineResources, allAudioResources);
        StartPreload(allSpineResources, allAudioResources, battleGuid, progressCallback, () =>
        {
            isLoading = false;
            completeCallback?.Invoke();
@@ -95,11 +102,12 @@
        return false;
    }
    
    private void StartPreload(TeamResTracker.TeamResourceInfo redInfo, TeamResTracker.TeamResourceInfo blueInfo,
    private void StartPreload(List<BattleResCache.ResourceIdentifier> spineResources,
        List<BattleResCache.ResourceIdentifier> audioResources,
        string battleGuid,
        Action<float> progressCallback, Action completeCallback)
    {
        int totalResources = redInfo.GetTotalCount() + blueInfo.GetTotalCount();
        int totalResources = spineResources.Count + audioResources.Count;
        
        if (totalResources == 0)
        {
@@ -109,60 +117,90 @@
        }
        
        Debug.Log($"BattlePreloadManager: Preloading {totalResources} resources for battlefield {battleGuid}");
        Debug.Log($"  Red: Spine={redInfo.SpineResources.Count}, Audio={redInfo.AudioResources.Count}");
        Debug.Log($"  Blue: Spine={blueInfo.SpineResources.Count}, Audio={blueInfo.AudioResources.Count}");
        Debug.Log($"  Spine={spineResources.Count}, Audio={audioResources.Count}");
        
        int completedPhases = 0;
        int totalPhases = 4;
        int loadedCount = 0;
        
        Action onPhaseComplete = () =>
        Action onSingleComplete = () =>
        {
            completedPhases++;
            float progress = (float)completedPhases / totalPhases;
            loadedCount++;
            float progress = (float)loadedCount / totalResources;
            progressCallback?.Invoke(progress);
            
            if (completedPhases >= totalPhases)
            if (loadedCount >= totalResources)
            {
                Debug.Log($"BattlePreloadManager: Completed! {cacheManager.GetCacheStats(battleGuid)}");
                completeCallback?.Invoke();
            }
        };
        
        // 并行加载4个阶段(传入cacheManager和是否为红队标识)
        spineLoader.LoadSpineResourcesAsync(
            redInfo.SpineResources,
            cacheManager.GetSpineCache(true, battleGuid),
            null,
            onPhaseComplete,
            cacheManager,  // ← 传入管理器
            true           // ← 是红队
        );
        // 异步加载所有Spine资源
        foreach (var identifier in spineResources)
        {
            LoadSpineAsync(identifier, battleGuid, onSingleComplete);
        }
        
        audioLoader.LoadAudioResourcesAsync(
            redInfo.AudioResources,
            cacheManager.GetAudioCache(true, battleGuid),
            null,
            onPhaseComplete,
            cacheManager,  // ← 传入管理器
            true           // ← 是红队
        );
        // 异步加载所有Audio资源
        foreach (var identifier in audioResources)
        {
            LoadAudioAsync(identifier, battleGuid, onSingleComplete);
        }
    }
    private void LoadSpineAsync(BattleResCache.ResourceIdentifier identifier, string battleGuid, Action onComplete)
    {
        string key = identifier.GetKey();
        
        spineLoader.LoadSpineResourcesAsync(
            blueInfo.SpineResources,
            cacheManager.GetSpineCache(false, battleGuid),
            null,
            onPhaseComplete,
            null,   // ← 蓝队不需要引用追踪
            false   // ← 不是红队
        ResManager.Instance.LoadAssetAsync<SkeletonDataAsset>(
            identifier.Directory,
            identifier.AssetName,
            (success, asset) =>
            {
                if (success && asset != null)
                {
                    var skeletonData = asset as SkeletonDataAsset;
                    if (skeletonData != null)
                    {
                        var cachedRes = new BattleResCache.CachedResource(identifier, skeletonData, false);
                        cacheManager.UpdateResourceReference(key, cachedRes, battleGuid, identifier.OwnerId);
                        Debug.Log($"BattlePreloadManager: Loaded spine: {key}");
                    }
                }
                else
                {
                    Debug.LogError($"BattlePreloadManager: Failed to load spine: {key}");
                }
                onComplete?.Invoke();
            }
        );
    }
    private void LoadAudioAsync(BattleResCache.ResourceIdentifier identifier, string battleGuid, Action onComplete)
    {
        string key = identifier.GetKey();
        
        audioLoader.LoadAudioResourcesAsync(
            blueInfo.AudioResources,
            cacheManager.GetAudioCache(false, battleGuid),
            null,
            onPhaseComplete,
            null,   // ← 蓝队不需要引用追踪
            false   // ← 不是红队
        ResManager.Instance.LoadAssetAsync<AudioClip>(
            identifier.Directory,
            identifier.AssetName,
            (success, asset) =>
            {
                if (success && asset != null)
                {
                    var audioClip = asset as AudioClip;
                    if (audioClip != null)
                    {
                        var cachedRes = new BattleResCache.CachedResource(identifier, audioClip, false);
                        cacheManager.UpdateResourceReference(key, cachedRes, battleGuid, identifier.OwnerId);
                        Debug.Log($"BattlePreloadManager: Loaded audio: {key}");
                    }
                }
                else
                {
                    Debug.LogError($"BattlePreloadManager: Failed to load audio: {key}");
                }
                onComplete?.Invoke();
            },
            false  // needExt = false
        );
    }
}
Main/System/Battle/BattleResources/BattleResManager.cs
@@ -21,34 +21,26 @@
    }
    
    /// <summary>
    /// 战斗结束后卸载蓝队资源
    /// 战斗结束后卸载资源
    /// </summary>
    public void UnloadBattleResources(string battleGuid)
    {
        preloadManager.UnloadManager.UnloadBlueTeamResources(preloadManager.CacheManager, battleGuid);
        preloadManager.UnloadManager.UnloadBattleResources(preloadManager.CacheManager, battleGuid);
    }
    
    /// <summary>
    /// 获取Spine资源
    /// </summary>
    public SkeletonDataAsset GetSpineResource(string directory, string assetName, string battleGuid = "", bool autoLoadIfMissing = true)
    public SkeletonDataAsset GetSpineResource(string directory, string assetName, string battleGuid = "")
    {
        return preloadManager.CacheManager.GetSpineResource(directory, assetName, battleGuid, autoLoadIfMissing);
        return preloadManager.CacheManager.GetSpineResource(directory, assetName, battleGuid);
    }
    
    /// <summary>
    /// 获取音频资源
    /// </summary>
    public AudioClip GetAudioResource(string directory, string assetName, string battleGuid = "", bool autoLoadIfMissing = true)
    public AudioClip GetAudioResource(string directory, string assetName, string battleGuid = "")
    {
        return preloadManager.CacheManager.GetAudioResource(directory, assetName, battleGuid, autoLoadIfMissing);
    }
    /// <summary>
    /// 处理红队变更
    /// </summary>
    public void HandleRedTeamChange(List<TeamBase> newRedTeamList, Action completeCallback = null)
    {
        preloadManager.HandleRedTeamChange(newRedTeamList, completeCallback);
        return preloadManager.CacheManager.GetAudioResource(directory, assetName, battleGuid);
    }
}
Main/System/Battle/BattleResources/BattleSpineResLoader.cs
File was deleted
Main/System/Battle/BattleResources/BattleSpineResLoader.cs.meta
File was deleted
Main/System/Battle/BattleResources/BattleUnloadManager.cs
@@ -1,103 +1,13 @@
using UnityEngine;
using System.Collections.Generic;
public class BattleUnloadManager
{
    /// <summary>
    /// 卸载指定战场的蓝队资源
    /// </summary>
    public void UnloadBlueTeamResources(BattleCacheManager cacheManager, string battleGuid)
    {
        var blueSpineCache = cacheManager.GetSpineCache(false, battleGuid);
        var blueAudioCache = cacheManager.GetAudioCache(false, battleGuid);
        int spineCount = UnloadResourceCache(blueSpineCache);
        int audioCount = UnloadResourceCache(blueAudioCache);
        cacheManager.ClearBlueTeamCache(battleGuid);
        Debug.Log($"BattleUnloadManager: Unloaded blue team for battlefield {battleGuid} - Spine: {spineCount}, Audio: {audioCount}");
    }
    /// <summary>
    /// 卸载红队资源(队伍变更时使用)
    /// </summary>
    public void UnloadRedTeamResources(BattleCacheManager cacheManager)
    {
        var redSpineCache = cacheManager.GetSpineCache(true, "");
        var redAudioCache = cacheManager.GetAudioCache(true, "");
        int spineCount = UnloadResourceCache(redSpineCache);
        int audioCount = UnloadResourceCache(redAudioCache);
        cacheManager.ClearRedTeamCache();
        Debug.Log($"BattleUnloadManager: Unloaded red team - Spine: {spineCount}, Audio: {audioCount}");
    }
    /// <summary>
    /// 卸载整个资源缓存
    /// </summary>
    private int UnloadResourceCache(Dictionary<string, BattleResCache.CachedResource> cache)
    {
        int unloadCount = 0;
        foreach (var kvp in cache)
        {
            if (kvp.Value.CanUnload())
            {
                UnloadSingleResource(kvp.Value);
                unloadCount++;
            }
        }
        return unloadCount;
    }
    /// <summary>
    /// 卸载单个资源
    /// </summary>
    private void UnloadSingleResource(BattleResCache.CachedResource cachedRes)
    {
        if (cachedRes == null || cachedRes.Asset == null)
        {
            return;
        }
        string assetKey = cachedRes.Identifier.GetKey();
        // 卸载资源
        ResManager.Instance.UnloadAsset(
            cachedRes.Identifier.Directory.ToLower(),
            cachedRes.Identifier.AssetName.ToLower()
        );
        Debug.Log($"BattleUnloadManager: Unloaded resource: {assetKey}");
    }
    /// <summary>
    /// 卸载所有资源
    /// </summary>
    public void UnloadAllResources(BattleCacheManager cacheManager, string battleGuid = "")
    {
        UnloadRedTeamResources(cacheManager);
        if (!string.IsNullOrEmpty(battleGuid))
        {
            UnloadBlueTeamResources(cacheManager, battleGuid);
        }
        Debug.Log("BattleUnloadManager: All resources unloaded");
    }
    /// <summary>
    /// 卸载战场资源
    /// 卸载战场资源(统一处理红蓝队)
    /// </summary>
    public void UnloadBattleResources(BattleCacheManager cacheManager, string battleGuid)
    {
        // 卸载蓝队资源
        UnloadBlueTeamResources(cacheManager, battleGuid);
        // ===== 新增:注销战场红队引用(自动按引用计数卸载)=====
        cacheManager.UnregisterBattlefieldRedTeam(battleGuid);
        cacheManager.UnregisterBattlefield(battleGuid);
        Debug.Log($"BattleUnloadManager: Unloaded battlefield {battleGuid}");
    }
}