From c2c98d54516b45118bd4061317b3e291539119c5 Mon Sep 17 00:00:00 2001
From: hch <305670599@qq.com>
Date: 星期三, 03 十二月 2025 18:10:54 +0800
Subject: [PATCH] Merge branch 'master' of http://mobile.secondworld.net.cn:10010/r/Project_SG_scripts
---
Main/System/Battle/BattleResources/BattleCacheManager.cs.meta | 11
Main/System/Battle/BattleResources/TeamResTracker.cs.meta | 11
Main/System/Battle/BattleField/RecordActions/PreloadResAction.cs | 61 ++
Main/System/Battle/BattleObject/BattleObjectFactory.cs | 24
Main/System/Battle/BattleResources/BattleUnloadManager.cs | 103 +++
Main/System/Battle/BattleResources/BattleResCache.cs | 93 +++
Main/System/Battle/BattleResources/BattleResManager.cs | 54 ++
Main/System/Battle/RecordPlayer/RecordActionType.cs | 2
Main/System/Battle/BattleResources/TeamResTracker.cs | 192 +++++++
Main/System/Battle/BattleField/BattleField.cs | 25
Main/System/Battle/BattleResources.meta | 8
Main/System/Battle/BattleResources/BattleResCache.cs.meta | 11
Main/System/Battle/BattleResources/BattleResManager.cs.meta | 11
Main/System/Battle/BattleResources/BattleCacheManager.cs | 462 +++++++++++++++++
Main/System/Battle/BattleResources/BattleUnloadManager.cs.meta | 11
Main/System/Battle/BattleResources/BattleAudioResLoader.cs.meta | 11
Main/System/Battle/BattleResources/BattleAudioResLoader.cs | 125 ++++
Main/System/Battle/BattleResources/BattleSpineResLoader.cs.meta | 11
Main/System/Battle/BattleResources/BattlePreloadManager.cs.meta | 11
Main/System/Battle/BattleField/RecordActions/PreloadResAction.cs.meta | 11
Main/System/Battle/Sound/BattleSoundManager.cs | 17
Main/Component/UI/Effect/BattleEffectPlayer.cs | 18
Main/System/Battle/BattleResources/BattleSpineResLoader.cs | 125 ++++
Main/System/Battle/BattleResources/BattlePreloadManager.cs | 168 ++++++
24 files changed, 1,559 insertions(+), 17 deletions(-)
diff --git a/Main/Component/UI/Effect/BattleEffectPlayer.cs b/Main/Component/UI/Effect/BattleEffectPlayer.cs
index 8539e27..139fc2a 100644
--- a/Main/Component/UI/Effect/BattleEffectPlayer.cs
+++ b/Main/Component/UI/Effect/BattleEffectPlayer.cs
@@ -353,15 +353,27 @@
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;
diff --git a/Main/System/Battle/BattleField/BattleField.cs b/Main/System/Battle/BattleField/BattleField.cs
index fdd0225..a56fe95 100644
--- a/Main/System/Battle/BattleField/BattleField.cs
+++ b/Main/System/Battle/BattleField/BattleField.cs
@@ -145,6 +145,8 @@
rejectNewPackage = false;
OnRoundChange?.Invoke(round, turnMax);
+ PreloadResources(redTeamList, blueTeamList);
+
#if UNITY_EDITOR
if (Launch.Instance.isOpenSkillLogFile)
{
@@ -182,7 +184,19 @@
}
#endif
}
-
+
+ private void PreloadResources(List<TeamBase> redTeamList, List<TeamBase> blueTeamList)
+ {
+ if (blueTeamList == null || blueTeamList.Count <= 0)
+ {
+ return;
+ }
+
+ // 浼犻�掓垬鍦篏UID
+ PreloadResAction preloadAction = new PreloadResAction(this, redTeamList, blueTeamList);
+ recordPlayer.PlayRecord(preloadAction);
+ }
+
protected virtual void LoadMap(int mapID)
{
BattleMapConfig battleMapConfig = BattleMapConfig.Get(mapID);
@@ -244,6 +258,9 @@
public virtual void Run()
{
+ // 娓呯悊闊抽
+ soundManager.Run();
+
if (IsPause)
return;
@@ -605,6 +622,9 @@
// 鎴樺満鑷韩鐨勭粨鏉熼�昏緫锛屼笉鍚粨绠楃瓑澶栭儴閫昏緫
OnSettlement(turnFightStateData);
+ BattleResManager.Instance.UnloadBattleResources(guid);
+
+
int winFaction = (int)turnFightStateData["winFaction"];
//鑾疯儨闃佃惀: 涓�鑸负1鎴栬��2锛屽綋鐜╁鍙戣捣鐨勬垬鏂楁椂锛屽鏋滆幏鑳滈樀钀ヤ笉绛変簬1浠h〃鐜╁澶辫触浜�
@@ -641,6 +661,9 @@
// 娓呯悊姝讳骸澶勭悊璁板綍
processingDeathObjIds.Clear();
+
+ // ===== 鏂板锛氬嵏杞借摑闃熻祫婧� =====
+ BattleResManager.Instance.UnloadBattleResources(guid);
}
//娓呭満鏁屾柟浣嗕笉缁堟鎴樻枟锛岀敤浜庡垏鎹富绾緽OSS鎴樻枟鍚庯紝姝e父鏄剧ず鏁屾柟
diff --git a/Main/System/Battle/BattleField/RecordActions/PreloadResAction.cs b/Main/System/Battle/BattleField/RecordActions/PreloadResAction.cs
new file mode 100644
index 0000000..278d4e3
--- /dev/null
+++ b/Main/System/Battle/BattleField/RecordActions/PreloadResAction.cs
@@ -0,0 +1,61 @@
+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;
+ }
+
+ // 浼犻�掓垬鍦篏UID
+ BattleResManager.Instance.PreloadBattleResources(
+ battleField.guid, // 鈫� 鍏抽敭锛氫紶閫掓垬鍦篏UID
+ 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()
+ {
+ //姝e父寮�濮嬩箣鍚庡埌鐣岄潰鍑虹幇涔嬪墠閮界偣涓嶄簡 鎵�浠ヨ繖杈逛笉鐢ㄥ己鍒跺畬鎴� 鎺ュ彛鐣欑潃
+
+ base.ForceFinish();
+ // 瀹屾垚灏卞紑濮嬫樉绀篣I
+
+ }
+}
\ No newline at end of file
diff --git a/Main/System/Battle/BattleField/RecordActions/PreloadResAction.cs.meta b/Main/System/Battle/BattleField/RecordActions/PreloadResAction.cs.meta
new file mode 100644
index 0000000..5d30a06
--- /dev/null
+++ b/Main/System/Battle/BattleField/RecordActions/PreloadResAction.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e694db14c5e25c6488fb04c8847759ae
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/System/Battle/BattleObject/BattleObjectFactory.cs b/Main/System/Battle/BattleObject/BattleObjectFactory.cs
index 82bbde4..58cae7d 100644
--- a/Main/System/Battle/BattleObject/BattleObjectFactory.cs
+++ b/Main/System/Battle/BattleObject/BattleObjectFactory.cs
@@ -24,12 +24,26 @@
// 杩欓噷鎶ラ敊浜嗘鏌ヤ竴涓�
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*/);
@@ -40,12 +54,6 @@
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;
diff --git a/Main/System/Battle/BattleResources.meta b/Main/System/Battle/BattleResources.meta
new file mode 100644
index 0000000..d8dd4b3
--- /dev/null
+++ b/Main/System/Battle/BattleResources.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: eb0c5385e2bb4aa4b8860b129609a2c4
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/System/Battle/BattleResources/BattleAudioResLoader.cs b/Main/System/Battle/BattleResources/BattleAudioResLoader.cs
new file mode 100644
index 0000000..194214f
--- /dev/null
+++ b/Main/System/Battle/BattleResources/BattleAudioResLoader.cs
@@ -0,0 +1,125 @@
+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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Main/System/Battle/BattleResources/BattleAudioResLoader.cs.meta b/Main/System/Battle/BattleResources/BattleAudioResLoader.cs.meta
new file mode 100644
index 0000000..e15c6fd
--- /dev/null
+++ b/Main/System/Battle/BattleResources/BattleAudioResLoader.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ee1b6f0f511d6814b9d5d8c88ebbf62d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/System/Battle/BattleResources/BattleCacheManager.cs b/Main/System/Battle/BattleResources/BattleCacheManager.cs
new file mode 100644
index 0000000..86f9d04
--- /dev/null
+++ b/Main/System/Battle/BattleResources/BattleCacheManager.cs
@@ -0,0 +1,462 @@
+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>>();
+
+ // 闇�瑕佹坊鍔犵殑瀛楁
+ 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 = "")
+ {
+ 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>();
+
+ foreach (var res in spineResources)
+ {
+ if (!string.IsNullOrEmpty(res.OwnerId))
+ {
+ ownerIds.Add(res.OwnerId);
+ }
+ }
+
+ 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;
+
+ var ownerIds = battlefieldRedTeamOwners[battleGuid];
+
+ // 浠庢墍鏈夌孩闃熻祫婧愪腑绉婚櫎杩欎簺OwnerIds鐨勫紩鐢�
+ RemoveOwnersFromRedTeamCache(ownerIds);
+
+ battlefieldRedTeamOwners.Remove(battleGuid);
+ }
+
+ private void RemoveOwnersFromRedTeamCache(HashSet<string> ownerIds)
+ {
+ // 澶勭悊Spine璧勬簮
+ var spineKeysToRemove = new List<string>();
+ foreach (var kvp in globalRedTeamSpineCache)
+ {
+ foreach (var ownerId in ownerIds)
+ {
+ kvp.Value.RemoveOwner(ownerId);
+ }
+
+ // 寮曠敤璁℃暟涓�0鏃剁湡姝e嵏杞�
+ if (kvp.Value.RefCount == 0)
+ {
+ spineKeysToRemove.Add(kvp.Key);
+ }
+ }
+
+ 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}");
+ }
+
+ // 澶勭悊Audio璧勬簮
+ 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 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}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Main/System/Battle/BattleResources/BattleCacheManager.cs.meta b/Main/System/Battle/BattleResources/BattleCacheManager.cs.meta
new file mode 100644
index 0000000..4a5044f
--- /dev/null
+++ b/Main/System/Battle/BattleResources/BattleCacheManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 257fb2e8a69b6a44899a0111603f9fdc
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/System/Battle/BattleResources/BattlePreloadManager.cs b/Main/System/Battle/BattleResources/BattlePreloadManager.cs
new file mode 100644
index 0000000..9d69924
--- /dev/null
+++ b/Main/System/Battle/BattleResources/BattlePreloadManager.cs
@@ -0,0 +1,168 @@
+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);
+
+ // ===== 鏂板锛氭敞鍐屾垬鍦虹孩闃熻祫婧愰渶姹� =====
+ cacheManager.RegisterBattlefieldRedTeam(battleGuid, redTeamInfo.SpineResources, redTeamInfo.AudioResources);
+
+ 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 // 鈫� 涓嶆槸绾㈤槦
+ );
+ }
+}
\ No newline at end of file
diff --git a/Main/System/Battle/BattleResources/BattlePreloadManager.cs.meta b/Main/System/Battle/BattleResources/BattlePreloadManager.cs.meta
new file mode 100644
index 0000000..822374b
--- /dev/null
+++ b/Main/System/Battle/BattleResources/BattlePreloadManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e34093150a553f343ac1758dd429544a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/System/Battle/BattleResources/BattleResCache.cs b/Main/System/Battle/BattleResources/BattleResCache.cs
new file mode 100644
index 0000000..736befd
--- /dev/null
+++ b/Main/System/Battle/BattleResources/BattleResCache.cs
@@ -0,0 +1,93 @@
+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; // 璧勬簮鎵�鏈夎�匢D (鏍煎紡: 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Main/System/Battle/BattleResources/BattleResCache.cs.meta b/Main/System/Battle/BattleResources/BattleResCache.cs.meta
new file mode 100644
index 0000000..a17e927
--- /dev/null
+++ b/Main/System/Battle/BattleResources/BattleResCache.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 57597e8314173bd498166dc244e55a96
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/System/Battle/BattleResources/BattleResManager.cs b/Main/System/Battle/BattleResources/BattleResManager.cs
new file mode 100644
index 0000000..4858268
--- /dev/null
+++ b/Main/System/Battle/BattleResources/BattleResManager.cs
@@ -0,0 +1,54 @@
+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);
+ }
+}
\ No newline at end of file
diff --git a/Main/System/Battle/BattleResources/BattleResManager.cs.meta b/Main/System/Battle/BattleResources/BattleResManager.cs.meta
new file mode 100644
index 0000000..729ba5e
--- /dev/null
+++ b/Main/System/Battle/BattleResources/BattleResManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b625f831b0c2aa0439ba1614b4159616
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/System/Battle/BattleResources/BattleSpineResLoader.cs b/Main/System/Battle/BattleResources/BattleSpineResLoader.cs
new file mode 100644
index 0000000..174d944
--- /dev/null
+++ b/Main/System/Battle/BattleResources/BattleSpineResLoader.cs
@@ -0,0 +1,125 @@
+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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Main/System/Battle/BattleResources/BattleSpineResLoader.cs.meta b/Main/System/Battle/BattleResources/BattleSpineResLoader.cs.meta
new file mode 100644
index 0000000..1e0102b
--- /dev/null
+++ b/Main/System/Battle/BattleResources/BattleSpineResLoader.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b7c61553574c7a045871fc98457bfe1a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/System/Battle/BattleResources/BattleUnloadManager.cs b/Main/System/Battle/BattleResources/BattleUnloadManager.cs
new file mode 100644
index 0000000..9645371
--- /dev/null
+++ b/Main/System/Battle/BattleResources/BattleUnloadManager.cs
@@ -0,0 +1,103 @@
+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);
+ }
+}
\ No newline at end of file
diff --git a/Main/System/Battle/BattleResources/BattleUnloadManager.cs.meta b/Main/System/Battle/BattleResources/BattleUnloadManager.cs.meta
new file mode 100644
index 0000000..0274a94
--- /dev/null
+++ b/Main/System/Battle/BattleResources/BattleUnloadManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f103af01d502fa142b50f08dc14cdb67
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/System/Battle/BattleResources/TeamResTracker.cs b/Main/System/Battle/BattleResources/TeamResTracker.cs
new file mode 100644
index 0000000..56e20fa
--- /dev/null
+++ b/Main/System/Battle/BattleResources/TeamResTracker.cs
@@ -0,0 +1,192 @@
+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);
+
+ // 鍙鍔犺浇鎶�鑳界浉鍏宠祫婧�(闊虫晥銆佺壒鏁圫pine)
+ 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璧勬簮锛堝彧棰勫姞杞界壒鏁圫pine锛屼笉棰勫姞杞借鑹睸pine锛�
+ 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 // 鈫� 娣诲姞鎵�鏈夎�匢D
+ };
+
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/Main/System/Battle/BattleResources/TeamResTracker.cs.meta b/Main/System/Battle/BattleResources/TeamResTracker.cs.meta
new file mode 100644
index 0000000..cd2a5b0
--- /dev/null
+++ b/Main/System/Battle/BattleResources/TeamResTracker.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1ab0650d593061a4aaa2e7ad15d86d10
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/System/Battle/RecordPlayer/RecordActionType.cs b/Main/System/Battle/RecordPlayer/RecordActionType.cs
index cd8f4b3..fbc5588 100644
--- a/Main/System/Battle/RecordPlayer/RecordActionType.cs
+++ b/Main/System/Battle/RecordPlayer/RecordActionType.cs
@@ -12,4 +12,6 @@
DodgeFinish,//闂伩瀹屾垚
RoundChange,//鍥炲悎鍒囨崲
+
+ PreloadRes,//棰勫姞杞借祫婧�
}
diff --git a/Main/System/Battle/Sound/BattleSoundManager.cs b/Main/System/Battle/Sound/BattleSoundManager.cs
index 7653235..de14247 100644
--- a/Main/System/Battle/Sound/BattleSoundManager.cs
+++ b/Main/System/Battle/Sound/BattleSoundManager.cs
@@ -195,11 +195,18 @@
private AudioClip LoadAudioClip(int audioId)
{
var config = AudioConfig.Get(audioId);
- if (config != null)
- {
- return AudioLoader.LoadAudio(config.Folder, config.Audio);
- }
- return null;
+ if (config == null)
+ return null;
+
+ // ===== 淇锛氭坊鍔� Audio/ 鍓嶇紑 =====
+ AudioClip audioClip = BattleResManager.Instance.GetAudioResource(
+ "Audio/" + config.Folder, // 鈫� 淇锛氭坊鍔� Audio/ 鍓嶇紑
+ config.Audio,
+ battleField.guid
+ );
+
+ return audioClip;
+ // ================================
}
/// <summary>
--
Gitblit v1.8.0