CombatNodeComponent 战斗节点功能补全
- 失败逻辑补全:添加基地血量,当血量归0时强制结束关卡 - `CombatInfoForm` 刷新逻辑补全:通过订阅 `CombatCoinChanged/Process/BaseHpChangedEventArgs` 来更新 UI,同时可以避免一点小变动刷新整个 UI - `CombatSelectForm` 逻辑优化:订阅 `CombatCoinChanged` 来实时更新当前 Coin 是否满足操作条件
This commit is contained in:
@@ -16,11 +16,9 @@ namespace GeometryTD.CustomComponent
|
||||
{
|
||||
public class BuiltinDataComponent : GameFrameworkComponent
|
||||
{
|
||||
[FormerlySerializedAs("m_BuildInfoTextAsset")] [SerializeField] private TextAsset _buildInfoTextAsset = null;
|
||||
[SerializeField] private TextAsset _buildInfoTextAsset = null;
|
||||
|
||||
[FormerlySerializedAs("m_DefaultDictionaryTextAsset")] [SerializeField] private TextAsset _defaultDictionaryTextAsset = null;
|
||||
|
||||
[FormerlySerializedAs("m_UpdateResourceFormTemplate")] [SerializeField] private UpdateResourceForm _updateResourceFormTemplate = null;
|
||||
[SerializeField] private UpdateResourceForm _updateResourceFormTemplate = null;
|
||||
|
||||
private BuildInfo _buildInfo = null;
|
||||
|
||||
@@ -43,20 +41,5 @@ namespace GeometryTD.CustomComponent
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void InitDefaultDictionary()
|
||||
{
|
||||
if (_defaultDictionaryTextAsset == null || string.IsNullOrEmpty(_defaultDictionaryTextAsset.text))
|
||||
{
|
||||
Log.Info("Default dictionary can not be found or empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GameEntry.Localization.ParseData(_defaultDictionaryTextAsset.text))
|
||||
{
|
||||
Log.Warning("Parse default dictionary failure.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,13 +26,18 @@ namespace GeometryTD.CustomComponent
|
||||
private readonly CombatScheduler _combatScheduler = new CombatScheduler();
|
||||
|
||||
private bool _runtimeInitialized;
|
||||
private bool _isCombatActive;
|
||||
private int _currentCoin;
|
||||
private int _currentGold;
|
||||
private int _currentBaseHp;
|
||||
private bool _lastCombatSucceeded = true;
|
||||
|
||||
public LevelThemeType CurrentThemeType { get; private set; }
|
||||
public DRLevel CurrentLevel { get; private set; }
|
||||
public int CurrentCoin => _currentCoin;
|
||||
public int CurrentGold => _currentGold;
|
||||
public int CurrentBaseHp => _currentBaseHp;
|
||||
public bool LastCombatSucceeded => _lastCombatSucceeded;
|
||||
public bool CanEndCombat => _combatScheduler.CanEndCombat;
|
||||
public int LastDefeatedEnemyCount { get; private set; }
|
||||
public int LastGainedCoin { get; private set; }
|
||||
@@ -70,8 +75,11 @@ namespace GeometryTD.CustomComponent
|
||||
|
||||
CurrentThemeType = themeType;
|
||||
CurrentLevel = null;
|
||||
_isCombatActive = false;
|
||||
_currentCoin = 0;
|
||||
_currentGold = 0;
|
||||
_currentBaseHp = 0;
|
||||
_lastCombatSucceeded = true;
|
||||
LastDefeatedEnemyCount = 0;
|
||||
LastGainedCoin = 0;
|
||||
LastGainedGold = 0;
|
||||
@@ -208,22 +216,42 @@ namespace GeometryTD.CustomComponent
|
||||
}
|
||||
|
||||
CurrentLevel = selectedLevel;
|
||||
_isCombatActive = false;
|
||||
_currentCoin = Mathf.Max(0, selectedLevel.StartCoin);
|
||||
_currentGold = 0;
|
||||
_currentBaseHp = Mathf.Max(0, selectedLevel.BaseHp);
|
||||
_lastCombatSucceeded = true;
|
||||
LastDefeatedEnemyCount = 0;
|
||||
LastGainedCoin = 0;
|
||||
LastGainedGold = 0;
|
||||
_combatScheduler.Start(selectedLevel, phaseList, _selectedSpawnEntriesByPhaseId);
|
||||
if (!_combatScheduler.Start(selectedLevel, phaseList, _selectedSpawnEntriesByPhaseId))
|
||||
{
|
||||
CurrentLevel = null;
|
||||
_currentCoin = 0;
|
||||
_currentGold = 0;
|
||||
_currentBaseHp = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_isCombatActive = true;
|
||||
GameEntry.Event.Fire(this, NodeEnterEventArgs.Create());
|
||||
}
|
||||
|
||||
public void EndCombat()
|
||||
{
|
||||
if (!_isCombatActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isCombatActive = false;
|
||||
LastDefeatedEnemyCount = _combatScheduler.DefeatedEnemyCount;
|
||||
LastGainedCoin = _combatScheduler.GainedCoin;
|
||||
LastGainedGold = _combatScheduler.GainedGold;
|
||||
_currentCoin = 0;
|
||||
_currentGold = 0;
|
||||
_currentBaseHp = 0;
|
||||
CurrentLevel = null;
|
||||
GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create());
|
||||
}
|
||||
|
||||
@@ -244,26 +272,55 @@ namespace GeometryTD.CustomComponent
|
||||
|
||||
public void OnShutdown()
|
||||
{
|
||||
_isCombatActive = false;
|
||||
CurrentLevel = null;
|
||||
_currentCoin = 0;
|
||||
_currentGold = 0;
|
||||
_currentBaseHp = 0;
|
||||
_lastCombatSucceeded = true;
|
||||
LastDefeatedEnemyCount = 0;
|
||||
LastGainedCoin = 0;
|
||||
LastGainedGold = 0;
|
||||
ShutdownCombatRuntime();
|
||||
}
|
||||
|
||||
public int ApplyBaseDamage(int damage)
|
||||
{
|
||||
int resolvedDamage = Mathf.Max(0, damage);
|
||||
if (resolvedDamage <= 0 || CurrentLevel == null || !_isCombatActive)
|
||||
{
|
||||
return _currentBaseHp;
|
||||
}
|
||||
|
||||
_currentBaseHp = Mathf.Max(0, _currentBaseHp - resolvedDamage);
|
||||
FireBaseHpChangedEventIfNeeded();
|
||||
return _currentBaseHp;
|
||||
}
|
||||
|
||||
public void OnCombatEndedByScheduler(bool succeeded)
|
||||
{
|
||||
_lastCombatSucceeded = succeeded;
|
||||
EndCombat();
|
||||
}
|
||||
|
||||
public void ApplyEnemyDropReward(int coin, int gold)
|
||||
{
|
||||
bool coinChanged = false;
|
||||
if (coin > 0)
|
||||
{
|
||||
_currentCoin += coin;
|
||||
coinChanged = true;
|
||||
}
|
||||
|
||||
if (gold > 0)
|
||||
{
|
||||
_currentGold += gold;
|
||||
}
|
||||
|
||||
if (coinChanged)
|
||||
{
|
||||
FireCoinChangedEventIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryConsumeCoin(int coin)
|
||||
@@ -280,6 +337,7 @@ namespace GeometryTD.CustomComponent
|
||||
}
|
||||
|
||||
_currentCoin -= requiredCoin;
|
||||
FireCoinChangedEventIfNeeded();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -292,6 +350,7 @@ namespace GeometryTD.CustomComponent
|
||||
}
|
||||
|
||||
_currentCoin += gainCoin;
|
||||
FireCoinChangedEventIfNeeded();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
@@ -356,5 +415,25 @@ namespace GeometryTD.CustomComponent
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private void FireCoinChangedEventIfNeeded()
|
||||
{
|
||||
if (!_isCombatActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Fire(this, CombatCoinChangedEventArgs.Create(_currentCoin));
|
||||
}
|
||||
|
||||
private void FireBaseHpChangedEventIfNeeded()
|
||||
{
|
||||
if (!_isCombatActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Fire(this, CombatBaseHpChangedEventArgs.Create(_currentBaseHp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.CustomEvent;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Entity;
|
||||
@@ -34,6 +35,7 @@ namespace GeometryTD.CustomComponent
|
||||
private bool _initialized;
|
||||
private int _gainedCoin;
|
||||
private int _gainedGold;
|
||||
private bool _isFinishAsVictory = true;
|
||||
|
||||
public bool IsRunning => _state == SchedulerState.WaitingForLoading || _state == SchedulerState.RunningPhase;
|
||||
public bool IsCompleted => _state == SchedulerState.Completed;
|
||||
@@ -67,23 +69,19 @@ namespace GeometryTD.CustomComponent
|
||||
ResetRuntime();
|
||||
}
|
||||
|
||||
public void Start(
|
||||
public bool Start(
|
||||
DRLevel level,
|
||||
IReadOnlyList<DRLevelPhase> phases,
|
||||
IReadOnlyDictionary<int, IReadOnlyList<DRLevelSpawnEntry>> spawnEntriesByPhaseId)
|
||||
{
|
||||
if (!_initialized || _entity == null)
|
||||
{
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Warning("CombatScheduler start failed. Runtime is not initialized.");
|
||||
return;
|
||||
return HandleStartFailure("CombatScheduler start failed. Runtime is not initialized.");
|
||||
}
|
||||
|
||||
if (level == null || phases == null || phases.Count <= 0)
|
||||
{
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Warning("CombatScheduler start failed. Invalid level or phase data.");
|
||||
return;
|
||||
return HandleStartFailure("CombatScheduler start failed. Invalid level or phase data.");
|
||||
}
|
||||
|
||||
CleanupAllCombatEntities();
|
||||
@@ -93,6 +91,7 @@ namespace GeometryTD.CustomComponent
|
||||
ResetRuntime();
|
||||
_gainedCoin = 0;
|
||||
_gainedGold = 0;
|
||||
_isFinishAsVictory = true;
|
||||
|
||||
_currentLevel = level;
|
||||
for (int i = 0; i < phases.Count; i++)
|
||||
@@ -115,21 +114,18 @@ namespace GeometryTD.CustomComponent
|
||||
_phaseLoopRuntime.SetPhases(_phaseBuffer);
|
||||
if (_phaseLoopRuntime.PhaseCount <= 0)
|
||||
{
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Warning("CombatScheduler start failed. Level '{0}' has no phase data.", level.Id);
|
||||
return;
|
||||
return HandleStartFailure($"CombatScheduler start failed. Level '{level.Id}' has no phase data.");
|
||||
}
|
||||
|
||||
if (!_loadSession.StartLoading(level, out string loadError))
|
||||
{
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Warning("CombatScheduler start failed. {0}", loadError);
|
||||
return;
|
||||
return HandleStartFailure($"CombatScheduler start failed. {loadError}");
|
||||
}
|
||||
|
||||
_state = SchedulerState.WaitingForLoading;
|
||||
Log.Info("CombatScheduler started. Level={0}, PhaseCount={1}.", _currentLevel.Id,
|
||||
_phaseLoopRuntime.PhaseCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
@@ -182,22 +178,16 @@ namespace GeometryTD.CustomComponent
|
||||
return false;
|
||||
}
|
||||
|
||||
EnterFinishFlow("Combat ended by player.");
|
||||
EnterFinishFlow("Combat ended by player.", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RefreshCombatInfoForm()
|
||||
{
|
||||
_loadSession.RefreshCombatInfoForm(_currentLevel);
|
||||
}
|
||||
|
||||
private void UpdateCurrentPhase(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
DRLevelPhase currentPhase = _phaseLoopRuntime.CurrentPhase;
|
||||
if (currentPhase == null)
|
||||
{
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Warning("CombatScheduler update failed. Current phase is null.");
|
||||
EnterFailureFallback("CombatScheduler update failed. Current phase is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -229,15 +219,16 @@ namespace GeometryTD.CustomComponent
|
||||
{
|
||||
if (!_phaseLoopRuntime.TryEnterNextPhase(out DRLevelPhase nextPhase))
|
||||
{
|
||||
EnterFinishFlow("Combat ended after loop completion.");
|
||||
EnterFinishFlow("Combat ended after loop completion.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshCombatInfoForm();
|
||||
|
||||
_spawnEntriesByPhaseId.TryGetValue(nextPhase.Id, out IReadOnlyList<DRLevelSpawnEntry> spawnEntries);
|
||||
_enemyManager.BeginPhase(nextPhase, spawnEntries);
|
||||
_state = SchedulerState.RunningPhase;
|
||||
GameEntry.Event.Fire(
|
||||
this,
|
||||
CombatProcessEventArgs.Create(_phaseLoopRuntime.DisplayPhaseIndex, _phaseLoopRuntime.PhaseCount));
|
||||
|
||||
Log.Info(
|
||||
"CombatScheduler phase started. Level={0}, Phase={1}, EndType={2}, Entries={3}.",
|
||||
@@ -247,13 +238,14 @@ namespace GeometryTD.CustomComponent
|
||||
spawnEntries != null ? spawnEntries.Count : 0);
|
||||
}
|
||||
|
||||
private void EnterFinishFlow(string reason)
|
||||
private void EnterFinishFlow(string reason, bool isVictory)
|
||||
{
|
||||
int defeatedEnemyCount = _enemyManager.DefeatedEnemyCount;
|
||||
|
||||
// Step 1: stop runtime and clear enemy entities only.
|
||||
_enemyManager.EndPhase();
|
||||
_state = SchedulerState.WaitingForFinishReturn;
|
||||
_isFinishAsVictory = isVictory;
|
||||
_enemyManager.CleanupTrackedEnemies();
|
||||
|
||||
Log.Info(
|
||||
@@ -264,6 +256,34 @@ namespace GeometryTD.CustomComponent
|
||||
OpenCombatFinishForm(defeatedEnemyCount, _gainedGold);
|
||||
}
|
||||
|
||||
public void OnEnemyReachedBase(int baseDamage)
|
||||
{
|
||||
if (_state != SchedulerState.RunningPhase)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int resolvedBaseDamage = Mathf.Max(0, baseDamage);
|
||||
if (resolvedBaseDamage <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CombatNodeComponent combatNode = GameEntry.CombatNode;
|
||||
if (combatNode == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int currentBaseHp = combatNode.ApplyBaseDamage(resolvedBaseDamage);
|
||||
if (currentBaseHp > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnterFinishFlow("Combat ended because base HP reached zero.", false);
|
||||
}
|
||||
|
||||
private void ResetRuntime()
|
||||
{
|
||||
_phaseBuffer.Clear();
|
||||
@@ -271,6 +291,7 @@ namespace GeometryTD.CustomComponent
|
||||
_phaseLoopRuntime.Reset();
|
||||
_loadSession.Reset();
|
||||
_currentLevel = null;
|
||||
_isFinishAsVictory = true;
|
||||
_state = SchedulerState.Idle;
|
||||
}
|
||||
|
||||
@@ -321,10 +342,37 @@ namespace GeometryTD.CustomComponent
|
||||
_loadSession.Cleanup();
|
||||
CloseCombatFinishForm();
|
||||
_state = SchedulerState.Completed;
|
||||
GameEntry.CombatNode.EndCombat();
|
||||
GameEntry.CombatNode?.OnCombatEndedByScheduler(_isFinishAsVictory);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleStartFailure(string errorMessage)
|
||||
{
|
||||
Log.Warning("{0}", errorMessage);
|
||||
_state = SchedulerState.Failed;
|
||||
_enemyManager.EndPhase();
|
||||
CleanupAllCombatEntities();
|
||||
CloseCombatFinishForm();
|
||||
ResetRuntime();
|
||||
return false;
|
||||
}
|
||||
|
||||
private void EnterFailureFallback(string errorMessage)
|
||||
{
|
||||
if (_state == SchedulerState.Failed || _state == SchedulerState.Completed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Error("CombatScheduler failed. LevelId={0}, {1}", _currentLevel != null ? _currentLevel.Id : 0,
|
||||
errorMessage);
|
||||
_enemyManager.EndPhase();
|
||||
CleanupAllCombatEntities();
|
||||
CloseCombatFinishForm();
|
||||
GameEntry.CombatNode?.OnCombatEndedByScheduler(false);
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
private void OnShowEntitySuccess(ShowEntitySuccessEventArgs args)
|
||||
@@ -332,8 +380,7 @@ namespace GeometryTD.CustomComponent
|
||||
var status = _loadSession.HandleShowEntitySuccess(args, out string errorMessage);
|
||||
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
||||
{
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Error("CombatScheduler failed. {0}", errorMessage);
|
||||
EnterFailureFallback(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -355,9 +402,7 @@ namespace GeometryTD.CustomComponent
|
||||
var status = _loadSession.HandleShowEntityFailure(args, out string errorMessage);
|
||||
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
||||
{
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Error("CombatScheduler failed. LevelId={0}, {1}", _currentLevel != null ? _currentLevel.Id : 0,
|
||||
errorMessage);
|
||||
EnterFailureFallback(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,8 +416,7 @@ namespace GeometryTD.CustomComponent
|
||||
var status = _loadSession.HandleOpenUIFormSuccess(args, out string errorMessage);
|
||||
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
||||
{
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Error("CombatScheduler failed. {0}", errorMessage);
|
||||
EnterFailureFallback(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,8 +425,7 @@ namespace GeometryTD.CustomComponent
|
||||
var status = _loadSession.HandleOpenUIFormFailure(args, out string errorMessage);
|
||||
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
||||
{
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Error("CombatScheduler failed. {0}", errorMessage);
|
||||
EnterFailureFallback(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -530,12 +530,14 @@ namespace GeometryTD.CustomComponent
|
||||
|
||||
_currentEnemyCount = Mathf.Max(0, _currentEnemyCount - 1);
|
||||
bool wasKilled = EnemyEntity.TryConsumeKilledFlag(ne.EntityId);
|
||||
if (_combatScheduler != null && _combatScheduler.IsRunning && wasKilled)
|
||||
bool isCombatRunning = _combatScheduler != null && _combatScheduler.IsRunning;
|
||||
int baseDamage = 0;
|
||||
int droppedCoin = 0;
|
||||
int droppedGold = 0;
|
||||
if (_trackedEnemyConfigByEntityId.TryGetValue(ne.EntityId, out DREnemy enemyConfig) && enemyConfig != null)
|
||||
{
|
||||
_defeatedEnemyCount++;
|
||||
int droppedCoin = 0;
|
||||
int droppedGold = 0;
|
||||
if (_trackedEnemyConfigByEntityId.TryGetValue(ne.EntityId, out DREnemy enemyConfig) && enemyConfig != null)
|
||||
baseDamage = Mathf.Max(0, enemyConfig.BaseDamage);
|
||||
if (wasKilled)
|
||||
{
|
||||
droppedCoin = Mathf.Max(0, enemyConfig.DropCoin);
|
||||
float dropRate = enemyConfig.DropPercent > 1f
|
||||
@@ -546,9 +548,17 @@ namespace GeometryTD.CustomComponent
|
||||
droppedGold = Mathf.Max(0, enemyConfig.DropGold);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isCombatRunning && wasKilled)
|
||||
{
|
||||
_defeatedEnemyCount++;
|
||||
_combatScheduler.OnEnemyDefeatedRewardResolved(droppedCoin, droppedGold);
|
||||
}
|
||||
else if (isCombatRunning && baseDamage > 0)
|
||||
{
|
||||
_combatScheduler.OnEnemyReachedBase(baseDamage);
|
||||
}
|
||||
|
||||
_trackedEnemyConfigByEntityId.Remove(ne.EntityId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user