CombatNodeComponent 战斗节点功能补全

- 失败逻辑补全:添加基地血量,当血量归0时强制结束关卡
- `CombatInfoForm` 刷新逻辑补全:通过订阅 `CombatCoinChanged/Process/BaseHpChangedEventArgs` 来更新 UI,同时可以避免一点小变动刷新整个 UI
- `CombatSelectForm` 逻辑优化:订阅 `CombatCoinChanged` 来实时更新当前 Coin 是否满足操作条件
This commit is contained in:
SepComet
2026-03-02 12:06:36 +08:00
parent 5c7501d4fb
commit c576224991
23 changed files with 625 additions and 85 deletions
@@ -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);
}
@@ -91,7 +91,6 @@ namespace GeometryTD.Entity
_maxHealth = 0;
_currentHealth = 0;
_activeEnemies.Remove(this);
_killedEnemyEntityIds.Remove(Id);
base.OnHide(isShutdown, userData);
}
@@ -99,10 +98,6 @@ namespace GeometryTD.Entity
private void OnDestroy()
{
_activeEnemies.Remove(this);
if (Entity != null)
{
_killedEnemyEntityIds.Remove(Entity.Id);
}
}
public void TakeDamage(int damage, AttackPropertyType attackPropertyType)
@@ -196,4 +191,4 @@ namespace GeometryTD.Entity
GameEntry.Entity.HideEntity(Entity);
}
}
}
}
@@ -18,8 +18,8 @@ namespace GeometryTD.Entity
[SerializeField] private bool _enableCombatSelectInput = true;
[SerializeField] private int _towerTypeId = DefaultTowerTypeId;
[SerializeField] private int[] _buildTowerCosts = { 80, 120, 160, 220 };
[SerializeField] private int _upgradeCost = 80;
[SerializeField] private int[] _buildTowerCosts = { 40, 60, 60, 80 };
[SerializeField] private int _upgradeCost = 50;
[SerializeField] private int _destroyGain = 40;
private MapDataRefs _mapDataRefs;
@@ -0,0 +1,33 @@
using GameFramework;
using GameFramework.Event;
namespace GeometryTD.CustomEvent
{
public class CombatBaseHpChangedEventArgs : GameEventArgs
{
public static int EventId => typeof(CombatBaseHpChangedEventArgs).GetHashCode();
public override int Id => EventId;
public int CurrentBaseHp { get; private set; }
public CombatBaseHpChangedEventArgs()
{
CurrentBaseHp = 100;
}
public static CombatBaseHpChangedEventArgs Create(int currentBaseHp)
{
var args = ReferencePool.Acquire<CombatBaseHpChangedEventArgs>();
args.CurrentBaseHp = currentBaseHp;
return args;
}
public override void Clear()
{
CurrentBaseHp = 100;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c6bad381b11f48fc96b6f421d1c038b8
timeCreated: 1772423097
@@ -0,0 +1,33 @@
using GameFramework;
using GameFramework.Event;
namespace GeometryTD.CustomEvent
{
public class CombatCoinChangedEventArgs : GameEventArgs
{
public static int EventId => typeof(CombatCoinChangedEventArgs).GetHashCode();
public override int Id => EventId;
public int CurrentCoin { get; private set; }
public CombatCoinChangedEventArgs()
{
CurrentCoin = 0;
}
public static CombatCoinChangedEventArgs Create(int currentCoin)
{
var args = ReferencePool.Acquire<CombatCoinChangedEventArgs>();
args.CurrentCoin = currentCoin;
return args;
}
public override void Clear()
{
CurrentCoin = 0;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7b2164bc737d4523936e658ee5054c24
timeCreated: 1772423027
@@ -0,0 +1,37 @@
using GameFramework;
using GameFramework.Event;
namespace GeometryTD.CustomEvent
{
public class CombatProcessEventArgs : GameEventArgs
{
public static int EventId => typeof(CombatProcessEventArgs).GetHashCode();
public override int Id => EventId;
public int CurrentPhase { get; private set; }
public int TotalPhase { get; private set; }
public CombatProcessEventArgs()
{
CurrentPhase = 0;
TotalPhase = 0;
}
public static CombatProcessEventArgs Create(int currentPhase, int totalPhase)
{
var args = ReferencePool.Acquire<CombatProcessEventArgs>();
args.CurrentPhase = currentPhase;
args.TotalPhase = totalPhase;
return args;
}
public override void Clear()
{
CurrentPhase = 0;
TotalPhase = 0;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 237e1ef1e2614b2ab89d4d57bdd5174c
timeCreated: 1772422933
@@ -32,10 +32,6 @@ namespace GeometryTD.Procedure
// 声音配置:根据用户配置数据,设置即将使用的声音选项
InitSoundSettings();
// 默认字典:加载默认字典文件 Assets/GameMain/Configs/DefaultDictionary.xml
// 此字典文件记录了资源更新前使用的各种语言的字符串,会随 App 一起发布,故不可更新
GameEntry.BuiltinData.InitDefaultDictionary();
}
protected override void OnUpdate(ProcedureOwner procedureOwner, float elapseSeconds, float realElapseSeconds)
@@ -203,7 +203,7 @@ namespace GeometryTD.Map
case 0:
return new DefenseTowerStatsData
{
AttackDamage = 100,
AttackDamage = 200,
DamageRandomRate = 0f,
RotateSpeed = 200f,
AttackRange = 4.5f,
@@ -215,7 +215,7 @@ namespace GeometryTD.Map
case 1:
return new DefenseTowerStatsData
{
AttackDamage = 13,
AttackDamage = 260,
DamageRandomRate = 0f,
RotateSpeed = 160f,
AttackRange = 5f,
@@ -227,7 +227,7 @@ namespace GeometryTD.Map
case 2:
return new DefenseTowerStatsData
{
AttackDamage = 17,
AttackDamage = 340,
DamageRandomRate = 0f,
RotateSpeed = 140f,
AttackRange = 5.5f,
@@ -239,7 +239,7 @@ namespace GeometryTD.Map
case 3:
return new DefenseTowerStatsData
{
AttackDamage = 22,
AttackDamage = 440,
DamageRandomRate = 0f,
RotateSpeed = 120f,
AttackRange = 6f,
@@ -251,7 +251,7 @@ namespace GeometryTD.Map
default:
return new DefenseTowerStatsData
{
AttackDamage = 100,
AttackDamage = 200,
DamageRandomRate = 0f,
RotateSpeed = 180f,
AttackRange = 5f,
@@ -5,6 +5,7 @@ namespace GeometryTD.UI
public string LevelMetaText;
public string LevelPhaseText;
public string CoinText;
public string BaseHpText;
public bool CanPause;
public bool CanEnd;
}
@@ -20,12 +20,18 @@ namespace GeometryTD.UI
{
GameEntry.Event.Subscribe(CombatPauseEventArgs.EventId, OnCombatPauseButtonClicked);
GameEntry.Event.Subscribe(CombatEndEventArgs.EventId, OnCombatEndButtonClicked);
GameEntry.Event.Subscribe(CombatProcessEventArgs.EventId, OnCombatProcessChanged);
GameEntry.Event.Subscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
GameEntry.Event.Subscribe(CombatBaseHpChangedEventArgs.EventId, OnCombatBaseHpChanged);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(CombatPauseEventArgs.EventId, OnCombatPauseButtonClicked);
GameEntry.Event.Unsubscribe(CombatEndEventArgs.EventId, OnCombatEndButtonClicked);
GameEntry.Event.Unsubscribe(CombatProcessEventArgs.EventId, OnCombatProcessChanged);
GameEntry.Event.Unsubscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
GameEntry.Event.Unsubscribe(CombatBaseHpChangedEventArgs.EventId, OnCombatBaseHpChanged);
}
public int? OpenUI(CombatInfoFormRawData rawData)
@@ -85,6 +91,7 @@ namespace GeometryTD.UI
LevelMetaText = BuildLevelMetaText(rawData.LevelThemeType, rawData.LevelId),
LevelPhaseText = BuildPhaseText(rawData.CurrentPhaseIndex, rawData.TotalPhaseCount),
CoinText = BuildCoinText(rawData.Coin),
BaseHpText = BuildBaseHpText(rawData.BaseHp, rawData.BaseHpMax),
CanPause = rawData.CanPause,
CanEnd = rawData.CanEnd
};
@@ -121,6 +128,18 @@ namespace GeometryTD.UI
return $"Coin: {coin}";
}
private static string BuildBaseHpText(int baseHp, int baseHpMax)
{
if (baseHpMax <= 0)
{
return "\u57FA\u5730\uFF1A-";
}
int clampedHp = UnityEngine.Mathf.Clamp(baseHp, 0, baseHpMax);
int percent = UnityEngine.Mathf.RoundToInt((float)clampedHp / baseHpMax * 100f);
return $"\u57FA\u5730\uFF1A{percent}%";
}
private void OnCombatPauseButtonClicked(object sender, GameEventArgs e)
{
if (!(sender is CombatInfoForm) || !(e is CombatPauseEventArgs))
@@ -147,5 +166,53 @@ namespace GeometryTD.UI
_useCase?.TryEndCombat();
}
private void OnCombatProcessChanged(object sender, GameEventArgs e)
{
if (!(e is CombatProcessEventArgs))
{
return;
}
RefreshFromUseCase();
}
private void OnCombatCoinChanged(object sender, GameEventArgs e)
{
if (!(e is CombatCoinChangedEventArgs))
{
return;
}
RefreshFromUseCase();
}
private void OnCombatBaseHpChanged(object sender, GameEventArgs e)
{
if (!(e is CombatBaseHpChangedEventArgs))
{
return;
}
RefreshFromUseCase();
}
private void RefreshFromUseCase()
{
if (_useCase == null)
{
return;
}
CombatInfoFormRawData rawData = _useCase.TryRefresh();
CombatInfoFormContext context = BuildContext(rawData);
if (context == null)
{
return;
}
SetContext(context);
RefreshCurrentUI();
}
}
}
@@ -20,11 +20,13 @@ namespace GeometryTD.UI
protected override void SubscribeCustomEvents()
{
GameEntry.Event.Subscribe(CombatSelectItemClickEventArgs.EventId, OnSelectItemClicked);
GameEntry.Event.Subscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(CombatSelectItemClickEventArgs.EventId, OnSelectItemClicked);
GameEntry.Event.Unsubscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
}
public int? OpenUI(CombatSelectFormRawData rawData)
@@ -262,5 +264,20 @@ namespace GeometryTD.UI
_useCase.TryExecuteAction(args.ActionType, args.ActionIndex);
RefreshFromUseCase();
}
private void OnCombatCoinChanged(object sender, GameEventArgs e)
{
if (!(e is CombatCoinChangedEventArgs))
{
return;
}
if (Form == null)
{
return;
}
RefreshFromUseCase();
}
}
}
@@ -9,6 +9,8 @@ namespace GeometryTD.UI
public int CurrentPhaseIndex;
public int TotalPhaseCount;
public int Coin;
public int BaseHp;
public int BaseHpMax;
public bool CanPause;
public bool CanEnd;
}
@@ -34,6 +34,7 @@ namespace GeometryTD.UI
var level = GameEntry.CombatNode.CurrentLevel;
LevelThemeType themeType = level != null ? level.LevelThemeType : GameEntry.CombatNode.CurrentThemeType;
int levelId = level != null ? level.Id : 0;
int baseHpMax = level != null ? level.BaseHp : 0;
return new CombatInfoFormRawData
{
@@ -42,6 +43,8 @@ namespace GeometryTD.UI
CurrentPhaseIndex = GameEntry.CombatNode.CurrentPhaseIndex,
TotalPhaseCount = GameEntry.CombatNode.CurrentLevelPhaseCount,
Coin = GameEntry.CombatNode.CurrentCoin,
BaseHp = GameEntry.CombatNode.CurrentBaseHp,
BaseHpMax = baseHpMax,
CanPause = true,
CanEnd = GameEntry.CombatNode.CanEndCombat
};
@@ -12,6 +12,8 @@ namespace GeometryTD.UI
[SerializeField] private TMP_Text _levelPhaseText;
[SerializeField] private TMP_Text _coinText;
[SerializeField] private TMP_Text _baseHpText;
[SerializeField] private CommonButton _pauseButton;
@@ -37,6 +39,11 @@ namespace GeometryTD.UI
{
_coinText.text = context?.CoinText ?? "0";
}
if (_baseHpText != null)
{
_baseHpText.text = context?.BaseHpText ?? string.Empty;
}
if (_pauseButton != null)
{
@@ -100,6 +107,11 @@ namespace GeometryTD.UI
{
_coinText.text = string.Empty;
}
if (_baseHpText != null)
{
_baseHpText.text = string.Empty;
}
base.OnClose(isShutdown, userData);
}