- 调整优化了 CombatNodeComponent 相关实体的生命周期管理
- 总结了一份 CombatNodeComponent 文档 CombatNodeArchitecture.md
This commit is contained in:
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user