- 调整优化了 CombatNodeComponent 相关实体的生命周期管理

- 总结了一份 CombatNodeComponent 文档 CombatNodeArchitecture.md
This commit is contained in:
SepComet
2026-02-28 20:20:02 +08:00
parent dfd37778b6
commit 26dc1a5600
10 changed files with 364 additions and 73 deletions
+1 -1
View File
@@ -7,6 +7,6 @@
101 设置 SettingForm Default False True
102 关于 AboutForm Default False True
103 主UI MainForm Default False True
150 测试UI TestMenuForm Default False False
150 测试UI TestMenuForm Test False False
200 事件UI EventForm Default False False
201 背包UI RepoForm Default False True
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using GameFramework.DataTable;
using GeometryTD.CustomEvent;
using GeometryTD.DataTable;
using GeometryTD.Definition;
using UnityEngine;
@@ -65,9 +66,8 @@ namespace GeometryTD.CustomComponent
}
DRLevelPhase[] levelPhases = dtLevelPhase.GetAllDataRows();
for (int i = 0; i < levelPhases.Length; i++)
foreach (var phase in levelPhases)
{
DRLevelPhase phase = levelPhases[i];
int levelId = phase.Id / 1000;
if (!_levelsById.ContainsKey(levelId))
{
@@ -85,9 +85,8 @@ namespace GeometryTD.CustomComponent
}
DRLevelSpawnEntry[] spawnEntries = dtSpawnEntry.GetAllDataRows();
for (int i = 0; i < spawnEntries.Length; i++)
foreach (var spawnEntry in spawnEntries)
{
DRLevelSpawnEntry spawnEntry = spawnEntries[i];
int phaseId = spawnEntry.Id / 1000;
int levelId = phaseId / 1000;
if (!_levelsById.ContainsKey(levelId))
@@ -171,6 +170,12 @@ namespace GeometryTD.CustomComponent
CurrentLevel = selectedLevel;
_combatScheduler.Start(selectedLevel, phaseList, _selectedSpawnEntriesByPhaseId);
GameEntry.Event.Fire(this, NodeEnterEventArgs.Create());
}
public void EndCombat()
{
GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create());
}
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
@@ -252,4 +257,4 @@ namespace GeometryTD.CustomComponent
return count;
}
}
}
}
@@ -23,9 +23,9 @@ namespace GeometryTD.CustomComponent
Failed = 4
}
private readonly List<DRLevelPhase> _phaseBuffer = new List<DRLevelPhase>();
private readonly Dictionary<int, IReadOnlyList<DRLevelSpawnEntry>> _spawnEntriesByPhaseId =
new Dictionary<int, IReadOnlyList<DRLevelSpawnEntry>>();
private readonly List<DRLevelPhase> _phaseBuffer = new();
private readonly Dictionary<int, IReadOnlyList<DRLevelSpawnEntry>> _spawnEntriesByPhaseId = new();
private readonly EnemyManager _enemyManager = new EnemyManager();
@@ -79,8 +79,8 @@ namespace GeometryTD.CustomComponent
return;
}
CleanupBattleEntities();
_enemyManager.EndPhase();
HideCurrentMapIfNeeded();
ResetRuntime();
_currentLevel = level;
@@ -145,8 +145,8 @@ namespace GeometryTD.CustomComponent
return;
}
CleanupBattleEntities();
_enemyManager.OnDestroy();
HideCurrentMapIfNeeded();
ResetRuntime();
UnsubscribeEntityEvents();
_entity = null;
@@ -253,7 +253,12 @@ namespace GeometryTD.CustomComponent
{
_state = SchedulerState.Completed;
_currentPhase = null;
if (_currentMap != null)
{
_entity.HideEntity(_currentMap);
}
Log.Info("CombatScheduler level completed. Level={0}.", _currentLevel != null ? _currentLevel.Id : 0);
GameEntry.CombatNode.EndCombat();
return;
}
@@ -273,39 +278,6 @@ namespace GeometryTD.CustomComponent
spawnEntries != null ? spawnEntries.Count : 0);
}
private void HideCurrentMapIfNeeded()
{
if (_entity == null)
{
_currentMap = null;
_loadingMapEntityId = 0;
_loadedMapEntityId = 0;
return;
}
if (_loadingMapEntityId != 0)
{
EntityBase loadingMap = _entity.GetGameEntity(_loadingMapEntityId);
if (loadingMap != null)
{
_entity.HideEntity(loadingMap);
}
}
if (_loadedMapEntityId != 0)
{
EntityBase loadedMap = _entity.GetGameEntity(_loadedMapEntityId);
if (loadedMap != null)
{
_entity.HideEntity(loadedMap);
}
}
_loadingMapEntityId = 0;
_loadedMapEntityId = 0;
_currentMap = null;
}
private void EnsureEntityEventSubscribed()
{
if (_isEntityEventSubscribed)
@@ -340,9 +312,40 @@ namespace GeometryTD.CustomComponent
_currentPhase = null;
_currentPhaseIndex = -1;
_currentPhaseElapsed = 0f;
_loadingMapEntityId = 0;
_loadedMapEntityId = 0;
_currentMap = null;
_state = SchedulerState.Idle;
}
private void CleanupBattleEntities()
{
if (_entity == null)
{
return;
}
if (_loadingMapEntityId != 0 && _entity.IsLoadingEntity(_loadingMapEntityId))
{
_entity.HideEntity(_loadingMapEntityId);
}
if (_currentMap != null)
{
_entity.HideEntity(_currentMap);
}
else if (_loadedMapEntityId != 0)
{
UnityGameFramework.Runtime.Entity loadedMapEntity = _entity.GetEntity(_loadedMapEntityId);
if (loadedMapEntity != null)
{
_entity.HideEntity(loadedMapEntity);
}
}
_enemyManager.CleanupTrackedEnemies();
}
#region Event Handlers
private void OnShowEntitySuccess(object sender, GameEventArgs e)
@@ -29,6 +29,8 @@ namespace GeometryTD.CustomComponent
private readonly Dictionary<int, Spawner> _spawnerByOrder = new Dictionary<int, Spawner>();
private readonly List<Vector3> _pathBuffer = new List<Vector3>();
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 CombatScheduler _combatScheduler;
private EntityComponent _entity;
@@ -66,6 +68,8 @@ namespace GeometryTD.CustomComponent
_spawnerByOrder.Clear();
_pathBuffer.Clear();
_spawnRuntimes.Clear();
_trackedEnemyEntityIds.Clear();
_trackedEnemyIdBuffer.Clear();
_phaseElapsed = 0f;
_isPhaseRunning = false;
IsPhaseSpawnCompleted = true;
@@ -134,6 +138,7 @@ namespace GeometryTD.CustomComponent
return;
}
CleanupTrackedEnemies();
EndPhase();
GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
GameEntry.Event.Unsubscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure);
@@ -142,12 +147,47 @@ namespace GeometryTD.CustomComponent
_spawners.Clear();
_spawnerByOrder.Clear();
_pathBuffer.Clear();
_trackedEnemyEntityIds.Clear();
_trackedEnemyIdBuffer.Clear();
_currentEnemyCount = 0;
_currentMapEntityId = 0;
_nextSpawnerIndex = 0;
_combatScheduler = null;
_initialized = false;
}
public void CleanupTrackedEnemies()
{
if (_trackedEnemyEntityIds.Count <= 0)
{
_currentEnemyCount = 0;
return;
}
_trackedEnemyIdBuffer.Clear();
foreach (int trackedEnemyEntityId in _trackedEnemyEntityIds)
{
_trackedEnemyIdBuffer.Add(trackedEnemyEntityId);
}
_trackedEnemyEntityIds.Clear();
_currentEnemyCount = 0;
if (_entity == null)
{
return;
}
for (int i = 0; i < _trackedEnemyIdBuffer.Count; i++)
{
int trackedEnemyEntityId = _trackedEnemyIdBuffer[i];
if (_entity.HasEntity(trackedEnemyEntityId) || _entity.IsLoadingEntity(trackedEnemyEntityId))
{
_entity.HideEntity(trackedEnemyEntityId);
}
}
}
private SpawnEntryRuntime BuildSpawnRuntime(DRLevelSpawnEntry entry)
{
if (entry == null || entry.EntryType == EntryType.None)
@@ -167,12 +207,12 @@ namespace GeometryTD.CustomComponent
switch (entry.EntryType)
{
case EntryType.Stream:
{
float duration = Mathf.Max(0f, entry.Duration);
runtime.EndTime = duration > 0f ? runtime.NextTriggerTime + duration : runtime.NextTriggerTime;
runtime.Completed = entry.Count <= 0;
return runtime;
}
{
float duration = Mathf.Max(0f, entry.Duration);
runtime.EndTime = duration > 0f ? runtime.NextTriggerTime + duration : runtime.NextTriggerTime;
runtime.Completed = entry.Count <= 0;
return runtime;
}
case EntryType.Burst:
case EntryType.Boss:
runtime.Completed = runtime.RemainingCount <= 0;
@@ -311,6 +351,7 @@ namespace GeometryTD.CustomComponent
}
int enemyEntityId = _entity.GenerateSerialId();
_trackedEnemyEntityIds.Add(enemyEntityId);
EnemyData enemyData = new EnemyData(
enemyEntityId,
enemyConfig.EntityId,
@@ -434,17 +475,28 @@ namespace GeometryTD.CustomComponent
private void OnShowEntitySuccess(object sender, GameEventArgs e)
{
if (e is ShowEntitySuccessEventArgs ne)
if (!(e is ShowEntitySuccessEventArgs ne)) return;
if (ne.EntityLogicType == typeof(EnemyEntity) &&
_trackedEnemyEntityIds.Contains(ne.Entity.Id))
{
if (ne.EntityLogicType == typeof(EnemyEntity))
{
_currentEnemyCount++;
}
_currentEnemyCount++;
}
}
private void OnShowEntityFailure(object sender, GameEventArgs e)
{
if (!(e is ShowEntityFailureEventArgs ne))
{
return;
}
if (ne.EntityLogicType != typeof(EnemyEntity))
{
return;
}
_trackedEnemyEntityIds.Remove(ne.EntityId);
}
private void OnHideEntityComplete(object sender, GameEventArgs e)
@@ -454,7 +506,7 @@ namespace GeometryTD.CustomComponent
return;
}
if (ne.EntityGroup.Name != "Enemy")
if (!_trackedEnemyEntityIds.Remove(ne.EntityId))
{
return;
}
@@ -462,4 +514,4 @@ namespace GeometryTD.CustomComponent
_currentEnemyCount = Mathf.Max(0, _currentEnemyCount - 1);
}
}
}
}
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using GameFramework.DataTable;
using GeometryTD.CustomEvent;
using GeometryTD.DataTable;
using GeometryTD.Definition;
using Newtonsoft.Json.Linq;
@@ -72,9 +73,16 @@ namespace GeometryTD.CustomComponent
Log.Warning("EventNodeComponent StartEvent failed. Event form is not initialized.");
return;
}
_eventFormUseCase.SetCurrentEvent(randomEvent);
_eventFormController.OpenUI();
GameEntry.Event.Fire(this, NodeEnterEventArgs.Create());
}
public void EndEvent()
{
GameEntry.UIRouter.CloseUI(UIFormType.EventForm);
GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create());
}
private static EventOption[] ParseOptions(string optionsRaw)
@@ -169,4 +177,4 @@ namespace GeometryTD.CustomComponent
return effects.ToArray();
}
}
}
}
@@ -74,6 +74,7 @@ namespace GeometryTD.Procedure
if (!(e is NodeEnterEventArgs)) return;
GameEntry.UIRouter.CloseUI(UIFormType.TestMenuForm);
GameEntry.UIRouter.CloseUI(UIFormType.MainForm);
}
private void OnNodeComplete(object sender, GameEventArgs e)
@@ -81,6 +82,7 @@ namespace GeometryTD.Procedure
if (!(e is NodeCompleteEventArgs)) return;
GameEntry.UIRouter.OpenUI(UIFormType.TestMenuForm);
GameEntry.UIRouter.OpenUI(UIFormType.MainForm);
}
}
}
@@ -118,7 +118,6 @@ namespace GeometryTD.UI
}
m_UseCase.SelectOption(args.SelectedItemId);
CloseUI();
}
}
}
@@ -1,3 +1,4 @@
using GeometryTD.CustomComponent;
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using UnityGameFramework.Runtime;
@@ -6,42 +7,42 @@ namespace GeometryTD.UI
{
public class EventFormUseCase : IUIUseCase
{
private EventItem m_CurrentEvent;
private EventItem _currentEvent;
public void SetCurrentEvent(EventItem eventItem)
{
m_CurrentEvent = eventItem;
_currentEvent = eventItem;
}
public EventFormRawData CreateInitialModel()
{
if (m_CurrentEvent == null)
if (_currentEvent == null)
{
return null;
}
return new EventFormRawData
{
EventItem = m_CurrentEvent
EventItem = _currentEvent
};
}
public EventOption SelectOption(int optionIndex)
{
if (m_CurrentEvent == null || m_CurrentEvent.Options == null)
if (_currentEvent == null || _currentEvent.Options == null)
{
return null;
}
if (optionIndex < 0 || optionIndex >= m_CurrentEvent.Options.Length)
if (optionIndex < 0 || optionIndex >= _currentEvent.Options.Length)
{
Log.Warning("EventFormUseCase.SelectOption() option index is invalid: {0}", optionIndex);
return null;
}
// TODO: 执行 Requirements 校验、CostEffects/RewardEffects 结算与后续节点推进。
GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create());
return m_CurrentEvent.Options[optionIndex];
GameEntry.EventNode.EndEvent();
return _currentEvent.Options[optionIndex];
}
}
}
+5 -5
View File
@@ -490,7 +490,7 @@ PrefabInstance:
objectReference: {fileID: 1576232203}
- target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_UIGroups.Array.size
value: 1
value: 2
objectReference: {fileID: 0}
- target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_UIGroupHelperTypeName
@@ -502,7 +502,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_UIGroups.Array.data[1].m_Name
value: Default
value: Test
objectReference: {fileID: 0}
- target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_UIGroups.Array.data[0].m_Depth
@@ -510,7 +510,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_UIGroups.Array.data[1].m_Depth
value: 0
value: 2
objectReference: {fileID: 0}
- target: {fileID: 11461470, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_LocalizationHelperTypeName
@@ -522,7 +522,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.size
value: 5
value: 4
objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[0].m_Name
@@ -538,7 +538,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[3].m_Name
value: Player
value: Map
objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[4].m_Name