refactor 7:

- 拆分 CombatScheduler,将公用方法与字段拆分为:
    - FlowCoordinator
    - RuntimeContext
- 地图侧现在收口为“MapData 初始快照 + CombatCoinChangedEventArgs 同步 + 独立命令桥接”
This commit is contained in:
2026-03-07 19:42:17 +08:00
parent eb818e6295
commit b38088c3ea
25 changed files with 1031 additions and 786 deletions
@@ -2,33 +2,33 @@ using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatFailedState : CombatStateBase
{
private sealed class CombatFailedState : CombatStateBase
private readonly string _errorMessage;
public CombatFailedState(
CombatSchedulerRuntimeContext context,
CombatSchedulerFlowCoordinator flow,
string errorMessage) : base(context, flow)
{
private readonly string _errorMessage;
_errorMessage = errorMessage;
}
public CombatFailedState(CombatScheduler scheduler, string errorMessage) : base(scheduler)
{
_errorMessage = errorMessage;
}
public override void OnEnter()
{
Log.Error(
"CombatScheduler failed. LevelId={0}, {1}",
Context.CurrentLevel != null ? Context.CurrentLevel.Id : 0,
_errorMessage);
Context.EnemyManager.EndPhase();
Flow.CloseCombatFinishForm();
Flow.CloseRewardSelectForm();
Flow.OpenCombatFailureDialog(_errorMessage);
}
public override void OnEnter()
{
Log.Error(
"CombatScheduler failed. LevelId={0}, {1}",
Scheduler._currentLevel != null ? Scheduler._currentLevel.Id : 0,
_errorMessage);
Scheduler._enemyManager.EndPhase();
Scheduler.CloseCombatFinishForm();
Scheduler.CloseRewardSelectForm();
Scheduler.OpenCombatFailureDialog(_errorMessage);
}
public override void OnExit()
{
Scheduler.CloseDialogForm();
}
public override void OnExit()
{
Flow.CloseDialogForm();
}
}
}
@@ -1,28 +1,26 @@
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatFinishFormState : CombatStateBase
{
private sealed class CombatFinishFormState : CombatStateBase
public CombatFinishFormState(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
: base(context, flow)
{
public CombatFinishFormState(CombatScheduler scheduler) : base(scheduler)
}
public override void OnEnter()
{
if (Context.SettlementContext == null)
{
Flow.EnterFailureFallback("Combat finish form failed. Settlement context is missing.");
return;
}
public override void OnEnter()
{
if (Scheduler._settlementContext == null)
{
Scheduler.EnterFailureFallback("Combat finish form failed. Settlement context is missing.");
return;
}
Scheduler._settlementFlowService.CommitSettlementInventory(Scheduler._settlementContext);
Scheduler.EnsureCombatFinishFormUseCaseBound();
Scheduler._settlementFlowService.OpenCombatFinishForm(
Scheduler._settlementContext,
Scheduler._combatFinishFormUseCase);
Scheduler.ChangeState(new CombatWaitingForReturnState(Scheduler));
}
Context.SettlementFlowService.CommitSettlementInventory(Context.SettlementContext);
Flow.EnsureCombatFinishFormUseCaseBound();
Context.SettlementFlowService.OpenCombatFinishForm(
Context.SettlementContext,
Context.CombatFinishFormUseCase);
Flow.Scheduler.ChangeState(new CombatWaitingForReturnState(Context, Flow));
}
}
}
@@ -6,70 +6,67 @@ using UnityEngine;
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatLoadingState : CombatStateBase
{
private sealed class CombatLoadingState : CombatStateBase
public CombatLoadingState(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
: base(context, flow)
{
public CombatLoadingState(CombatScheduler scheduler) : base(scheduler)
}
public override void OnEnter()
{
if (Context.CurrentLevel == null)
{
Flow.EnterFailureFallback("Combat loading failed. Current level is null.");
return;
}
public override void OnEnter()
MapEntityLoadContext mapLoadContext = BuildMapLoadContext();
if (!Context.LoadSession.StartLoading(Context.CurrentLevel, mapLoadContext, Flow.Scheduler, out string errorMessage))
{
if (Scheduler._currentLevel == null)
{
Scheduler.EnterFailureFallback("Combat loading failed. Current level is null.");
return;
}
Flow.EnterFailureFallback($"Combat loading failed. {errorMessage}");
}
}
MapData mapData = BuildMapData();
if (!Scheduler._loadSession.StartLoading(Scheduler._currentLevel, mapData, Scheduler, out string errorMessage))
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_ = elapseSeconds;
_ = realElapseSeconds;
if (!Context.LoadSession.IsReady)
{
return;
}
Flow.TryBeginNextPhase();
}
private MapEntityLoadContext BuildMapLoadContext()
{
List<TowerStatsData> buildTowerStatsSnapshot = new();
for (int i = 0; i < Context.CombatInRunResourceManager.CurrentBuildTowerCount; i++)
{
if (Context.CombatInRunResourceManager.TryGetBuildTowerStats(i, out TowerStatsData stats) &&
stats != null)
{
Scheduler.EnterFailureFallback($"Combat loading failed. {errorMessage}");
buildTowerStatsSnapshot.Add(stats);
}
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_ = elapseSeconds;
_ = realElapseSeconds;
if (!Scheduler._loadSession.IsReady)
{
return;
}
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,
inventorySnapshot: GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetInventorySnapshot()
: null,
participantTowerSnapshot: GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetParticipantTowerSnapshot()
: null,
tryConsumeCoin: Scheduler.TryConsumeCoin,
addCoin: Scheduler.AddCoin);
}
MapData mapData = new MapData(
entityId: 0,
typeId: 0,
levelId: Context.CurrentLevel.Id,
position: Vector3.zero,
initialCoin: Context.CombatInRunResourceManager.CurrentCoin,
buildTowerStatsSnapshot: buildTowerStatsSnapshot,
inventorySnapshot: GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetInventorySnapshot()
: null,
participantTowerSnapshot: GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetParticipantTowerSnapshot()
: null);
return new MapEntityLoadContext(mapData, Flow.Scheduler.TryConsumeCoin, Flow.Scheduler.AddCoin);
}
}
}
@@ -1,39 +1,37 @@
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatRewardSelectionState : CombatStateBase
{
private sealed class CombatRewardSelectionState : CombatStateBase
public CombatRewardSelectionState(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
: base(context, flow)
{
public CombatRewardSelectionState(CombatScheduler scheduler) : base(scheduler)
}
public override void OnEnter()
{
if (Context.SettlementContext == null)
{
Flow.EnterFailureFallback("Combat reward selection failed. Settlement context is missing.");
return;
}
public override void OnEnter()
Flow.EnsureRewardSelectFormUseCaseBound();
if (!Context.SettlementFlowService.TryPrepareRewardSelection(
Context.SettlementContext,
Context.CombatInRunResourceManager,
Context.PhaseLoopRuntime.DisplayPhaseIndex,
Flow.ResolveCurrentThemeType(),
Context.RewardSelectFormUseCase,
Flow.OnFullBaseHpRewardSelected,
Flow.OnFullBaseHpRewardGiveUp))
{
if (Scheduler._settlementContext == null)
{
Scheduler.EnterFailureFallback("Combat reward selection failed. Settlement context is missing.");
return;
}
Scheduler.EnsureRewardSelectFormUseCaseBound();
if (!Scheduler._settlementFlowService.TryPrepareRewardSelection(
Scheduler._settlementContext,
Scheduler._combatInRunResourceManager,
Scheduler._phaseLoopRuntime.DisplayPhaseIndex,
Scheduler.ResolveCurrentThemeType(),
Scheduler._rewardSelectFormUseCase,
Scheduler.OnFullBaseHpRewardSelected,
Scheduler.OnFullBaseHpRewardGiveUp))
{
Scheduler.ChangeState(new CombatFinishFormState(Scheduler));
}
Flow.Scheduler.ChangeState(new CombatFinishFormState(Context, Flow));
}
}
public override void OnExit()
{
Scheduler.CloseRewardSelectForm();
}
public override void OnExit()
{
Flow.CloseRewardSelectForm();
}
}
}
@@ -5,72 +5,70 @@ using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatRunningPhaseState : CombatStateBase
{
private sealed class CombatRunningPhaseState : CombatStateBase
private readonly DRLevelPhase _phase;
private readonly IReadOnlyList<DRLevelSpawnEntry> _spawnEntries;
public CombatRunningPhaseState(
CombatSchedulerRuntimeContext context,
CombatSchedulerFlowCoordinator flow,
DRLevelPhase phase,
IReadOnlyList<DRLevelSpawnEntry> spawnEntries) : base(context, flow)
{
private readonly DRLevelPhase _phase;
private readonly IReadOnlyList<DRLevelSpawnEntry> _spawnEntries;
_phase = phase;
_spawnEntries = spawnEntries;
}
public CombatRunningPhaseState(
CombatScheduler scheduler,
DRLevelPhase phase,
IReadOnlyList<DRLevelSpawnEntry> spawnEntries) : base(scheduler)
public override void OnEnter()
{
Context.EnemyManager.BeginPhase(_phase, _spawnEntries);
GameEntry.Event.Fire(
Flow,
CombatProcessEventArgs.Create(
Context.PhaseLoopRuntime.DisplayPhaseIndex,
Context.PhaseLoopRuntime.PhaseCount));
GameEntry.Event.Fire(
Flow,
CombatEnemyHpRateChangedEventArgs.Create(
Flow.ResolveEnemyHpRateMultiplier(
Context.PhaseLoopRuntime.DisplayPhaseIndex,
Context.PhaseLoopRuntime.PhaseCount)));
if (!Context.NodeEnterFired)
{
_phase = phase;
_spawnEntries = spawnEntries;
Context.NodeEnterFired = true;
GameEntry.Event.Fire(Flow, NodeEnterEventArgs.Create());
}
public override void OnEnter()
Log.Info(
"CombatScheduler phase started. Level={0}, Phase={1}, EndType={2}, Entries={3}.",
Context.CurrentLevel != null ? Context.CurrentLevel.Id : 0,
Context.PhaseLoopRuntime.DisplayPhaseIndex,
_phase.EndType,
_spawnEntries != null ? _spawnEntries.Count : 0);
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
if (Context.PhaseLoopRuntime.CurrentPhase == null)
{
Scheduler._enemyManager.BeginPhase(_phase, _spawnEntries);
GameEntry.Event.Fire(
Scheduler,
CombatProcessEventArgs.Create(
Scheduler._phaseLoopRuntime.DisplayPhaseIndex,
Scheduler._phaseLoopRuntime.PhaseCount));
GameEntry.Event.Fire(
Scheduler,
CombatEnemyHpRateChangedEventArgs.Create(
ResolveEnemyHpRateMultiplier(
Scheduler._phaseLoopRuntime.DisplayPhaseIndex,
Scheduler._phaseLoopRuntime.PhaseCount)));
if (!Scheduler._nodeEnterFired)
{
Scheduler._nodeEnterFired = true;
GameEntry.Event.Fire(Scheduler, NodeEnterEventArgs.Create());
}
Log.Info(
"CombatScheduler phase started. Level={0}, Phase={1}, EndType={2}, Entries={3}.",
Scheduler._currentLevel != null ? Scheduler._currentLevel.Id : 0,
Scheduler._phaseLoopRuntime.DisplayPhaseIndex,
_phase.EndType,
_spawnEntries != null ? _spawnEntries.Count : 0);
Flow.EnterFailureFallback("CombatScheduler update failed. Current phase is null.");
return;
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
Context.PhaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
Context.EnemyManager.OnUpdate(elapseSeconds, realElapseSeconds);
if (Flow.ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory))
{
if (Scheduler._phaseLoopRuntime.CurrentPhase == null)
{
Scheduler.EnterFailureFallback("CombatScheduler update failed. Current phase is null.");
return;
}
Flow.Scheduler.ChangeState(new CombatSettlementState(Context, Flow, reason, isVictory));
return;
}
Scheduler._phaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
Scheduler._enemyManager.OnUpdate(elapseSeconds, realElapseSeconds);
if (Scheduler.ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory))
{
Scheduler.ChangeState(new CombatSettlementState(Scheduler, reason, isVictory));
return;
}
if (Scheduler._enemyManager.IsPhaseSpawnCompleted)
{
Scheduler.EnterWaitingForPhaseEnd();
}
if (Context.EnemyManager.IsPhaseSpawnCompleted)
{
Flow.EnterWaitingForPhaseEnd();
}
}
}
@@ -1,37 +1,38 @@
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatSettlementState : CombatStateBase
{
private sealed class CombatSettlementState : CombatStateBase
private readonly string _reason;
private readonly bool _isVictory;
public CombatSettlementState(
CombatSchedulerRuntimeContext context,
CombatSchedulerFlowCoordinator flow,
string reason,
bool isVictory) : base(context, flow)
{
private readonly string _reason;
private readonly bool _isVictory;
_reason = reason;
_isVictory = isVictory;
}
public CombatSettlementState(CombatScheduler scheduler, string reason, bool isVictory) : base(scheduler)
public override void OnEnter()
{
Context.EnemyManager.EndPhase();
Context.EnemyManager.CleanupTrackedEnemies();
Context.IsFinishAsVictory = _isVictory;
Context.SettlementContext = Context.SettlementFlowService.BuildSettlementContext(
_reason,
_isVictory,
Context.CurrentLevel,
Context.EnemyManager.DefeatedEnemyCount,
Context.CombatInRunResourceManager);
if (Context.SettlementContext.ShouldOpenRewardSelection)
{
_reason = reason;
_isVictory = isVictory;
Flow.Scheduler.ChangeState(new CombatRewardSelectionState(Context, Flow));
return;
}
public override void OnEnter()
{
Scheduler._enemyManager.EndPhase();
Scheduler._enemyManager.CleanupTrackedEnemies();
Scheduler._isFinishAsVictory = _isVictory;
Scheduler._settlementContext = Scheduler._settlementFlowService.BuildSettlementContext(
_reason,
_isVictory,
Scheduler._currentLevel,
Scheduler._enemyManager.DefeatedEnemyCount,
Scheduler._combatInRunResourceManager);
if (Scheduler._settlementContext.ShouldOpenRewardSelection)
{
Scheduler.ChangeState(new CombatRewardSelectionState(Scheduler));
return;
}
Scheduler.ChangeState(new CombatFinishFormState(Scheduler));
}
Flow.Scheduler.ChangeState(new CombatFinishFormState(Context, Flow));
}
}
}
@@ -1,35 +1,34 @@
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal abstract class CombatStateBase
{
private abstract class CombatStateBase
protected CombatSchedulerRuntimeContext Context { get; }
protected CombatSchedulerFlowCoordinator Flow { get; }
protected CombatStateBase(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
{
protected CombatScheduler Scheduler { get; }
Context = context;
Flow = flow;
}
protected CombatStateBase(CombatScheduler scheduler)
{
Scheduler = scheduler;
}
public virtual void OnInit()
{
}
public virtual void OnInit()
{
}
public virtual void OnEnter()
{
}
public virtual void OnEnter()
{
}
public virtual void OnExit()
{
}
public virtual void OnExit()
{
}
public virtual void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
}
public virtual void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
}
public virtual void OnDestroy()
{
}
public virtual void OnDestroy()
{
}
}
}
@@ -1,46 +1,46 @@
using GeometryTD.DataTable;
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatWaitingForPhaseEndState : CombatStateBase
{
private sealed class CombatWaitingForPhaseEndState : CombatStateBase
public CombatWaitingForPhaseEndState(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
: base(context, flow)
{
public CombatWaitingForPhaseEndState(CombatScheduler scheduler) : base(scheduler)
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_ = realElapseSeconds;
DRLevelPhase currentPhase = Context.PhaseLoopRuntime.CurrentPhase;
if (currentPhase == null)
{
Flow.EnterFailureFallback("CombatScheduler waiting phase failed. Current phase is null.");
return;
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
Context.PhaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
if (Flow.ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory))
{
_ = realElapseSeconds;
var currentPhase = Scheduler._phaseLoopRuntime.CurrentPhase;
if (currentPhase == null)
{
Scheduler.EnterFailureFallback("CombatScheduler waiting phase failed. Current phase is null.");
return;
}
Scheduler._phaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
if (Scheduler.ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory))
{
Scheduler.ChangeState(new CombatSettlementState(Scheduler, reason, isVictory));
return;
}
PhaseEndConditionContext context = new(
currentPhase,
Scheduler._phaseLoopRuntime.CurrentPhaseElapsed,
Scheduler._enemyManager.IsPhaseSpawnCompleted,
Scheduler._enemyManager.AliveEnemyCount,
Scheduler._enemyManager.HasAliveBoss);
IPhaseEndCondition endCondition = PhaseEndConditionFactory.Create(currentPhase.EndType);
if (!endCondition.ShouldExit(context))
{
return;
}
Scheduler.CompleteCurrentPhase();
Flow.Scheduler.ChangeState(new CombatSettlementState(Context, Flow, reason, isVictory));
return;
}
PhaseEndConditionContext conditionContext = new(
currentPhase,
Context.PhaseLoopRuntime.CurrentPhaseElapsed,
Context.EnemyManager.IsPhaseSpawnCompleted,
Context.EnemyManager.AliveEnemyCount,
Context.EnemyManager.HasAliveBoss);
IPhaseEndCondition endCondition = PhaseEndConditionFactory.Create(currentPhase.EndType);
if (!endCondition.ShouldExit(conditionContext))
{
return;
}
Flow.CompleteCurrentPhase();
}
}
}
}
@@ -1,35 +1,33 @@
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatWaitingForReturnState : CombatStateBase
{
private sealed class CombatWaitingForReturnState : CombatStateBase
private bool _returnRequested;
public CombatWaitingForReturnState(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
: base(context, flow)
{
private bool _returnRequested;
}
public CombatWaitingForReturnState(CombatScheduler scheduler) : base(scheduler)
public void RequestReturn()
{
_returnRequested = true;
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_ = elapseSeconds;
_ = realElapseSeconds;
if (!_returnRequested)
{
return;
}
public void RequestReturn()
{
_returnRequested = true;
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_ = elapseSeconds;
_ = realElapseSeconds;
if (!_returnRequested)
{
return;
}
Scheduler._loadSession.Cleanup();
Scheduler.CloseCombatFinishForm();
Scheduler.CloseRewardSelectForm();
Scheduler.CompleteNormalCombatAndNotify(Scheduler._isFinishAsVictory);
}
Context.LoadSession.Cleanup();
Flow.CloseCombatFinishForm();
Flow.CloseRewardSelectForm();
Flow.CompleteNormalCombatAndNotify(Context.IsFinishAsVictory);
}
}
}