CombatNode 相关逻辑补充
- CombatFinishForm:战斗节点的结算页面(杀死的敌人、获得的金币、获得的道具) - CombatSelectForm:战斗节点建造防御塔的选择 UI 以及对防御塔进行操作的 UI(只实现了建造逻辑,在 MapEntity 里做射线检测) - 构建了基本的防御塔三组件(ShooterMuzzleComp/BasicBearingComp/BasicBaseComp)和防御塔主控(DefenseTowerController)以及相应的实体类(DefenseTowerEntity)
This commit is contained in:
@@ -20,7 +20,7 @@ namespace GeometryTD.CustomComponent
|
||||
}
|
||||
|
||||
private EntityComponent _entity;
|
||||
private CombatInfoFormUseCase _useCase;
|
||||
private CombatInfoFormUseCase _combatInfoFormUseCase;
|
||||
private int _loadingMapEntityId;
|
||||
private int _loadedMapEntityId;
|
||||
private int? _loadingCombatInfoFormSerialId;
|
||||
@@ -63,7 +63,7 @@ namespace GeometryTD.CustomComponent
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,8 @@ namespace GeometryTD.CustomComponent
|
||||
return EventHandleStatus.Ignored;
|
||||
}
|
||||
|
||||
if (args.EntityLogicType != typeof(MapEntity) || args.Entity == null || args.Entity.Id != _loadingMapEntityId)
|
||||
if (args.EntityLogicType != typeof(MapEntity) || args.Entity == null ||
|
||||
args.Entity.Id != _loadingMapEntityId)
|
||||
{
|
||||
return EventHandleStatus.Ignored;
|
||||
}
|
||||
@@ -248,10 +249,10 @@ namespace GeometryTD.CustomComponent
|
||||
private bool TryOpenCombatInfoForm(out string errorMessage)
|
||||
{
|
||||
errorMessage = null;
|
||||
if (_useCase == null)
|
||||
if (_combatInfoFormUseCase == null)
|
||||
{
|
||||
_useCase = new CombatInfoFormUseCase();
|
||||
GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatInfoForm, _useCase);
|
||||
_combatInfoFormUseCase = new CombatInfoFormUseCase();
|
||||
GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatInfoForm, _combatInfoFormUseCase);
|
||||
}
|
||||
|
||||
int? serialId = GameEntry.UIRouter.OpenUI(UIFormType.CombatInfoForm);
|
||||
@@ -273,4 +274,4 @@ namespace GeometryTD.CustomComponent
|
||||
_isCombatInfoFormReady = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,11 +27,16 @@ namespace GeometryTD.CustomComponent
|
||||
|
||||
private bool _runtimeInitialized;
|
||||
private int _currentCoin;
|
||||
private int _currentGold;
|
||||
|
||||
public LevelThemeType CurrentThemeType { get; private set; }
|
||||
public DRLevel CurrentLevel { get; private set; }
|
||||
public int CurrentCoin => _currentCoin;
|
||||
public int CurrentGold => _currentGold;
|
||||
public bool CanEndCombat => _combatScheduler.CanEndCombat;
|
||||
public int LastDefeatedEnemyCount { get; private set; }
|
||||
public int LastGainedCoin { get; private set; }
|
||||
public int LastGainedGold { get; private set; }
|
||||
|
||||
public int CurrentLevelPhaseCount
|
||||
{
|
||||
@@ -66,6 +71,10 @@ namespace GeometryTD.CustomComponent
|
||||
CurrentThemeType = themeType;
|
||||
CurrentLevel = null;
|
||||
_currentCoin = 0;
|
||||
_currentGold = 0;
|
||||
LastDefeatedEnemyCount = 0;
|
||||
LastGainedCoin = 0;
|
||||
LastGainedGold = 0;
|
||||
_levelsById.Clear();
|
||||
_phasesByLevelId.Clear();
|
||||
_spawnEntriesByPhaseId.Clear();
|
||||
@@ -200,13 +209,21 @@ namespace GeometryTD.CustomComponent
|
||||
|
||||
CurrentLevel = selectedLevel;
|
||||
_currentCoin = Mathf.Max(0, selectedLevel.StartCoin);
|
||||
_currentGold = 0;
|
||||
LastDefeatedEnemyCount = 0;
|
||||
LastGainedCoin = 0;
|
||||
LastGainedGold = 0;
|
||||
_combatScheduler.Start(selectedLevel, phaseList, _selectedSpawnEntriesByPhaseId);
|
||||
GameEntry.Event.Fire(this, NodeEnterEventArgs.Create());
|
||||
}
|
||||
|
||||
public void EndCombat()
|
||||
{
|
||||
LastDefeatedEnemyCount = _combatScheduler.DefeatedEnemyCount;
|
||||
LastGainedCoin = _combatScheduler.GainedCoin;
|
||||
LastGainedGold = _combatScheduler.GainedGold;
|
||||
_currentCoin = 0;
|
||||
_currentGold = 0;
|
||||
GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create());
|
||||
}
|
||||
|
||||
@@ -229,9 +246,54 @@ namespace GeometryTD.CustomComponent
|
||||
{
|
||||
CurrentLevel = null;
|
||||
_currentCoin = 0;
|
||||
_currentGold = 0;
|
||||
LastDefeatedEnemyCount = 0;
|
||||
LastGainedCoin = 0;
|
||||
LastGainedGold = 0;
|
||||
ShutdownCombatRuntime();
|
||||
}
|
||||
|
||||
public void ApplyEnemyDropReward(int coin, int gold)
|
||||
{
|
||||
if (coin > 0)
|
||||
{
|
||||
_currentCoin += coin;
|
||||
}
|
||||
|
||||
if (gold > 0)
|
||||
{
|
||||
_currentGold += gold;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryConsumeCoin(int coin)
|
||||
{
|
||||
int requiredCoin = Mathf.Max(0, coin);
|
||||
if (requiredCoin <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_currentCoin < requiredCoin)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_currentCoin -= requiredCoin;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddCoin(int coin)
|
||||
{
|
||||
int gainCoin = Mathf.Max(0, coin);
|
||||
if (gainCoin <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentCoin += gainCoin;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
OnShutdown();
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Entity;
|
||||
using GeometryTD.UI;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.CustomComponent
|
||||
@@ -12,8 +15,9 @@ namespace GeometryTD.CustomComponent
|
||||
Idle = 0,
|
||||
WaitingForLoading = 1,
|
||||
RunningPhase = 2,
|
||||
Completed = 3,
|
||||
Failed = 4
|
||||
WaitingForFinishReturn = 3,
|
||||
Completed = 4,
|
||||
Failed = 5
|
||||
}
|
||||
|
||||
private readonly List<DRLevelPhase> _phaseBuffer = new();
|
||||
@@ -25,8 +29,11 @@ namespace GeometryTD.CustomComponent
|
||||
|
||||
private EntityComponent _entity;
|
||||
private DRLevel _currentLevel;
|
||||
private CombatFinishFormUseCase _combatFinishFormUseCase;
|
||||
private SchedulerState _state = SchedulerState.Idle;
|
||||
private bool _initialized;
|
||||
private int _gainedCoin;
|
||||
private int _gainedGold;
|
||||
|
||||
public bool IsRunning => _state == SchedulerState.WaitingForLoading || _state == SchedulerState.RunningPhase;
|
||||
public bool IsCompleted => _state == SchedulerState.Completed;
|
||||
@@ -35,6 +42,9 @@ namespace GeometryTD.CustomComponent
|
||||
public MapEntity CurrentMap => _loadSession.CurrentMap;
|
||||
public int DisplayPhaseIndex => _phaseLoopRuntime.DisplayPhaseIndex;
|
||||
public bool CanEndCombat => _phaseLoopRuntime.CanEndCombat;
|
||||
public int DefeatedEnemyCount => _enemyManager.DefeatedEnemyCount;
|
||||
public int GainedCoin => _gainedCoin;
|
||||
public int GainedGold => _gainedGold;
|
||||
|
||||
public void OnInit()
|
||||
{
|
||||
@@ -50,6 +60,7 @@ namespace GeometryTD.CustomComponent
|
||||
OnCloseUIFormComplete);
|
||||
_enemyManager.OnInit(this);
|
||||
_loadSession.OnInit(_entity);
|
||||
EnsureCombatFinishFormUseCaseBound();
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@@ -75,9 +86,13 @@ namespace GeometryTD.CustomComponent
|
||||
return;
|
||||
}
|
||||
|
||||
CleanupCombatEntities();
|
||||
CleanupAllCombatEntities();
|
||||
CloseCombatFinishForm();
|
||||
_enemyManager.EndPhase();
|
||||
_enemyManager.ResetCombatStats();
|
||||
ResetRuntime();
|
||||
_gainedCoin = 0;
|
||||
_gainedGold = 0;
|
||||
|
||||
_currentLevel = level;
|
||||
for (int i = 0; i < phases.Count; i++)
|
||||
@@ -113,7 +128,8 @@ namespace GeometryTD.CustomComponent
|
||||
}
|
||||
|
||||
_state = SchedulerState.WaitingForLoading;
|
||||
Log.Info("CombatScheduler started. Level={0}, PhaseCount={1}.", _currentLevel.Id, _phaseLoopRuntime.PhaseCount);
|
||||
Log.Info("CombatScheduler started. Level={0}, PhaseCount={1}.", _currentLevel.Id,
|
||||
_phaseLoopRuntime.PhaseCount);
|
||||
}
|
||||
|
||||
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
@@ -143,17 +159,20 @@ namespace GeometryTD.CustomComponent
|
||||
return;
|
||||
}
|
||||
|
||||
CleanupCombatEntities();
|
||||
CleanupAllCombatEntities();
|
||||
CloseCombatFinishForm();
|
||||
_enemyManager.OnDestroy();
|
||||
ResetRuntime();
|
||||
_eventBridge.Unbind();
|
||||
_combatFinishFormUseCase = null;
|
||||
|
||||
_entity = null;
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
public bool TryEndCombatByPlayer()
|
||||
{
|
||||
if (_state == SchedulerState.Completed || _state == SchedulerState.Failed || _state == SchedulerState.Idle)
|
||||
if (_state != SchedulerState.RunningPhase)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -163,7 +182,7 @@ namespace GeometryTD.CustomComponent
|
||||
return false;
|
||||
}
|
||||
|
||||
FinishCombat("Combat ended by player.");
|
||||
EnterFinishFlow("Combat ended by player.");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -185,7 +204,8 @@ namespace GeometryTD.CustomComponent
|
||||
_phaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
|
||||
_enemyManager.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
if (!_phaseLoopRuntime.ShouldEndCurrentPhase(_enemyManager.IsPhaseSpawnCompleted, _enemyManager.AliveEnemyCount))
|
||||
if (!_phaseLoopRuntime.ShouldEndCurrentPhase(_enemyManager.IsPhaseSpawnCompleted,
|
||||
_enemyManager.AliveEnemyCount))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -209,7 +229,7 @@ namespace GeometryTD.CustomComponent
|
||||
{
|
||||
if (!_phaseLoopRuntime.TryEnterNextPhase(out DRLevelPhase nextPhase))
|
||||
{
|
||||
FinishCombat("Combat ended after loop completion.");
|
||||
EnterFinishFlow("Combat ended after loop completion.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -227,17 +247,21 @@ namespace GeometryTD.CustomComponent
|
||||
spawnEntries != null ? spawnEntries.Count : 0);
|
||||
}
|
||||
|
||||
private void FinishCombat(string reason)
|
||||
private void EnterFinishFlow(string reason)
|
||||
{
|
||||
_state = SchedulerState.Completed;
|
||||
CleanupCombatEntities();
|
||||
int defeatedEnemyCount = _enemyManager.DefeatedEnemyCount;
|
||||
|
||||
// Step 1: stop runtime and clear enemy entities only.
|
||||
_enemyManager.EndPhase();
|
||||
_state = SchedulerState.WaitingForFinishReturn;
|
||||
_enemyManager.CleanupTrackedEnemies();
|
||||
|
||||
Log.Info(
|
||||
"CombatScheduler level completed. Level={0}. Reason={1}",
|
||||
"CombatScheduler entered finish flow. Level={0}. Reason={1}",
|
||||
_currentLevel != null ? _currentLevel.Id : 0,
|
||||
reason);
|
||||
GameEntry.CombatNode.EndCombat();
|
||||
|
||||
OpenCombatFinishForm(defeatedEnemyCount, _gainedGold);
|
||||
}
|
||||
|
||||
private void ResetRuntime()
|
||||
@@ -250,12 +274,57 @@ namespace GeometryTD.CustomComponent
|
||||
_state = SchedulerState.Idle;
|
||||
}
|
||||
|
||||
private void CleanupCombatEntities()
|
||||
private void CleanupAllCombatEntities()
|
||||
{
|
||||
_loadSession.Cleanup();
|
||||
_enemyManager.CleanupTrackedEnemies();
|
||||
}
|
||||
|
||||
private void EnsureCombatFinishFormUseCaseBound()
|
||||
{
|
||||
_combatFinishFormUseCase ??= new CombatFinishFormUseCase(this);
|
||||
|
||||
GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatFinishForm, _combatFinishFormUseCase);
|
||||
}
|
||||
|
||||
private void OpenCombatFinishForm(int defeatedEnemyCount, int gainedGold)
|
||||
{
|
||||
EnsureCombatFinishFormUseCaseBound();
|
||||
_combatFinishFormUseCase.SetSummary(
|
||||
defeatedEnemyCount,
|
||||
gainedGold);
|
||||
GameEntry.UIRouter.OpenUI(UIFormType.CombatFinishForm);
|
||||
}
|
||||
|
||||
public void OnEnemyDefeatedRewardResolved(int gainedCoin, int gainedGold)
|
||||
{
|
||||
int coin = Mathf.Max(0, gainedCoin);
|
||||
int gold = Mathf.Max(0, gainedGold);
|
||||
_gainedCoin += coin;
|
||||
_gainedGold += gold;
|
||||
GameEntry.CombatNode?.ApplyEnemyDropReward(coin, gold);
|
||||
}
|
||||
|
||||
private void CloseCombatFinishForm()
|
||||
{
|
||||
GameEntry.UIRouter.CloseUI(UIFormType.CombatFinishForm);
|
||||
}
|
||||
|
||||
public bool OnCombatFinishReturnRequested()
|
||||
{
|
||||
if (_state != SchedulerState.WaitingForFinishReturn)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: clear remaining map/UI resources and close finish form.
|
||||
_loadSession.Cleanup();
|
||||
CloseCombatFinishForm();
|
||||
_state = SchedulerState.Completed;
|
||||
GameEntry.CombatNode.EndCombat();
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
private void OnShowEntitySuccess(ShowEntitySuccessEventArgs args)
|
||||
@@ -287,7 +356,8 @@ namespace GeometryTD.CustomComponent
|
||||
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
||||
{
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Error("CombatScheduler failed. LevelId={0}, {1}", _currentLevel != null ? _currentLevel.Id : 0, errorMessage);
|
||||
Log.Error("CombatScheduler failed. LevelId={0}, {1}", _currentLevel != null ? _currentLevel.Id : 0,
|
||||
errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace GeometryTD.CustomComponent
|
||||
private readonly List<SpawnEntryRuntime> _spawnRuntimes = new List<SpawnEntryRuntime>();
|
||||
private readonly HashSet<int> _trackedEnemyEntityIds = new HashSet<int>();
|
||||
private readonly List<int> _trackedEnemyIdBuffer = new List<int>();
|
||||
private readonly Dictionary<int, DREnemy> _trackedEnemyConfigByEntityId = new Dictionary<int, DREnemy>();
|
||||
|
||||
private CombatScheduler _combatScheduler;
|
||||
private EntityComponent _entity;
|
||||
@@ -38,6 +39,7 @@ namespace GeometryTD.CustomComponent
|
||||
|
||||
private int _spawnEnemyMaxCount = 5000;
|
||||
private int _currentEnemyCount;
|
||||
private int _defeatedEnemyCount;
|
||||
private int _nextSpawnerIndex;
|
||||
private int _currentMapEntityId;
|
||||
private bool _initialized;
|
||||
@@ -47,6 +49,7 @@ namespace GeometryTD.CustomComponent
|
||||
private bool _isPhaseRunning;
|
||||
|
||||
public int AliveEnemyCount => _currentEnemyCount;
|
||||
public int DefeatedEnemyCount => _defeatedEnemyCount;
|
||||
public bool IsPhaseSpawnCompleted { get; private set; } = true;
|
||||
public bool IsPhaseRunning => _isPhaseRunning;
|
||||
|
||||
@@ -61,6 +64,7 @@ namespace GeometryTD.CustomComponent
|
||||
_entity = GameEntry.Entity;
|
||||
_drEnemy = GameEntry.DataTable.GetDataTable<DREnemy>();
|
||||
_currentEnemyCount = 0;
|
||||
_defeatedEnemyCount = 0;
|
||||
_nextSpawnerIndex = 0;
|
||||
_currentMapEntityId = 0;
|
||||
_enemyConfigMissingLogged = false;
|
||||
@@ -70,6 +74,7 @@ namespace GeometryTD.CustomComponent
|
||||
_spawnRuntimes.Clear();
|
||||
_trackedEnemyEntityIds.Clear();
|
||||
_trackedEnemyIdBuffer.Clear();
|
||||
_trackedEnemyConfigByEntityId.Clear();
|
||||
_phaseElapsed = 0f;
|
||||
_isPhaseRunning = false;
|
||||
IsPhaseSpawnCompleted = true;
|
||||
@@ -149,13 +154,20 @@ namespace GeometryTD.CustomComponent
|
||||
_pathBuffer.Clear();
|
||||
_trackedEnemyEntityIds.Clear();
|
||||
_trackedEnemyIdBuffer.Clear();
|
||||
_trackedEnemyConfigByEntityId.Clear();
|
||||
_currentEnemyCount = 0;
|
||||
_defeatedEnemyCount = 0;
|
||||
_currentMapEntityId = 0;
|
||||
_nextSpawnerIndex = 0;
|
||||
_combatScheduler = null;
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
public void ResetCombatStats()
|
||||
{
|
||||
_defeatedEnemyCount = 0;
|
||||
}
|
||||
|
||||
public void CleanupTrackedEnemies()
|
||||
{
|
||||
if (_trackedEnemyEntityIds.Count <= 0)
|
||||
@@ -171,6 +183,7 @@ namespace GeometryTD.CustomComponent
|
||||
}
|
||||
|
||||
_trackedEnemyEntityIds.Clear();
|
||||
_trackedEnemyConfigByEntityId.Clear();
|
||||
_currentEnemyCount = 0;
|
||||
|
||||
if (_entity == null)
|
||||
@@ -352,6 +365,7 @@ namespace GeometryTD.CustomComponent
|
||||
|
||||
int enemyEntityId = _entity.GenerateSerialId();
|
||||
_trackedEnemyEntityIds.Add(enemyEntityId);
|
||||
_trackedEnemyConfigByEntityId[enemyEntityId] = enemyConfig;
|
||||
EnemyData enemyData = new EnemyData(
|
||||
enemyEntityId,
|
||||
enemyConfig.EntityId,
|
||||
@@ -497,6 +511,7 @@ namespace GeometryTD.CustomComponent
|
||||
}
|
||||
|
||||
_trackedEnemyEntityIds.Remove(ne.EntityId);
|
||||
_trackedEnemyConfigByEntityId.Remove(ne.EntityId);
|
||||
}
|
||||
|
||||
private void OnHideEntityComplete(object sender, GameEventArgs e)
|
||||
@@ -508,10 +523,33 @@ namespace GeometryTD.CustomComponent
|
||||
|
||||
if (!_trackedEnemyEntityIds.Remove(ne.EntityId))
|
||||
{
|
||||
_trackedEnemyConfigByEntityId.Remove(ne.EntityId);
|
||||
return;
|
||||
}
|
||||
|
||||
_currentEnemyCount = Mathf.Max(0, _currentEnemyCount - 1);
|
||||
bool wasKilled = EnemyEntity.TryConsumeKilledFlag(ne.EntityId);
|
||||
if (_combatScheduler != null && _combatScheduler.IsRunning && wasKilled)
|
||||
{
|
||||
_defeatedEnemyCount++;
|
||||
int droppedCoin = 0;
|
||||
int droppedGold = 0;
|
||||
if (_trackedEnemyConfigByEntityId.TryGetValue(ne.EntityId, out DREnemy enemyConfig) && enemyConfig != null)
|
||||
{
|
||||
droppedCoin = Mathf.Max(0, enemyConfig.DropCoin);
|
||||
float dropRate = enemyConfig.DropPercent > 1f
|
||||
? Mathf.Clamp01(enemyConfig.DropPercent * 0.01f)
|
||||
: Mathf.Clamp01(enemyConfig.DropPercent);
|
||||
if (enemyConfig.DropGold > 0 && dropRate > 0f && Random.value <= dropRate)
|
||||
{
|
||||
droppedGold = Mathf.Max(0, enemyConfig.DropGold);
|
||||
}
|
||||
}
|
||||
|
||||
_combatScheduler.OnEnemyDefeatedRewardResolved(droppedCoin, droppedGold);
|
||||
}
|
||||
|
||||
_trackedEnemyConfigByEntityId.Remove(ne.EntityId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user