| | |
| | | |
| | | protected void PlaySpineEffect() |
| | | { |
| | | // 这里是纯spine的逻辑 |
| | | |
| | | if (spineComp == null) |
| | | { |
| | | Debug.LogError("BattleEffectPlayer spineComp is null, effect id is " + effectId); |
| | | return; |
| | | } |
| | | |
| | | SkeletonDataAsset skeletonDataAsset = ResManager.Instance.LoadAsset<SkeletonDataAsset>("UIEffect/" + effectConfig.packageName, effectConfig.fxName); |
| | | // ===== 简化:直接从缓存获取,缓存内部会自动加载 ===== |
| | | string directory = "UIEffect/" + effectConfig.packageName; |
| | | SkeletonDataAsset skeletonDataAsset = BattleResManager.Instance.GetSpineResource( |
| | | directory, |
| | | effectConfig.fxName, |
| | | battleField?.guid |
| | | ); |
| | | |
| | | if (skeletonDataAsset == null) |
| | | { |
| | | Debug.LogError($"BattleEffectPlayer: Failed to load effect spine {effectConfig.fxName}"); |
| | | return; |
| | | } |
| | | // ================================ |
| | | |
| | | spineComp.skeletonDataAsset = skeletonDataAsset; |
| | | spineComp.Initialize(true); |
| | | spineComp.timeScale = speedRate; |
| | |
| | | rejectNewPackage = false; |
| | | OnRoundChange?.Invoke(round, turnMax); |
| | | |
| | | PreloadResources(redTeamList, blueTeamList); |
| | | |
| | | #if UNITY_EDITOR |
| | | if (Launch.Instance.isOpenSkillLogFile) |
| | | { |
| | |
| | | battleHpRecorder.Add(guid, battleHpRecord); |
| | | } |
| | | #endif |
| | | } |
| | | |
| | | private void PreloadResources(List<TeamBase> redTeamList, List<TeamBase> blueTeamList) |
| | | { |
| | | if (blueTeamList == null || blueTeamList.Count <= 0) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | // 传递战场GUID |
| | | PreloadResAction preloadAction = new PreloadResAction(this, redTeamList, blueTeamList); |
| | | recordPlayer.PlayRecord(preloadAction); |
| | | } |
| | | |
| | | protected virtual void LoadMap(int mapID) |
| | |
| | | |
| | | public virtual void Run() |
| | | { |
| | | // 清理音频 |
| | | soundManager.Run(); |
| | | |
| | | if (IsPause) |
| | | return; |
| | | |
| | |
| | | // 战场自身的结束逻辑,不含结算等外部逻辑 |
| | | OnSettlement(turnFightStateData); |
| | | |
| | | BattleResManager.Instance.UnloadBattleResources(guid); |
| | | |
| | | |
| | | int winFaction = (int)turnFightStateData["winFaction"]; |
| | | //获胜阵营: 一般为1或者2,当玩家发起的战斗时,如果获胜阵营不等于1代表玩家失败了 |
| | | |
| | |
| | | |
| | | // 清理死亡处理记录 |
| | | processingDeathObjIds.Clear(); |
| | | |
| | | // ===== 新增:卸载蓝队资源 ===== |
| | | BattleResManager.Instance.UnloadBattleResources(guid); |
| | | } |
| | | |
| | | //清场敌方但不终止战斗,用于切换主线BOSS战斗后,正常显示敌方 |
| New file |
| | |
| | | using UnityEngine; |
| | | using System.Collections.Generic; |
| | | |
| | | public class PreloadResAction : RecordAction |
| | | { |
| | | |
| | | private List<TeamBase> redTeamList; |
| | | private List<TeamBase> blueTeamList; |
| | | |
| | | public PreloadResAction(BattleField _battleField, List<TeamBase> _redTeamList, List<TeamBase> _blueTeamList) |
| | | : base(RecordActionType.PreloadRes, _battleField, null) |
| | | { |
| | | redTeamList = _redTeamList; |
| | | blueTeamList = _blueTeamList; |
| | | } |
| | | |
| | | public override bool IsFinished() |
| | | { |
| | | return isFinish; |
| | | } |
| | | |
| | | |
| | | public override void Run() |
| | | { |
| | | base.Run(); |
| | | |
| | | if (isRunOnce) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | // 传递战场GUID |
| | | BattleResManager.Instance.PreloadBattleResources( |
| | | battleField.guid, // ← 关键:传递战场GUID |
| | | redTeamList, |
| | | blueTeamList, |
| | | (progress) => |
| | | { |
| | | BattleDebug.LogError($"Battle {battleField.guid} resources loading: {progress * 100}%"); |
| | | }, |
| | | OnPreloadFinish |
| | | ); |
| | | |
| | | isRunOnce = true; |
| | | } |
| | | |
| | | private void OnPreloadFinish() |
| | | { |
| | | BattleDebug.LogError("Battle resources preload complete."); |
| | | isFinish = true; |
| | | } |
| | | |
| | | public override void ForceFinish() |
| | | { |
| | | //正常开始之后到界面出现之前都点不了 所以这边不用强制完成 接口留着 |
| | | |
| | | base.ForceFinish(); |
| | | // 完成就开始显示UI |
| | | |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: e694db14c5e25c6488fb04c8847759ae |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| | |
| | | // 这里报错了检查一下 |
| | | public static BattleObject CreateBattleObject(BattleField _battleField, List<GameObject> posNodeList, TeamHero teamHero, BattleCamp _Camp) |
| | | { |
| | | HeroSkinConfig skinCfg = teamHero.skinConfig; |
| | | var skinCfg = HeroSkinConfig.Get(teamHero.SkinID); |
| | | if (skinCfg == null) |
| | | { |
| | | Debug.LogError(teamHero.heroId + "BattleObjectFactory.CreateBattleObject: skinCfg is null for " + teamHero.SkinID); |
| | | Debug.LogError($"BattleObjectFactory: skinCfg is null for SkinID {teamHero.SkinID}"); |
| | | return null; |
| | | } |
| | | |
| | | // ===== 简化:直接从缓存获取,缓存内部会自动加载 ===== |
| | | SkeletonDataAsset skeletonDataAsset = BattleResManager.Instance.GetSpineResource( |
| | | "Hero/SpineRes/", |
| | | skinCfg.SpineRes, |
| | | _battleField.guid |
| | | ); |
| | | |
| | | if (skeletonDataAsset == null) |
| | | { |
| | | Debug.LogError($"BattleObjectFactory: Failed to load SkeletonDataAsset for {skinCfg.SpineRes}"); |
| | | return null; |
| | | } |
| | | // ============================================== |
| | | |
| | | GameObject battleGO = ResManager.Instance.LoadAsset<GameObject>("Hero/SpineRes", "Hero_001"/*skinCfg.SpineRes*/); |
| | | |
| | |
| | | GameObject realGO = GameObject.Instantiate(battleGO, goParent.transform); |
| | | SkeletonAnimation skeletonAnimation = realGO.GetComponentInChildren<SkeletonAnimation>(true); |
| | | |
| | | var skeletonDataAsset = ResManager.Instance.LoadAsset<SkeletonDataAsset>("Hero/SpineRes/", skinCfg.SpineRes); |
| | | if (skeletonDataAsset == null) |
| | | { |
| | | Debug.LogError("BattleObjectFactory.CreateBattleObject: skeletonDataAsset is null for " + skinCfg.SpineRes); |
| | | return null; |
| | | } |
| | | |
| | | float finalScaleRate = modelScaleRate * teamHero.modelScale; |
| | | |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: eb0c5385e2bb4aa4b8860b129609a2c4 |
| | | folderAsset: yes |
| | | DefaultImporter: |
| | | externalObjects: {} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using UnityEngine; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | |
| | | /// <summary> |
| | | /// 音频资源异步加载器 |
| | | /// </summary> |
| | | public class BattleAudioResLoader |
| | | { |
| | | private int loadingCount = 0; |
| | | private int totalCount = 0; |
| | | private Action<float> onProgress; |
| | | private Action onComplete; |
| | | private BattleCacheManager cacheManager; |
| | | private bool isPersistent; |
| | | |
| | | /// <summary> |
| | | /// 批量异步加载音频资源 |
| | | /// </summary> |
| | | public void LoadAudioResourcesAsync(List<BattleResCache.ResourceIdentifier> identifiers, |
| | | Dictionary<string, BattleResCache.CachedResource> cache, |
| | | Action<float> progressCallback, |
| | | Action completeCallback, |
| | | BattleCacheManager manager = null, |
| | | bool isRedTeam = false) |
| | | { |
| | | if (identifiers == null || identifiers.Count == 0) |
| | | { |
| | | completeCallback?.Invoke(); |
| | | return; |
| | | } |
| | | |
| | | loadingCount = 0; |
| | | totalCount = identifiers.Count; |
| | | onProgress = progressCallback; |
| | | onComplete = completeCallback; |
| | | cacheManager = manager; |
| | | isPersistent = isRedTeam; |
| | | |
| | | foreach (var identifier in identifiers) |
| | | { |
| | | string key = identifier.GetKey(); |
| | | |
| | | // 检查缓存 |
| | | if (cache.ContainsKey(key)) |
| | | { |
| | | // 已缓存,如果是红队资源且有管理器,添加引用 |
| | | if (isPersistent && cacheManager != null && !string.IsNullOrEmpty(identifier.OwnerId)) |
| | | { |
| | | cacheManager.AddRedTeamAudioReference(key, cache[key], identifier.OwnerId); |
| | | } |
| | | OnSingleLoadComplete(); |
| | | continue; |
| | | } |
| | | |
| | | // 异步加载 |
| | | LoadSingleAudioAsync(identifier, cache); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 加载单个音频资源 |
| | | /// </summary> |
| | | private void LoadSingleAudioAsync(BattleResCache.ResourceIdentifier identifier, |
| | | Dictionary<string, BattleResCache.CachedResource> cache) |
| | | { |
| | | ResManager.Instance.LoadAssetAsync<AudioClip>( |
| | | identifier.Directory, |
| | | identifier.AssetName, |
| | | (success, asset) => |
| | | { |
| | | if (success && asset != null) |
| | | { |
| | | AudioClip audioClip = asset as AudioClip; |
| | | if (audioClip != null) |
| | | { |
| | | string key = identifier.GetKey(); |
| | | var cachedRes = new BattleResCache.CachedResource( |
| | | identifier, |
| | | audioClip, |
| | | identifier.IsPersistent |
| | | ); |
| | | cache[key] = cachedRes; |
| | | |
| | | // 如果是红队资源且有管理器,添加引用 |
| | | if (isPersistent && cacheManager != null && !string.IsNullOrEmpty(identifier.OwnerId)) |
| | | { |
| | | cacheManager.AddRedTeamAudioReference(key, cachedRes, identifier.OwnerId); |
| | | } |
| | | |
| | | Debug.Log($"BattleAudioResLoader: Loaded audio resource: {key}"); |
| | | } |
| | | else |
| | | { |
| | | Debug.LogError($"BattleAudioResLoader: Failed to cast to AudioClip: {identifier.AssetName}"); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | Debug.LogError($"BattleAudioResLoader: Failed to load audio resource: {identifier.Directory}/{identifier.AssetName}"); |
| | | } |
| | | |
| | | OnSingleLoadComplete(); |
| | | }, |
| | | false // needExt 参数:音频文件名已包含扩展名 |
| | | ); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 单个资源加载完成 |
| | | /// </summary> |
| | | private void OnSingleLoadComplete() |
| | | { |
| | | loadingCount++; |
| | | |
| | | float progress = (float)loadingCount / totalCount; |
| | | onProgress?.Invoke(progress); |
| | | |
| | | if (loadingCount >= totalCount) |
| | | { |
| | | Debug.Log($"BattleAudioResLoader: All audio resources loaded ({totalCount} items)"); |
| | | onComplete?.Invoke(); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: ee1b6f0f511d6814b9d5d8c88ebbf62d |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using UnityEngine; |
| | | using System.Collections.Generic; |
| | | using Spine.Unity; |
| | | |
| | | public class BattleCacheManager |
| | | { |
| | | /// <summary> |
| | | /// 资源引用信息 |
| | | /// </summary> |
| | | private class ResourceReference |
| | | { |
| | | public BattleResCache.CachedResource CachedResource; |
| | | public HashSet<string> OwnerIds = new HashSet<string>(); // 使用该资源的角色ID集合 |
| | | |
| | | public int RefCount => OwnerIds.Count; |
| | | |
| | | public void AddOwner(string ownerId) |
| | | { |
| | | OwnerIds.Add(ownerId); |
| | | } |
| | | |
| | | public void RemoveOwner(string ownerId) |
| | | { |
| | | OwnerIds.Remove(ownerId); |
| | | } |
| | | } |
| | | |
| | | // ===== 红队资源:全局共享,按引用计数管理 ===== |
| | | private static Dictionary<string, ResourceReference> globalRedTeamSpineCache = |
| | | new Dictionary<string, ResourceReference>(); |
| | | |
| | | private static Dictionary<string, ResourceReference> globalRedTeamAudioCache = |
| | | 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>>(); |
| | | |
| | | /// <summary> |
| | | /// 获取Spine缓存(红队全局,蓝队按战场隔离) |
| | | /// </summary> |
| | | public Dictionary<string, BattleResCache.CachedResource> GetSpineCache(bool isPersistent, string battleGuid = "") |
| | | { |
| | | 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}"; |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 257fb2e8a69b6a44899a0111603f9fdc |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using UnityEngine; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | |
| | | public class BattlePreloadManager |
| | | { |
| | | private BattleSpineResLoader spineLoader = new BattleSpineResLoader(); |
| | | private BattleAudioResLoader audioLoader = new BattleAudioResLoader(); |
| | | private BattleCacheManager cacheManager = new BattleCacheManager(); |
| | | private BattleUnloadManager unloadManager = new BattleUnloadManager(); |
| | | |
| | | private bool isLoading = false; |
| | | |
| | | public BattleCacheManager CacheManager => cacheManager; |
| | | public BattleUnloadManager UnloadManager => unloadManager; |
| | | |
| | | /// <summary> |
| | | /// 预加载战斗资源 |
| | | /// </summary> |
| | | public void PreloadBattleResources(string battleGuid, List<TeamBase> redTeamList, List<TeamBase> blueTeamList, |
| | | Action<float> progressCallback, Action completeCallback) |
| | | { |
| | | if (isLoading) |
| | | { |
| | | Debug.LogWarning("BattlePreloadManager: Already loading, ignoring request"); |
| | | return; |
| | | } |
| | | |
| | | isLoading = true; |
| | | |
| | | var redTeamInfo = AnalyzeTeamList(redTeamList, true); |
| | | var blueTeamInfo = AnalyzeTeamList(blueTeamList, false); |
| | | |
| | | StartPreload(redTeamInfo, blueTeamInfo, battleGuid, progressCallback, () => |
| | | { |
| | | isLoading = false; |
| | | completeCallback?.Invoke(); |
| | | }); |
| | | } |
| | | |
| | | private TeamResTracker.TeamResourceInfo AnalyzeTeamList(List<TeamBase> teamList, bool isPersistent) |
| | | { |
| | | var combinedInfo = new TeamResTracker.TeamResourceInfo(); |
| | | |
| | | if (teamList == null || teamList.Count == 0) |
| | | { |
| | | return combinedInfo; |
| | | } |
| | | |
| | | foreach (var team in teamList) |
| | | { |
| | | if (team == null) |
| | | continue; |
| | | |
| | | var teamInfo = TeamResTracker.AnalyzeTeam(team, isPersistent); |
| | | MergeResourceInfo(combinedInfo, teamInfo); |
| | | } |
| | | |
| | | return combinedInfo; |
| | | } |
| | | |
| | | private void MergeResourceInfo(TeamResTracker.TeamResourceInfo target, TeamResTracker.TeamResourceInfo source) |
| | | { |
| | | // 合并Spine资源(去重) |
| | | foreach (var res in source.SpineResources) |
| | | { |
| | | if (!ContainsResource(target.SpineResources, res)) |
| | | { |
| | | target.SpineResources.Add(res); |
| | | } |
| | | } |
| | | |
| | | // 合并音频资源(去重) |
| | | foreach (var res in source.AudioResources) |
| | | { |
| | | if (!ContainsResource(target.AudioResources, res)) |
| | | { |
| | | target.AudioResources.Add(res); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private bool ContainsResource(List<BattleResCache.ResourceIdentifier> list, BattleResCache.ResourceIdentifier resource) |
| | | { |
| | | foreach (var item in list) |
| | | { |
| | | if (item.GetKey() == resource.GetKey()) |
| | | { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private void StartPreload(TeamResTracker.TeamResourceInfo redInfo, TeamResTracker.TeamResourceInfo blueInfo, |
| | | string battleGuid, |
| | | Action<float> progressCallback, Action completeCallback) |
| | | { |
| | | int totalResources = redInfo.GetTotalCount() + blueInfo.GetTotalCount(); |
| | | |
| | | if (totalResources == 0) |
| | | { |
| | | Debug.Log("BattlePreloadManager: No resources to preload"); |
| | | completeCallback?.Invoke(); |
| | | return; |
| | | } |
| | | |
| | | 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}"); |
| | | |
| | | int completedPhases = 0; |
| | | int totalPhases = 4; |
| | | |
| | | Action onPhaseComplete = () => |
| | | { |
| | | completedPhases++; |
| | | float progress = (float)completedPhases / totalPhases; |
| | | progressCallback?.Invoke(progress); |
| | | |
| | | if (completedPhases >= totalPhases) |
| | | { |
| | | Debug.Log($"BattlePreloadManager: Completed! {cacheManager.GetCacheStats(battleGuid)}"); |
| | | completeCallback?.Invoke(); |
| | | } |
| | | }; |
| | | |
| | | // 并行加载4个阶段(传入cacheManager和是否为红队标识) |
| | | spineLoader.LoadSpineResourcesAsync( |
| | | redInfo.SpineResources, |
| | | cacheManager.GetSpineCache(true, battleGuid), |
| | | null, |
| | | onPhaseComplete, |
| | | cacheManager, // ← 传入管理器 |
| | | true // ← 是红队 |
| | | ); |
| | | |
| | | audioLoader.LoadAudioResourcesAsync( |
| | | redInfo.AudioResources, |
| | | cacheManager.GetAudioCache(true, battleGuid), |
| | | null, |
| | | onPhaseComplete, |
| | | cacheManager, // ← 传入管理器 |
| | | true // ← 是红队 |
| | | ); |
| | | |
| | | spineLoader.LoadSpineResourcesAsync( |
| | | blueInfo.SpineResources, |
| | | cacheManager.GetSpineCache(false, battleGuid), |
| | | null, |
| | | onPhaseComplete, |
| | | null, // ← 蓝队不需要引用追踪 |
| | | false // ← 不是红队 |
| | | ); |
| | | |
| | | audioLoader.LoadAudioResourcesAsync( |
| | | blueInfo.AudioResources, |
| | | cacheManager.GetAudioCache(false, battleGuid), |
| | | null, |
| | | onPhaseComplete, |
| | | null, // ← 蓝队不需要引用追踪 |
| | | false // ← 不是红队 |
| | | ); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理红队变更:清空旧的,重新加载新的 |
| | | /// </summary> |
| | | public void HandleRedTeamChange(List<TeamBase> newRedTeamList, Action completeCallback) |
| | | { |
| | | if (newRedTeamList == null) |
| | | { |
| | | completeCallback?.Invoke(); |
| | | return; |
| | | } |
| | | |
| | | Debug.Log("BattlePreloadManager: Handling red team change"); |
| | | |
| | | // 1. 卸载旧的红队资源 |
| | | unloadManager.UnloadRedTeamResources(cacheManager); |
| | | |
| | | // 2. 分析新红队资源 |
| | | var newRedInfo = AnalyzeTeamList(newRedTeamList, true); |
| | | |
| | | // 3. 预加载新红队资源(红队是全局的,传空字符串) |
| | | StartPreload(newRedInfo, new TeamResTracker.TeamResourceInfo(), "", null, completeCallback); |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: e34093150a553f343ac1758dd429544a |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using UnityEngine; |
| | | using Spine.Unity; |
| | | using System.Collections.Generic; |
| | | |
| | | /// <summary> |
| | | /// 战斗资源缓存信息 |
| | | /// </summary> |
| | | public class BattleResCache |
| | | { |
| | | /// <summary> |
| | | /// 资源标识符 |
| | | /// </summary> |
| | | public class ResourceIdentifier |
| | | { |
| | | public string Directory; // 资源目录 |
| | | public string AssetName; // 资源名称 |
| | | public ResourceType Type; // 资源类型 |
| | | public bool IsPersistent; // 是否常驻(红队资源) |
| | | public string OwnerId; // 资源所有者ID (格式: HeroID_SkinID) |
| | | |
| | | /// <summary> |
| | | /// 获取资源唯一Key(目录+名称) |
| | | /// </summary> |
| | | public string GetKey() |
| | | { |
| | | return $"{Directory}/{AssetName}"; |
| | | } |
| | | |
| | | public override int GetHashCode() |
| | | { |
| | | return GetKey().GetHashCode(); |
| | | } |
| | | |
| | | public override bool Equals(object obj) |
| | | { |
| | | if (obj is ResourceIdentifier other) |
| | | { |
| | | return GetKey() == other.GetKey(); |
| | | } |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 资源类型 |
| | | /// </summary> |
| | | public enum ResourceType |
| | | { |
| | | Spine, // Spine动画资源 |
| | | Audio // 音频资源 |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 缓存的资源项 |
| | | /// </summary> |
| | | public class CachedResource |
| | | { |
| | | public ResourceIdentifier Identifier; |
| | | public Object Asset; // UnityEngine.Object |
| | | public bool IsPersistent; // 是否常驻 |
| | | |
| | | public CachedResource(ResourceIdentifier identifier, Object asset, bool isPersistent) |
| | | { |
| | | Identifier = identifier; |
| | | Asset = asset; |
| | | IsPersistent = isPersistent; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 是否可以卸载(非常驻资源才能卸载) |
| | | /// </summary> |
| | | public bool CanUnload() |
| | | { |
| | | return !IsPersistent; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取Spine资源 |
| | | /// </summary> |
| | | public SkeletonDataAsset GetSkeletonDataAsset() |
| | | { |
| | | return Asset as SkeletonDataAsset; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取音频资源 |
| | | /// </summary> |
| | | public AudioClip GetAudioClip() |
| | | { |
| | | return Asset as AudioClip; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 57597e8314173bd498166dc244e55a96 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using UnityEngine; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using Spine.Unity; |
| | | |
| | | /// <summary> |
| | | /// 战斗资源总管理器 |
| | | /// 对外提供统一的资源管理接口 |
| | | /// </summary> |
| | | public class BattleResManager : Singleton<BattleResManager> |
| | | { |
| | | private BattlePreloadManager preloadManager = new BattlePreloadManager(); |
| | | |
| | | /// <summary> |
| | | /// 预加载战斗资源 |
| | | /// </summary> |
| | | public void PreloadBattleResources(string battleGuid, List<TeamBase> redTeamList, List<TeamBase> blueTeamList, |
| | | Action<float> progressCallback = null, Action completeCallback = null) |
| | | { |
| | | preloadManager.PreloadBattleResources(battleGuid, redTeamList, blueTeamList, progressCallback, completeCallback); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 战斗结束后卸载蓝队资源 |
| | | /// </summary> |
| | | public void UnloadBattleResources(string battleGuid) |
| | | { |
| | | preloadManager.UnloadManager.UnloadBlueTeamResources(preloadManager.CacheManager, battleGuid); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取Spine资源 |
| | | /// </summary> |
| | | public SkeletonDataAsset GetSpineResource(string directory, string assetName, string battleGuid = "", bool autoLoadIfMissing = true) |
| | | { |
| | | return preloadManager.CacheManager.GetSpineResource(directory, assetName, battleGuid, autoLoadIfMissing); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取音频资源 |
| | | /// </summary> |
| | | public AudioClip GetAudioResource(string directory, string assetName, string battleGuid = "", bool autoLoadIfMissing = true) |
| | | { |
| | | return preloadManager.CacheManager.GetAudioResource(directory, assetName, battleGuid, autoLoadIfMissing); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理红队变更 |
| | | /// </summary> |
| | | public void HandleRedTeamChange(List<TeamBase> newRedTeamList, Action completeCallback = null) |
| | | { |
| | | preloadManager.HandleRedTeamChange(newRedTeamList, completeCallback); |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: b625f831b0c2aa0439ba1614b4159616 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using UnityEngine; |
| | | using Spine.Unity; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | |
| | | /// <summary> |
| | | /// Spine资源异步加载器 |
| | | /// </summary> |
| | | public class BattleSpineResLoader |
| | | { |
| | | private int loadingCount = 0; |
| | | private int totalCount = 0; |
| | | private Action<float> onProgress; |
| | | private Action onComplete; |
| | | private BattleCacheManager cacheManager; |
| | | private bool isPersistent; |
| | | |
| | | /// <summary> |
| | | /// 批量异步加载Spine资源 |
| | | /// </summary> |
| | | public void LoadSpineResourcesAsync(List<BattleResCache.ResourceIdentifier> identifiers, |
| | | Dictionary<string, BattleResCache.CachedResource> cache, |
| | | Action<float> progressCallback, |
| | | Action completeCallback, |
| | | BattleCacheManager manager = null, |
| | | bool isRedTeam = false) |
| | | { |
| | | if (identifiers == null || identifiers.Count == 0) |
| | | { |
| | | completeCallback?.Invoke(); |
| | | return; |
| | | } |
| | | |
| | | loadingCount = 0; |
| | | totalCount = identifiers.Count; |
| | | onProgress = progressCallback; |
| | | onComplete = completeCallback; |
| | | cacheManager = manager; |
| | | isPersistent = isRedTeam; |
| | | |
| | | foreach (var identifier in identifiers) |
| | | { |
| | | string key = identifier.GetKey(); |
| | | |
| | | // 检查缓存 |
| | | if (cache.ContainsKey(key)) |
| | | { |
| | | // 已缓存,如果是红队资源且有管理器,添加引用 |
| | | if (isPersistent && cacheManager != null && !string.IsNullOrEmpty(identifier.OwnerId)) |
| | | { |
| | | cacheManager.AddRedTeamSpineReference(key, cache[key], identifier.OwnerId); |
| | | } |
| | | OnSingleLoadComplete(); |
| | | continue; |
| | | } |
| | | |
| | | // 异步加载 |
| | | LoadSingleSpineAsync(identifier, cache); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 加载单个Spine资源 |
| | | /// </summary> |
| | | private void LoadSingleSpineAsync(BattleResCache.ResourceIdentifier identifier, |
| | | Dictionary<string, BattleResCache.CachedResource> cache) |
| | | { |
| | | ResManager.Instance.LoadAssetAsync<SkeletonDataAsset>( |
| | | identifier.Directory, |
| | | identifier.AssetName, |
| | | (success, asset) => |
| | | { |
| | | if (success && asset != null) |
| | | { |
| | | SkeletonDataAsset skeletonAsset = asset as SkeletonDataAsset; |
| | | if (skeletonAsset != null) |
| | | { |
| | | string key = identifier.GetKey(); |
| | | var cachedRes = new BattleResCache.CachedResource( |
| | | identifier, |
| | | skeletonAsset, |
| | | identifier.IsPersistent |
| | | ); |
| | | cache[key] = cachedRes; |
| | | |
| | | // 如果是红队资源且有管理器,添加引用 |
| | | if (isPersistent && cacheManager != null && !string.IsNullOrEmpty(identifier.OwnerId)) |
| | | { |
| | | cacheManager.AddRedTeamSpineReference(key, cachedRes, identifier.OwnerId); |
| | | } |
| | | |
| | | Debug.Log($"BattleSpineResLoader: Loaded spine resource: {key}"); |
| | | } |
| | | else |
| | | { |
| | | Debug.LogError($"BattleSpineResLoader: Failed to cast to SkeletonDataAsset: {identifier.AssetName}"); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | Debug.LogError($"BattleSpineResLoader: Failed to load spine resource: {identifier.Directory}/{identifier.AssetName}"); |
| | | } |
| | | |
| | | OnSingleLoadComplete(); |
| | | } |
| | | ); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 单个资源加载完成 |
| | | /// </summary> |
| | | private void OnSingleLoadComplete() |
| | | { |
| | | loadingCount++; |
| | | |
| | | float progress = (float)loadingCount / totalCount; |
| | | onProgress?.Invoke(progress); |
| | | |
| | | if (loadingCount >= totalCount) |
| | | { |
| | | Debug.Log($"BattleSpineResLoader: All Spine resources loaded ({totalCount} items)"); |
| | | onComplete?.Invoke(); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: b7c61553574c7a045871fc98457bfe1a |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | 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"); |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: f103af01d502fa142b50f08dc14cdb67 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System.Collections.Generic; |
| | | using UnityEngine; |
| | | |
| | | /// <summary> |
| | | /// 队伍资源追踪器 |
| | | /// 分析队伍需要哪些资源(Spine动画、音频) |
| | | /// </summary> |
| | | public class TeamResTracker |
| | | { |
| | | /// <summary> |
| | | /// 队伍资源信息 |
| | | /// </summary> |
| | | public class TeamResourceInfo |
| | | { |
| | | public List<BattleResCache.ResourceIdentifier> SpineResources = new List<BattleResCache.ResourceIdentifier>(); |
| | | public List<BattleResCache.ResourceIdentifier> AudioResources = new List<BattleResCache.ResourceIdentifier>(); |
| | | |
| | | public void Clear() |
| | | { |
| | | SpineResources.Clear(); |
| | | AudioResources.Clear(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取所有资源数量 |
| | | /// </summary> |
| | | public int GetTotalCount() |
| | | { |
| | | return SpineResources.Count + AudioResources.Count; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 分析单个队伍的资源需求 |
| | | /// </summary> |
| | | public static TeamResourceInfo AnalyzeTeam(TeamBase team, bool isPersistent) |
| | | { |
| | | TeamResourceInfo info = new TeamResourceInfo(); |
| | | |
| | | if (team == null || team.serverHeroes == null) |
| | | { |
| | | return info; |
| | | } |
| | | |
| | | foreach (var teamHero in team.serverHeroes) |
| | | { |
| | | if (teamHero == null) |
| | | continue; |
| | | |
| | | // 生成角色唯一标识(用于引用追踪) |
| | | string ownerId = $"{teamHero.heroId}_{teamHero.SkinID}"; |
| | | |
| | | // ===== 移除:不再预加载角色Spine资源 ===== |
| | | // AddHeroSpineResource(teamHero, info, isPersistent, ownerId); |
| | | |
| | | // 只预加载技能相关资源(音效、特效Spine) |
| | | AddSkillResources(teamHero, info, isPersistent, ownerId); |
| | | } |
| | | |
| | | return info; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 添加技能相关资源 |
| | | /// </summary> |
| | | private static void AddSkillResources(TeamHero teamHero, TeamResourceInfo info, bool isPersistent, string ownerId) |
| | | { |
| | | if (teamHero.heroConfig == null) |
| | | return; |
| | | |
| | | // 普攻技能 |
| | | if (teamHero.heroConfig.AtkSkillID > 0) |
| | | { |
| | | AddSingleSkillResources(teamHero.heroConfig.AtkSkillID, info, isPersistent, ownerId); |
| | | } |
| | | |
| | | // 怒气技能 |
| | | if (teamHero.heroConfig.AngerSkillID > 0) |
| | | { |
| | | AddSingleSkillResources(teamHero.heroConfig.AngerSkillID, info, isPersistent, ownerId); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 添加单个技能的资源 |
| | | /// </summary> |
| | | private static void AddSingleSkillResources(int skillId, TeamResourceInfo info, bool isPersistent, string ownerId) |
| | | { |
| | | SkillConfig skillConfig = SkillConfig.Get(skillId); |
| | | if (skillConfig == null) |
| | | return; |
| | | |
| | | AddSkillAudio(skillConfig, info, isPersistent, ownerId); |
| | | AddSkillEffects(skillConfig, info, isPersistent, ownerId); |
| | | } |
| | | |
| | | private static void AddSkillAudio(SkillConfig skillConfig, TeamResourceInfo info, bool isPersistent, string ownerId) |
| | | { |
| | | if (skillConfig.SkinllSFX1 > 0) |
| | | { |
| | | AddAudioResource(skillConfig.SkinllSFX1, info, isPersistent, ownerId); |
| | | } |
| | | |
| | | if (skillConfig.SkinllSFX2 > 0) |
| | | { |
| | | AddAudioResource(skillConfig.SkinllSFX2, info, isPersistent, ownerId); |
| | | } |
| | | } |
| | | |
| | | private static void AddSkillEffects(SkillConfig skillConfig, TeamResourceInfo info, bool isPersistent, string ownerId) |
| | | { |
| | | List<int> effectIds = new List<int>(); |
| | | |
| | | if (skillConfig.BulletEffectId > 0) effectIds.Add(skillConfig.BulletEffectId); |
| | | if (skillConfig.ExplosionEffectId > 0) effectIds.Add(skillConfig.ExplosionEffectId); |
| | | if (skillConfig.ExplosionEffect2 > 0) effectIds.Add(skillConfig.ExplosionEffect2); |
| | | if (skillConfig.ExplosionEffect3 > 0) effectIds.Add(skillConfig.ExplosionEffect3); |
| | | if (skillConfig.ExplosionEffect4 > 0) effectIds.Add(skillConfig.ExplosionEffect4); |
| | | if (skillConfig.EffectId > 0) effectIds.Add(skillConfig.EffectId); |
| | | if (skillConfig.EffectId2 > 0) effectIds.Add(skillConfig.EffectId2); |
| | | if (skillConfig.MStartEffectId > 0) effectIds.Add(skillConfig.MStartEffectId); |
| | | if (skillConfig.BuffEffect > 0) effectIds.Add(skillConfig.BuffEffect); |
| | | if (skillConfig.TriggerEffect > 0) effectIds.Add(skillConfig.TriggerEffect); |
| | | |
| | | foreach (int effectId in effectIds) |
| | | { |
| | | EffectConfig effectConfig = EffectConfig.Get(effectId); |
| | | if (effectConfig == null) |
| | | continue; |
| | | |
| | | // 特效Spine资源(只预加载特效Spine,不预加载角色Spine) |
| | | if (effectConfig.isSpine > 0 && !string.IsNullOrEmpty(effectConfig.fxName)) |
| | | { |
| | | var identifier = new BattleResCache.ResourceIdentifier |
| | | { |
| | | Directory = "UIEffect/" + effectConfig.packageName, |
| | | AssetName = effectConfig.fxName, |
| | | Type = BattleResCache.ResourceType.Spine, |
| | | IsPersistent = isPersistent, |
| | | OwnerId = ownerId // ← 添加所有者ID |
| | | }; |
| | | |
| | | if (!ContainsResource(info.SpineResources, identifier)) |
| | | { |
| | | info.SpineResources.Add(identifier); |
| | | } |
| | | } |
| | | |
| | | // 特效音频 |
| | | if (effectConfig.audio > 0) |
| | | { |
| | | AddAudioResource(effectConfig.audio, info, isPersistent, ownerId); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private static void AddAudioResource(int audioId, TeamResourceInfo info, bool isPersistent, string ownerId) |
| | | { |
| | | AudioConfig audioConfig = AudioConfig.Get(audioId); |
| | | if (audioConfig == null) |
| | | return; |
| | | |
| | | var identifier = new BattleResCache.ResourceIdentifier |
| | | { |
| | | Directory = "Audio/" + audioConfig.Folder, // ← 修复:添加 Audio/ 前缀 |
| | | AssetName = audioConfig.Audio, |
| | | Type = BattleResCache.ResourceType.Audio, |
| | | IsPersistent = isPersistent, |
| | | OwnerId = ownerId |
| | | }; |
| | | |
| | | if (!ContainsResource(info.AudioResources, identifier)) |
| | | { |
| | | info.AudioResources.Add(identifier); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 检查资源列表是否包含指定资源 |
| | | /// </summary> |
| | | private static bool ContainsResource(List<BattleResCache.ResourceIdentifier> list, BattleResCache.ResourceIdentifier resource) |
| | | { |
| | | foreach (var item in list) |
| | | { |
| | | if (item.GetKey() == resource.GetKey()) |
| | | { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 1ab0650d593061a4aaa2e7ad15d86d10 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| | |
| | | DodgeFinish,//闪避完成 |
| | | |
| | | RoundChange,//回合切换 |
| | | |
| | | PreloadRes,//预加载资源 |
| | | } |
| | |
| | | private AudioClip LoadAudioClip(int audioId) |
| | | { |
| | | var config = AudioConfig.Get(audioId); |
| | | if (config != null) |
| | | { |
| | | return AudioLoader.LoadAudio(config.Folder, config.Audio); |
| | | } |
| | | if (config == null) |
| | | return null; |
| | | |
| | | // ===== 修复:添加 Audio/ 前缀 ===== |
| | | AudioClip audioClip = BattleResManager.Instance.GetAudioResource( |
| | | "Audio/" + config.Folder, // ← 修复:添加 Audio/ 前缀 |
| | | config.Audio, |
| | | battleField.guid |
| | | ); |
| | | |
| | | return audioClip; |
| | | // ================================ |
| | | } |
| | | |
| | | /// <summary> |