refactor 5:
- MapData.cs 现在会携带战斗初始上下文:
- InitialCoin
- 建塔 TowerStatsData 快照
- TryConsumeCoin / AddCoin 运行时回调
- CombatLoadingState.cs 负责从 CombatInRunResourceManager 读取 coin 和 build stats 快照,组装 MapData 后再交给加载链。
- CombatLoadSession.cs 改成接收外部构造好的 MapData,不再自己只塞一个 LevelId。
- CombatScheduler.cs 不再在 Start() 里直接发起地图加载,加载细节回到 CombatLoadingState。
- MapEntity.cs 已经不再直接读 GameEntry.CombatNode 的 coin / build count / build stats,也不再通过它做 coin 读写:
- 初始 coin 和 build stats 从 MapData 读取
- 后续 coin 通过 CombatCoinChangedEventArgs 同步
- 建塔/升级/拆塔时的 coin 变更通过 MapData 注入回调执行
This commit is contained in:
parent
ccb4738b96
commit
ca7b2f2dca
|
|
@ -45,7 +45,7 @@ namespace GeometryTD.CustomComponent
|
||||||
_currentMap = null;
|
_currentMap = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool StartLoading(DRLevel level, out string errorMessage)
|
public bool StartLoading(DRLevel level, MapData mapData, out string errorMessage)
|
||||||
{
|
{
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
if (_entity == null)
|
if (_entity == null)
|
||||||
|
|
@ -54,7 +54,7 @@ namespace GeometryTD.CustomComponent
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryShowMap(level, out errorMessage))
|
if (!TryShowMap(level, mapData, out errorMessage))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -224,7 +224,7 @@ namespace GeometryTD.CustomComponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryShowMap(DRLevel level, out string errorMessage)
|
private bool TryShowMap(DRLevel level, MapData mapData, out string errorMessage)
|
||||||
{
|
{
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
if (level == null)
|
if (level == null)
|
||||||
|
|
@ -242,7 +242,10 @@ namespace GeometryTD.CustomComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadingMapEntityId = _entity.GenerateSerialId();
|
_loadingMapEntityId = _entity.GenerateSerialId();
|
||||||
_entity.ShowMap(new MapData(_loadingMapEntityId, level.Id, Vector3.zero), mapAssetName);
|
MapData resolvedMapData = mapData != null
|
||||||
|
? mapData.CloneForEntity(_loadingMapEntityId, Vector3.zero)
|
||||||
|
: new MapData(_loadingMapEntityId, level.Id, Vector3.zero);
|
||||||
|
_entity.ShowMap(resolvedMapData, mapAssetName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,11 +120,6 @@ namespace GeometryTD.CustomComponent
|
||||||
return HandleStartFailure($"CombatScheduler start failed. Level '{level.Id}' has no phase data.");
|
return HandleStartFailure($"CombatScheduler start failed. Level '{level.Id}' has no phase data.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_loadSession.StartLoading(level, out string loadError))
|
|
||||||
{
|
|
||||||
return HandleStartFailure($"CombatScheduler start failed. {loadError}");
|
|
||||||
}
|
|
||||||
|
|
||||||
ChangeState(new CombatLoadingState(this));
|
ChangeState(new CombatLoadingState(this));
|
||||||
Log.Info(
|
Log.Info(
|
||||||
"CombatScheduler started. Level={0}, PhaseCount={1}.",
|
"CombatScheduler started. Level={0}, PhaseCount={1}.",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
using GeometryTD.Entity.EntityData;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
public partial class CombatScheduler
|
public partial class CombatScheduler
|
||||||
|
|
@ -8,6 +13,21 @@ namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnEnter()
|
||||||
|
{
|
||||||
|
if (Scheduler._currentLevel == null)
|
||||||
|
{
|
||||||
|
Scheduler.EnterFailureFallback("Combat loading failed. Current level is null.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MapData mapData = BuildMapData();
|
||||||
|
if (!Scheduler._loadSession.StartLoading(Scheduler._currentLevel, mapData, out string errorMessage))
|
||||||
|
{
|
||||||
|
Scheduler.EnterFailureFallback($"Combat loading failed. {errorMessage}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||||
{
|
{
|
||||||
_ = elapseSeconds;
|
_ = elapseSeconds;
|
||||||
|
|
@ -20,6 +40,29 @@ namespace GeometryTD.CustomComponent
|
||||||
|
|
||||||
Scheduler.TryBeginNextPhase();
|
Scheduler.TryBeginNextPhase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MapData BuildMapData()
|
||||||
|
{
|
||||||
|
List<TowerStatsData> buildTowerStatsSnapshot = new();
|
||||||
|
for (int i = 0; i < Scheduler._combatInRunResourceManager.CurrentBuildTowerCount; i++)
|
||||||
|
{
|
||||||
|
if (Scheduler._combatInRunResourceManager.TryGetBuildTowerStats(i, out TowerStatsData stats) &&
|
||||||
|
stats != null)
|
||||||
|
{
|
||||||
|
buildTowerStatsSnapshot.Add(stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MapData(
|
||||||
|
entityId: 0,
|
||||||
|
typeId: 0,
|
||||||
|
levelId: Scheduler._currentLevel.Id,
|
||||||
|
position: Vector3.zero,
|
||||||
|
initialCoin: Scheduler._combatInRunResourceManager.CurrentCoin,
|
||||||
|
buildTowerStatsSnapshot: buildTowerStatsSnapshot,
|
||||||
|
tryConsumeCoin: Scheduler.TryConsumeCoin,
|
||||||
|
addCoin: Scheduler.AddCoin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
using GeometryTD.CustomUtility;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace GeometryTD.Entity.EntityData
|
namespace GeometryTD.Entity.EntityData
|
||||||
|
|
@ -7,6 +10,11 @@ namespace GeometryTD.Entity.EntityData
|
||||||
public class MapData : EntityDataBase
|
public class MapData : EntityDataBase
|
||||||
{
|
{
|
||||||
[SerializeField] private int _levelId = 0;
|
[SerializeField] private int _levelId = 0;
|
||||||
|
[SerializeField] private int _initialCoin = 0;
|
||||||
|
[SerializeField] private TowerStatsData[] _buildTowerStatsSnapshot = Array.Empty<TowerStatsData>();
|
||||||
|
|
||||||
|
[NonSerialized] private Func<int, bool> _tryConsumeCoin;
|
||||||
|
[NonSerialized] private Action<int> _addCoin;
|
||||||
|
|
||||||
public MapData(int entityId, int levelId, Vector3 position) : this(entityId, 0, levelId, position)
|
public MapData(int entityId, int levelId, Vector3 position) : this(entityId, 0, levelId, position)
|
||||||
{
|
{
|
||||||
|
|
@ -18,10 +26,110 @@ namespace GeometryTD.Entity.EntityData
|
||||||
Position = position;
|
Position = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MapData(
|
||||||
|
int entityId,
|
||||||
|
int typeId,
|
||||||
|
int levelId,
|
||||||
|
Vector3 position,
|
||||||
|
int initialCoin,
|
||||||
|
IReadOnlyList<TowerStatsData> buildTowerStatsSnapshot,
|
||||||
|
Func<int, bool> tryConsumeCoin,
|
||||||
|
Action<int> addCoin) : base(entityId, typeId)
|
||||||
|
{
|
||||||
|
_levelId = levelId;
|
||||||
|
Position = position;
|
||||||
|
_initialCoin = Mathf.Max(0, initialCoin);
|
||||||
|
SetBuildTowerStatsSnapshot(buildTowerStatsSnapshot);
|
||||||
|
_tryConsumeCoin = tryConsumeCoin;
|
||||||
|
_addCoin = addCoin;
|
||||||
|
}
|
||||||
|
|
||||||
public int LevelId
|
public int LevelId
|
||||||
{
|
{
|
||||||
get => _levelId;
|
get => _levelId;
|
||||||
set => _levelId = value;
|
set => _levelId = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int InitialCoin
|
||||||
|
{
|
||||||
|
get => _initialCoin;
|
||||||
|
set => _initialCoin = Mathf.Max(0, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CurrentBuildTowerCount => _buildTowerStatsSnapshot != null ? _buildTowerStatsSnapshot.Length : 0;
|
||||||
|
|
||||||
|
public void BindCombatCallbacks(Func<int, bool> tryConsumeCoin, Action<int> addCoin)
|
||||||
|
{
|
||||||
|
_tryConsumeCoin = tryConsumeCoin;
|
||||||
|
_addCoin = addCoin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryConsumeCoin(int coin)
|
||||||
|
{
|
||||||
|
int requiredCoin = Mathf.Max(0, coin);
|
||||||
|
if (requiredCoin <= 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _tryConsumeCoin != null && _tryConsumeCoin.Invoke(requiredCoin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddCoin(int coin)
|
||||||
|
{
|
||||||
|
int amount = Mathf.Max(0, coin);
|
||||||
|
if (amount <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_addCoin?.Invoke(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetBuildTowerStats(int buildIndex, out TowerStatsData stats)
|
||||||
|
{
|
||||||
|
stats = null;
|
||||||
|
if (_buildTowerStatsSnapshot == null || buildIndex < 0 || buildIndex >= _buildTowerStatsSnapshot.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TowerStatsData sourceStats = _buildTowerStatsSnapshot[buildIndex];
|
||||||
|
if (sourceStats == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stats = InventoryCloneUtility.CloneTowerStats(sourceStats);
|
||||||
|
return stats != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetBuildTowerStatsSnapshot(IReadOnlyList<TowerStatsData> buildTowerStatsSnapshot)
|
||||||
|
{
|
||||||
|
if (buildTowerStatsSnapshot == null || buildTowerStatsSnapshot.Count <= 0)
|
||||||
|
{
|
||||||
|
_buildTowerStatsSnapshot = Array.Empty<TowerStatsData>();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildTowerStatsSnapshot = new TowerStatsData[buildTowerStatsSnapshot.Count];
|
||||||
|
for (int i = 0; i < buildTowerStatsSnapshot.Count; i++)
|
||||||
|
{
|
||||||
|
_buildTowerStatsSnapshot[i] = InventoryCloneUtility.CloneTowerStats(buildTowerStatsSnapshot[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MapData CloneForEntity(int entityId, Vector3 position)
|
||||||
|
{
|
||||||
|
return new MapData(
|
||||||
|
entityId,
|
||||||
|
TypeId,
|
||||||
|
_levelId,
|
||||||
|
position,
|
||||||
|
_initialCoin,
|
||||||
|
_buildTowerStatsSnapshot,
|
||||||
|
_tryConsumeCoin,
|
||||||
|
_addCoin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GeometryTD.CustomComponent;
|
using GeometryTD.CustomComponent;
|
||||||
|
using GeometryTD.CustomEvent;
|
||||||
|
using GameFramework.Event;
|
||||||
using GeometryTD.Map;
|
using GeometryTD.Map;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
using GeometryTD.Entity.EntityData;
|
using GeometryTD.Entity.EntityData;
|
||||||
|
|
@ -37,6 +39,8 @@ namespace GeometryTD.Entity
|
||||||
private TowerSelectionPresenter _towerSelectionPresenter;
|
private TowerSelectionPresenter _towerSelectionPresenter;
|
||||||
|
|
||||||
private CombatSelectUseCaseConfigurator _combatSelectUseCaseConfigurator;
|
private CombatSelectUseCaseConfigurator _combatSelectUseCaseConfigurator;
|
||||||
|
private int _currentCoin;
|
||||||
|
private bool _isCoinEventSubscribed;
|
||||||
|
|
||||||
public IReadOnlyList<Vector3Int> PathCells => _mapTopologyService != null
|
public IReadOnlyList<Vector3Int> PathCells => _mapTopologyService != null
|
||||||
? _mapTopologyService.PathCells
|
? _mapTopologyService.PathCells
|
||||||
|
|
@ -117,6 +121,11 @@ namespace GeometryTD.Entity
|
||||||
{
|
{
|
||||||
Log.Warning("MapData is invalid for map entity '{0}'.", Id);
|
Log.Warning("MapData is invalid for map entity '{0}'.", Id);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_currentCoin = Mathf.Max(0, _mapData.InitialCoin);
|
||||||
|
SubscribeCombatEvents();
|
||||||
|
}
|
||||||
|
|
||||||
RefreshTiles();
|
RefreshTiles();
|
||||||
ConfigureCombatSelectUseCase();
|
ConfigureCombatSelectUseCase();
|
||||||
|
|
@ -125,6 +134,7 @@ namespace GeometryTD.Entity
|
||||||
|
|
||||||
protected override void OnHide(bool isShutdown, object userData)
|
protected override void OnHide(bool isShutdown, object userData)
|
||||||
{
|
{
|
||||||
|
UnsubscribeCombatEvents();
|
||||||
HideCombatSelectForm();
|
HideCombatSelectForm();
|
||||||
_towerPlacementService?.HideAndClearAllPlacedTowers();
|
_towerPlacementService?.HideAndClearAllPlacedTowers();
|
||||||
ClearSelectionState();
|
ClearSelectionState();
|
||||||
|
|
@ -301,8 +311,7 @@ namespace GeometryTD.Entity
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CombatNodeComponent combatNode = GameEntry.CombatNode;
|
if (_mapData == null || !_mapData.TryGetBuildTowerStats(buildIndex, out TowerStatsData buildTowerStats))
|
||||||
if (combatNode == null || !combatNode.TryGetBuildTowerStats(buildIndex, out TowerStatsData buildTowerStats))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -376,17 +385,49 @@ namespace GeometryTD.Entity
|
||||||
GameEntry.UIRouter.CloseUI(UIFormType.CombatSelectForm);
|
GameEntry.UIRouter.CloseUI(UIFormType.CombatSelectForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetCurrentBuildTowerCount()
|
private void SubscribeCombatEvents()
|
||||||
{
|
{
|
||||||
if (GameEntry.CombatNode == null)
|
if (_isCoinEventSubscribed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameEntry.Event.Subscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
|
||||||
|
_isCoinEventSubscribed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnsubscribeCombatEvents()
|
||||||
|
{
|
||||||
|
if (!_isCoinEventSubscribed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameEntry.Event.Unsubscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
|
||||||
|
_isCoinEventSubscribed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCombatCoinChanged(object sender, GameEventArgs e)
|
||||||
|
{
|
||||||
|
if (e is not CombatCoinChangedEventArgs args)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentCoin = Mathf.Max(0, args.CurrentCoin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetCurrentBuildTowerCount()
|
||||||
|
{
|
||||||
|
if (_mapData == null)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Mathf.Clamp(GameEntry.CombatNode.CurrentBuildTowerCount, 0, 4);
|
return Mathf.Clamp(_mapData.CurrentBuildTowerCount, 0, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryConsumeCoin(int cost)
|
private bool TryConsumeCoin(int cost)
|
||||||
{
|
{
|
||||||
int requiredCoin = Mathf.Max(0, cost);
|
int requiredCoin = Mathf.Max(0, cost);
|
||||||
if (requiredCoin <= 0)
|
if (requiredCoin <= 0)
|
||||||
|
|
@ -394,27 +435,28 @@ namespace GeometryTD.Entity
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GameEntry.CombatNode == null)
|
if (_mapData == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return GameEntry.CombatNode.TryConsumeCoin(requiredCoin);
|
return _mapData.TryConsumeCoin(requiredCoin);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetCurrentCoin()
|
private int GetCurrentCoin()
|
||||||
{
|
{
|
||||||
return GameEntry.CombatNode != null ? Mathf.Max(0, GameEntry.CombatNode.CurrentCoin) : 0;
|
return Mathf.Max(0, _currentCoin);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddCoin(int coin)
|
private void AddCoin(int coin)
|
||||||
{
|
{
|
||||||
int amount = Mathf.Max(0, coin);
|
int amount = Mathf.Max(0, coin);
|
||||||
if (amount <= 0)
|
if (amount <= 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
GameEntry.CombatNode?.AddCoin(amount);
|
|
||||||
|
_mapData?.AddCoin(amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue