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:
2026-03-07 15:12:18 +08:00
parent ccb4738b96
commit ca7b2f2dca
5 changed files with 213 additions and 22 deletions
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using GeometryTD.CustomComponent;
using GeometryTD.CustomEvent;
using GameFramework.Event;
using GeometryTD.Map;
using GeometryTD.Definition;
using GeometryTD.Entity.EntityData;
@@ -37,6 +39,8 @@ namespace GeometryTD.Entity
private TowerSelectionPresenter _towerSelectionPresenter;
private CombatSelectUseCaseConfigurator _combatSelectUseCaseConfigurator;
private int _currentCoin;
private bool _isCoinEventSubscribed;
public IReadOnlyList<Vector3Int> PathCells => _mapTopologyService != null
? _mapTopologyService.PathCells
@@ -117,6 +121,11 @@ namespace GeometryTD.Entity
{
Log.Warning("MapData is invalid for map entity '{0}'.", Id);
}
else
{
_currentCoin = Mathf.Max(0, _mapData.InitialCoin);
SubscribeCombatEvents();
}
RefreshTiles();
ConfigureCombatSelectUseCase();
@@ -125,6 +134,7 @@ namespace GeometryTD.Entity
protected override void OnHide(bool isShutdown, object userData)
{
UnsubscribeCombatEvents();
HideCombatSelectForm();
_towerPlacementService?.HideAndClearAllPlacedTowers();
ClearSelectionState();
@@ -301,8 +311,7 @@ namespace GeometryTD.Entity
return false;
}
CombatNodeComponent combatNode = GameEntry.CombatNode;
if (combatNode == null || !combatNode.TryGetBuildTowerStats(buildIndex, out TowerStatsData buildTowerStats))
if (_mapData == null || !_mapData.TryGetBuildTowerStats(buildIndex, out TowerStatsData buildTowerStats))
{
return false;
}
@@ -376,17 +385,49 @@ namespace GeometryTD.Entity
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 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);
if (requiredCoin <= 0)
@@ -394,27 +435,28 @@ namespace GeometryTD.Entity
return true;
}
if (GameEntry.CombatNode == null)
if (_mapData == null)
{
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);
if (amount <= 0)
{
return;
}
GameEntry.CombatNode?.AddCoin(amount);
_mapData?.AddCoin(amount);
}
}
}