refactor & fix
- 将 `CombatScheduler` 的职责分散到 3 个类中
- `PhaseLoopRuntime`:负责关卡各波次的调度
- `CombatLoadSession`:负责地图与 UI 等资源的加载
- `CombatEventBridge`:负责相关事件的处理
- 统一了战斗的命名为 `Combat`
- 调整了各个地图的坐标,使其位于摄像头中心
- 新增了 CombatInfoForm 展示战斗内的信息
- 补充了循环执行 LevelPhase,手动结束战斗的逻辑
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using GameFramework.Event;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.CustomComponent
|
||||
{
|
||||
internal sealed class CombatEventBridge
|
||||
{
|
||||
private Action<ShowEntitySuccessEventArgs> _onShowEntitySuccess;
|
||||
private Action<ShowEntityFailureEventArgs> _onShowEntityFailure;
|
||||
private Action<HideEntityCompleteEventArgs> _onHideEntityComplete;
|
||||
private Action<OpenUIFormSuccessEventArgs> _onOpenUIFormSuccess;
|
||||
private Action<OpenUIFormFailureEventArgs> _onOpenUIFormFailure;
|
||||
private Action<CloseUIFormCompleteEventArgs> _onCloseUIFormComplete;
|
||||
|
||||
private bool _isEntityEventSubscribed;
|
||||
private bool _isUIEventSubscribed;
|
||||
|
||||
public void Bind(
|
||||
Action<ShowEntitySuccessEventArgs> onShowEntitySuccess,
|
||||
Action<ShowEntityFailureEventArgs> onShowEntityFailure,
|
||||
Action<HideEntityCompleteEventArgs> onHideEntityComplete,
|
||||
Action<OpenUIFormSuccessEventArgs> onOpenUIFormSuccess,
|
||||
Action<OpenUIFormFailureEventArgs> onOpenUIFormFailure,
|
||||
Action<CloseUIFormCompleteEventArgs> onCloseUIFormComplete)
|
||||
{
|
||||
_onShowEntitySuccess = onShowEntitySuccess;
|
||||
_onShowEntityFailure = onShowEntityFailure;
|
||||
_onHideEntityComplete = onHideEntityComplete;
|
||||
_onOpenUIFormSuccess = onOpenUIFormSuccess;
|
||||
_onOpenUIFormFailure = onOpenUIFormFailure;
|
||||
_onCloseUIFormComplete = onCloseUIFormComplete;
|
||||
|
||||
EnsureEntityEventSubscribed();
|
||||
EnsureUIEventSubscribed();
|
||||
}
|
||||
|
||||
public void Unbind()
|
||||
{
|
||||
UnsubscribeEntityEvents();
|
||||
UnsubscribeUIEvents();
|
||||
|
||||
_onShowEntitySuccess = null;
|
||||
_onShowEntityFailure = null;
|
||||
_onHideEntityComplete = null;
|
||||
_onOpenUIFormSuccess = null;
|
||||
_onOpenUIFormFailure = null;
|
||||
_onCloseUIFormComplete = null;
|
||||
}
|
||||
|
||||
private void EnsureEntityEventSubscribed()
|
||||
{
|
||||
if (_isEntityEventSubscribed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
|
||||
GameEntry.Event.Subscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure);
|
||||
GameEntry.Event.Subscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
|
||||
_isEntityEventSubscribed = true;
|
||||
}
|
||||
|
||||
private void EnsureUIEventSubscribed()
|
||||
{
|
||||
if (_isUIEventSubscribed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OnOpenUIFormSuccess);
|
||||
GameEntry.Event.Subscribe(OpenUIFormFailureEventArgs.EventId, OnOpenUIFormFailure);
|
||||
GameEntry.Event.Subscribe(CloseUIFormCompleteEventArgs.EventId, OnCloseUIFormComplete);
|
||||
_isUIEventSubscribed = true;
|
||||
}
|
||||
|
||||
private void UnsubscribeEntityEvents()
|
||||
{
|
||||
if (!_isEntityEventSubscribed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
|
||||
GameEntry.Event.Unsubscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure);
|
||||
GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
|
||||
_isEntityEventSubscribed = false;
|
||||
}
|
||||
|
||||
private void UnsubscribeUIEvents()
|
||||
{
|
||||
if (!_isUIEventSubscribed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OnOpenUIFormSuccess);
|
||||
GameEntry.Event.Unsubscribe(OpenUIFormFailureEventArgs.EventId, OnOpenUIFormFailure);
|
||||
GameEntry.Event.Unsubscribe(CloseUIFormCompleteEventArgs.EventId, OnCloseUIFormComplete);
|
||||
_isUIEventSubscribed = false;
|
||||
}
|
||||
|
||||
private void OnShowEntitySuccess(object sender, GameEventArgs e)
|
||||
{
|
||||
if (e is ShowEntitySuccessEventArgs args)
|
||||
{
|
||||
_onShowEntitySuccess?.Invoke(args);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShowEntityFailure(object sender, GameEventArgs e)
|
||||
{
|
||||
if (e is ShowEntityFailureEventArgs args)
|
||||
{
|
||||
_onShowEntityFailure?.Invoke(args);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHideEntityComplete(object sender, GameEventArgs e)
|
||||
{
|
||||
if (e is HideEntityCompleteEventArgs args)
|
||||
{
|
||||
_onHideEntityComplete?.Invoke(args);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOpenUIFormSuccess(object sender, GameEventArgs e)
|
||||
{
|
||||
if (e is OpenUIFormSuccessEventArgs args)
|
||||
{
|
||||
_onOpenUIFormSuccess?.Invoke(args);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOpenUIFormFailure(object sender, GameEventArgs e)
|
||||
{
|
||||
if (e is OpenUIFormFailureEventArgs args)
|
||||
{
|
||||
_onOpenUIFormFailure?.Invoke(args);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCloseUIFormComplete(object sender, GameEventArgs e)
|
||||
{
|
||||
if (e is CloseUIFormCompleteEventArgs args)
|
||||
{
|
||||
_onCloseUIFormComplete?.Invoke(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8f34e0186c449fd8ff7a9d22c8aeb52
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,276 @@
|
||||
using GameFramework.Resource;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Entity;
|
||||
using GeometryTD.Entity.EntityData;
|
||||
using GeometryTD.UI;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.CustomComponent
|
||||
{
|
||||
internal sealed class CombatLoadSession
|
||||
{
|
||||
internal enum EventHandleStatus : byte
|
||||
{
|
||||
Ignored = 0,
|
||||
Succeeded = 1,
|
||||
Failed = 2
|
||||
}
|
||||
|
||||
private EntityComponent _entity;
|
||||
private CombatInfoFormUseCase _useCase;
|
||||
private int _loadingMapEntityId;
|
||||
private int _loadedMapEntityId;
|
||||
private int? _loadingCombatInfoFormSerialId;
|
||||
private bool _isCombatInfoFormReady;
|
||||
private MapEntity _currentMap;
|
||||
|
||||
public MapEntity CurrentMap => _currentMap;
|
||||
public bool IsReady => _currentMap != null && _isCombatInfoFormReady;
|
||||
|
||||
public void OnInit(EntityComponent entityComponent)
|
||||
{
|
||||
_entity = entityComponent;
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_loadingMapEntityId = 0;
|
||||
_loadedMapEntityId = 0;
|
||||
_loadingCombatInfoFormSerialId = null;
|
||||
_isCombatInfoFormReady = false;
|
||||
_currentMap = null;
|
||||
}
|
||||
|
||||
public bool StartLoading(DRLevel level, out string errorMessage)
|
||||
{
|
||||
errorMessage = null;
|
||||
if (_entity == null)
|
||||
{
|
||||
errorMessage = "CombatLoadSession start failed. Entity component is null.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryShowMap(level, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryOpenCombatInfoForm(out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RefreshCombatInfoForm(DRLevel currentLevel)
|
||||
{
|
||||
if (currentLevel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.UIRouter.OpenUI(UIFormType.CombatInfoForm);
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
CloseCombatInfoForm();
|
||||
}
|
||||
|
||||
public EventHandleStatus HandleShowEntitySuccess(ShowEntitySuccessEventArgs args, out string errorMessage)
|
||||
{
|
||||
errorMessage = null;
|
||||
if (args == null)
|
||||
{
|
||||
return EventHandleStatus.Ignored;
|
||||
}
|
||||
|
||||
if (args.EntityLogicType != typeof(MapEntity) || args.Entity == null || args.Entity.Id != _loadingMapEntityId)
|
||||
{
|
||||
return EventHandleStatus.Ignored;
|
||||
}
|
||||
|
||||
_loadedMapEntityId = _loadingMapEntityId;
|
||||
_loadingMapEntityId = 0;
|
||||
_currentMap = args.Entity.Logic as MapEntity;
|
||||
if (_currentMap == null)
|
||||
{
|
||||
errorMessage = $"Loaded map entity logic is invalid. EntityId={args.Entity.Id}.";
|
||||
return EventHandleStatus.Failed;
|
||||
}
|
||||
|
||||
return EventHandleStatus.Succeeded;
|
||||
}
|
||||
|
||||
public EventHandleStatus HandleShowEntityFailure(ShowEntityFailureEventArgs args, out string errorMessage)
|
||||
{
|
||||
errorMessage = null;
|
||||
if (args == null)
|
||||
{
|
||||
return EventHandleStatus.Ignored;
|
||||
}
|
||||
|
||||
if (args.EntityLogicType != typeof(MapEntity) || args.EntityId != _loadingMapEntityId)
|
||||
{
|
||||
return EventHandleStatus.Ignored;
|
||||
}
|
||||
|
||||
_loadingMapEntityId = 0;
|
||||
_currentMap = null;
|
||||
errorMessage = $"Map load failed. Asset='{args.EntityAssetName}', Error='{args.ErrorMessage}'.";
|
||||
return EventHandleStatus.Failed;
|
||||
}
|
||||
|
||||
public void HandleHideEntityComplete(HideEntityCompleteEventArgs args)
|
||||
{
|
||||
if (args == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.EntityId == _loadedMapEntityId)
|
||||
{
|
||||
_loadedMapEntityId = 0;
|
||||
_currentMap = null;
|
||||
}
|
||||
|
||||
if (args.EntityId == _loadingMapEntityId)
|
||||
{
|
||||
_loadingMapEntityId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public EventHandleStatus HandleOpenUIFormSuccess(OpenUIFormSuccessEventArgs args, out string errorMessage)
|
||||
{
|
||||
errorMessage = null;
|
||||
if (args == null || !_loadingCombatInfoFormSerialId.HasValue || args.UIForm == null)
|
||||
{
|
||||
return EventHandleStatus.Ignored;
|
||||
}
|
||||
|
||||
if (args.UIForm.SerialId != _loadingCombatInfoFormSerialId.Value)
|
||||
{
|
||||
return EventHandleStatus.Ignored;
|
||||
}
|
||||
|
||||
_isCombatInfoFormReady = args.UIForm.Logic is CombatInfoForm;
|
||||
if (!_isCombatInfoFormReady)
|
||||
{
|
||||
errorMessage = "CombatInfoForm open success but form logic type is invalid.";
|
||||
return EventHandleStatus.Failed;
|
||||
}
|
||||
|
||||
return EventHandleStatus.Succeeded;
|
||||
}
|
||||
|
||||
public EventHandleStatus HandleOpenUIFormFailure(OpenUIFormFailureEventArgs args, out string errorMessage)
|
||||
{
|
||||
errorMessage = null;
|
||||
if (args == null || !_loadingCombatInfoFormSerialId.HasValue)
|
||||
{
|
||||
return EventHandleStatus.Ignored;
|
||||
}
|
||||
|
||||
if (args.SerialId != _loadingCombatInfoFormSerialId.Value)
|
||||
{
|
||||
return EventHandleStatus.Ignored;
|
||||
}
|
||||
|
||||
_isCombatInfoFormReady = false;
|
||||
errorMessage = $"CombatInfoForm load failed. Asset='{args.UIFormAssetName}', Error='{args.ErrorMessage}'.";
|
||||
return EventHandleStatus.Failed;
|
||||
}
|
||||
|
||||
public void HandleCloseUIFormComplete(CloseUIFormCompleteEventArgs args)
|
||||
{
|
||||
if (args == null || !_loadingCombatInfoFormSerialId.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.SerialId == _loadingCombatInfoFormSerialId.Value)
|
||||
{
|
||||
_loadingCombatInfoFormSerialId = null;
|
||||
_isCombatInfoFormReady = false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryShowMap(DRLevel level, out string errorMessage)
|
||||
{
|
||||
errorMessage = null;
|
||||
if (level == null)
|
||||
{
|
||||
errorMessage = "CombatLoadSession map load failed. Level is null.";
|
||||
return false;
|
||||
}
|
||||
|
||||
string mapAssetName = level.Id.ToString();
|
||||
string mapAssetPath = AssetUtility.GetLevelMapAsset(mapAssetName);
|
||||
if (GameEntry.Resource.HasAsset(mapAssetPath) == HasAssetResult.NotExist)
|
||||
{
|
||||
errorMessage = $"CombatLoadSession map asset not found. Level='{level.Id}', Asset='{mapAssetPath}'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
_loadingMapEntityId = _entity.GenerateSerialId();
|
||||
_entity.ShowMap(new MapData(_loadingMapEntityId, level.Id, Vector3.zero), mapAssetName);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryOpenCombatInfoForm(out string errorMessage)
|
||||
{
|
||||
errorMessage = null;
|
||||
if (_useCase == null)
|
||||
{
|
||||
_useCase = new CombatInfoFormUseCase();
|
||||
GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatInfoForm, _useCase);
|
||||
}
|
||||
|
||||
int? serialId = GameEntry.UIRouter.OpenUI(UIFormType.CombatInfoForm);
|
||||
if (!serialId.HasValue)
|
||||
{
|
||||
errorMessage = "CombatLoadSession failed to open CombatInfoForm.";
|
||||
return false;
|
||||
}
|
||||
|
||||
_loadingCombatInfoFormSerialId = serialId.Value;
|
||||
_isCombatInfoFormReady = GameEntry.UI.HasUIForm(serialId.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CloseCombatInfoForm()
|
||||
{
|
||||
GameEntry.UIRouter.CloseUI(UIFormType.CombatInfoForm);
|
||||
_loadingCombatInfoFormSerialId = null;
|
||||
_isCombatInfoFormReady = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36fe6a0f3baf4f69a1b131cd9b6fd223
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -26,16 +26,46 @@ namespace GeometryTD.CustomComponent
|
||||
private readonly CombatScheduler _combatScheduler = new CombatScheduler();
|
||||
|
||||
private bool _runtimeInitialized;
|
||||
private int _currentCoin;
|
||||
|
||||
public LevelThemeType CurrentThemeType { get; private set; }
|
||||
public DRLevel CurrentLevel { get; private set; }
|
||||
public int CurrentCoin => _currentCoin;
|
||||
public bool CanEndCombat => _combatScheduler.CanEndCombat;
|
||||
|
||||
public int CurrentLevelPhaseCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CurrentLevel == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!_phasesByLevelId.TryGetValue(CurrentLevel.Id, out List<DRLevelPhase> phases) || phases == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return phases.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public int CurrentPhaseIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return _combatScheduler.DisplayPhaseIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnInit(LevelThemeType themeType)
|
||||
{
|
||||
ShutdownBattleRuntime();
|
||||
ShutdownCombatRuntime();
|
||||
|
||||
CurrentThemeType = themeType;
|
||||
CurrentLevel = null;
|
||||
_currentCoin = 0;
|
||||
_levelsById.Clear();
|
||||
_phasesByLevelId.Clear();
|
||||
_spawnEntriesByPhaseId.Clear();
|
||||
@@ -129,12 +159,12 @@ namespace GeometryTD.CustomComponent
|
||||
CountPhases(),
|
||||
CountEntries());
|
||||
|
||||
EnsureBattleRuntimeInitialized();
|
||||
EnsureCombatRuntimeInitialized();
|
||||
}
|
||||
|
||||
public void StartCombat()
|
||||
{
|
||||
if (!EnsureBattleRuntimeInitialized())
|
||||
if (!EnsureCombatRuntimeInitialized())
|
||||
{
|
||||
Log.Warning("CombatNodeComponent start failed. Missing scheduler runtime.");
|
||||
return;
|
||||
@@ -169,15 +199,22 @@ namespace GeometryTD.CustomComponent
|
||||
}
|
||||
|
||||
CurrentLevel = selectedLevel;
|
||||
_currentCoin = Mathf.Max(0, selectedLevel.StartCoin);
|
||||
_combatScheduler.Start(selectedLevel, phaseList, _selectedSpawnEntriesByPhaseId);
|
||||
GameEntry.Event.Fire(this, NodeEnterEventArgs.Create());
|
||||
}
|
||||
|
||||
public void EndCombat()
|
||||
{
|
||||
_currentCoin = 0;
|
||||
GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create());
|
||||
}
|
||||
|
||||
public bool TryEndCombatByPlayer()
|
||||
{
|
||||
return _combatScheduler.TryEndCombatByPlayer();
|
||||
}
|
||||
|
||||
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
if (!_runtimeInitialized)
|
||||
@@ -191,7 +228,8 @@ namespace GeometryTD.CustomComponent
|
||||
public void OnShutdown()
|
||||
{
|
||||
CurrentLevel = null;
|
||||
ShutdownBattleRuntime();
|
||||
_currentCoin = 0;
|
||||
ShutdownCombatRuntime();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
@@ -199,7 +237,7 @@ namespace GeometryTD.CustomComponent
|
||||
OnShutdown();
|
||||
}
|
||||
|
||||
private bool EnsureBattleRuntimeInitialized()
|
||||
private bool EnsureCombatRuntimeInitialized()
|
||||
{
|
||||
if (_runtimeInitialized)
|
||||
{
|
||||
@@ -211,7 +249,7 @@ namespace GeometryTD.CustomComponent
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ShutdownBattleRuntime()
|
||||
private void ShutdownCombatRuntime()
|
||||
{
|
||||
if (!_runtimeInitialized)
|
||||
{
|
||||
@@ -257,4 +295,4 @@ namespace GeometryTD.CustomComponent
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using GameFramework.Event;
|
||||
using GameFramework.Resource;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Entity;
|
||||
using GeometryTD.Entity.EntityData;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.CustomComponent
|
||||
@@ -17,43 +10,46 @@ namespace GeometryTD.CustomComponent
|
||||
private enum SchedulerState : byte
|
||||
{
|
||||
Idle = 0,
|
||||
WaitingForMap = 1,
|
||||
WaitingForLoading = 1,
|
||||
RunningPhase = 2,
|
||||
Completed = 3,
|
||||
Failed = 4
|
||||
}
|
||||
|
||||
private readonly List<DRLevelPhase> _phaseBuffer = new();
|
||||
|
||||
private readonly Dictionary<int, IReadOnlyList<DRLevelSpawnEntry>> _spawnEntriesByPhaseId = new();
|
||||
|
||||
private readonly EnemyManager _enemyManager = new EnemyManager();
|
||||
private readonly EnemyManager _enemyManager = new();
|
||||
private readonly PhaseLoopRuntime _phaseLoopRuntime = new();
|
||||
private readonly CombatLoadSession _loadSession = new();
|
||||
private readonly CombatEventBridge _eventBridge = new();
|
||||
|
||||
private EntityComponent _entity;
|
||||
private DRLevel _currentLevel;
|
||||
private DRLevelPhase _currentPhase;
|
||||
private int _currentPhaseIndex;
|
||||
private float _currentPhaseElapsed;
|
||||
private SchedulerState _state = SchedulerState.Idle;
|
||||
private int _loadingMapEntityId;
|
||||
private int _loadedMapEntityId;
|
||||
private bool _initialized;
|
||||
private bool _isEntityEventSubscribed;
|
||||
private MapEntity _currentMap;
|
||||
|
||||
public bool IsRunning => _state == SchedulerState.WaitingForMap || _state == SchedulerState.RunningPhase;
|
||||
public bool IsRunning => _state == SchedulerState.WaitingForLoading || _state == SchedulerState.RunningPhase;
|
||||
public bool IsCompleted => _state == SchedulerState.Completed;
|
||||
public DRLevel CurrentLevel => _currentLevel;
|
||||
public DRLevelPhase CurrentPhase => _currentPhase;
|
||||
public MapEntity CurrentMap => _currentMap;
|
||||
public DRLevelPhase CurrentPhase => _phaseLoopRuntime.CurrentPhase;
|
||||
public MapEntity CurrentMap => _loadSession.CurrentMap;
|
||||
public int DisplayPhaseIndex => _phaseLoopRuntime.DisplayPhaseIndex;
|
||||
public bool CanEndCombat => _phaseLoopRuntime.CanEndCombat;
|
||||
|
||||
public void OnInit()
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
_entity = GameEntry.Entity;
|
||||
EnsureEntityEventSubscribed();
|
||||
_eventBridge.Bind(
|
||||
OnShowEntitySuccess,
|
||||
OnShowEntityFailure,
|
||||
OnHideEntityComplete,
|
||||
OnOpenUIFormSuccess,
|
||||
OnOpenUIFormFailure,
|
||||
OnCloseUIFormComplete);
|
||||
_enemyManager.OnInit(this);
|
||||
_loadSession.OnInit(_entity);
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@@ -79,14 +75,18 @@ namespace GeometryTD.CustomComponent
|
||||
return;
|
||||
}
|
||||
|
||||
CleanupBattleEntities();
|
||||
CleanupCombatEntities();
|
||||
_enemyManager.EndPhase();
|
||||
ResetRuntime();
|
||||
|
||||
_currentLevel = level;
|
||||
foreach (var phase in phases)
|
||||
for (int i = 0; i < phases.Count; i++)
|
||||
{
|
||||
_phaseBuffer.Add(phase);
|
||||
DRLevelPhase phase = phases[i];
|
||||
if (phase != null)
|
||||
{
|
||||
_phaseBuffer.Add(phase);
|
||||
}
|
||||
}
|
||||
|
||||
if (spawnEntriesByPhaseId != null)
|
||||
@@ -97,33 +97,31 @@ namespace GeometryTD.CustomComponent
|
||||
}
|
||||
}
|
||||
|
||||
if (_phaseBuffer.Count <= 0)
|
||||
_phaseLoopRuntime.SetPhases(_phaseBuffer);
|
||||
if (_phaseLoopRuntime.PhaseCount <= 0)
|
||||
{
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Warning("CombatScheduler start failed. Level '{0}' has no phase data.", level.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryShowMap(level))
|
||||
if (!_loadSession.StartLoading(level, out string loadError))
|
||||
{
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Warning("CombatScheduler start failed. {0}", loadError);
|
||||
return;
|
||||
}
|
||||
|
||||
_currentPhase = null;
|
||||
_currentPhaseIndex = -1;
|
||||
_currentPhaseElapsed = 0f;
|
||||
_state = SchedulerState.WaitingForMap;
|
||||
|
||||
Log.Info("CombatScheduler started. Level={0}, PhaseCount={1}.", _currentLevel.Id, _phaseBuffer.Count);
|
||||
_state = SchedulerState.WaitingForLoading;
|
||||
Log.Info("CombatScheduler started. Level={0}, PhaseCount={1}.", _currentLevel.Id, _phaseLoopRuntime.PhaseCount);
|
||||
}
|
||||
|
||||
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
case SchedulerState.WaitingForMap:
|
||||
if (_currentMap == null)
|
||||
case SchedulerState.WaitingForLoading:
|
||||
if (!_loadSession.IsReady)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -145,51 +143,49 @@ namespace GeometryTD.CustomComponent
|
||||
return;
|
||||
}
|
||||
|
||||
CleanupBattleEntities();
|
||||
CleanupCombatEntities();
|
||||
_enemyManager.OnDestroy();
|
||||
ResetRuntime();
|
||||
UnsubscribeEntityEvents();
|
||||
_eventBridge.Unbind();
|
||||
_entity = null;
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
private bool TryShowMap(DRLevel level)
|
||||
public bool TryEndCombatByPlayer()
|
||||
{
|
||||
string mapAssetName = level.Id.ToString();
|
||||
string mapAssetPath = AssetUtility.GetLevelMapAsset(mapAssetName);
|
||||
if (GameEntry.Resource.HasAsset(mapAssetPath) == HasAssetResult.NotExist)
|
||||
if (_state == SchedulerState.Completed || _state == SchedulerState.Failed || _state == SchedulerState.Idle)
|
||||
{
|
||||
Log.Warning(
|
||||
"CombatScheduler start failed. Level '{0}' map asset not found: '{1}'.",
|
||||
level.Id,
|
||||
mapAssetPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
_loadingMapEntityId = _entity.GenerateSerialId();
|
||||
_entity.ShowMap(new MapData(_loadingMapEntityId, level.Id, Vector3.zero), mapAssetName);
|
||||
if (!_phaseLoopRuntime.TryRequestEndCombat())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.Info(
|
||||
"CombatScheduler loading map. Level={0}, Asset='{1}', EntityId={2}.",
|
||||
level.Id,
|
||||
mapAssetPath,
|
||||
_loadingMapEntityId);
|
||||
FinishCombat("Combat ended by player.");
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RefreshCombatInfoForm()
|
||||
{
|
||||
_loadSession.RefreshCombatInfoForm(_currentLevel);
|
||||
}
|
||||
|
||||
private void UpdateCurrentPhase(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
if (_currentPhase == null)
|
||||
DRLevelPhase currentPhase = _phaseLoopRuntime.CurrentPhase;
|
||||
if (currentPhase == null)
|
||||
{
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Warning("CombatScheduler update failed. Current phase is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
_currentPhaseElapsed += elapseSeconds;
|
||||
_phaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
|
||||
_enemyManager.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
if (!ShouldEndCurrentPhase())
|
||||
if (!_phaseLoopRuntime.ShouldEndCurrentPhase(_enemyManager.IsPhaseSpawnCompleted, _enemyManager.AliveEnemyCount))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -197,229 +193,134 @@ namespace GeometryTD.CustomComponent
|
||||
CompleteCurrentPhase();
|
||||
}
|
||||
|
||||
private bool ShouldEndCurrentPhase()
|
||||
{
|
||||
switch (_currentPhase.EndType)
|
||||
{
|
||||
case PhaseEndType.TimeElapsed:
|
||||
return _currentPhaseElapsed >= ResolveTimeElapsedThreshold(_currentPhase);
|
||||
case PhaseEndType.EnemiesCleared:
|
||||
case PhaseEndType.BossDead:
|
||||
return _enemyManager.IsPhaseSpawnCompleted && _enemyManager.AliveEnemyCount <= 0;
|
||||
case PhaseEndType.None:
|
||||
default:
|
||||
if (_currentPhase.DurationSeconds > 0 && _currentPhaseElapsed >= _currentPhase.DurationSeconds)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _enemyManager.IsPhaseSpawnCompleted && _enemyManager.AliveEnemyCount <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
private float ResolveTimeElapsedThreshold(DRLevelPhase phase)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(phase.EndParam) &&
|
||||
float.TryParse(phase.EndParam, NumberStyles.Float, CultureInfo.InvariantCulture,
|
||||
out float parsedSeconds))
|
||||
{
|
||||
return parsedSeconds;
|
||||
}
|
||||
|
||||
if (phase.DurationSeconds > 0)
|
||||
{
|
||||
return phase.DurationSeconds;
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
private void CompleteCurrentPhase()
|
||||
{
|
||||
_enemyManager.EndPhase();
|
||||
Log.Info(
|
||||
"CombatScheduler phase completed. Level={0}, Phase={1}, Elapsed={2:F2}s.",
|
||||
_currentLevel != null ? _currentLevel.Id : 0,
|
||||
_currentPhase != null ? _currentPhase.Id : 0,
|
||||
_currentPhaseElapsed);
|
||||
_phaseLoopRuntime.CurrentPhase != null ? _phaseLoopRuntime.CurrentPhase.Id : 0,
|
||||
_phaseLoopRuntime.CurrentPhaseElapsed);
|
||||
|
||||
BeginNextPhase();
|
||||
}
|
||||
|
||||
private void BeginNextPhase()
|
||||
{
|
||||
_currentPhaseIndex++;
|
||||
if (_currentPhaseIndex >= _phaseBuffer.Count)
|
||||
if (!_phaseLoopRuntime.TryEnterNextPhase(out DRLevelPhase nextPhase))
|
||||
{
|
||||
_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();
|
||||
FinishCombat("Combat ended after loop completion.");
|
||||
return;
|
||||
}
|
||||
|
||||
_currentPhase = _phaseBuffer[_currentPhaseIndex];
|
||||
_currentPhaseElapsed = 0f;
|
||||
RefreshCombatInfoForm();
|
||||
|
||||
IReadOnlyList<DRLevelSpawnEntry> spawnEntries = null;
|
||||
_spawnEntriesByPhaseId.TryGetValue(_currentPhase.Id, out spawnEntries);
|
||||
_enemyManager.BeginPhase(_currentPhase, spawnEntries);
|
||||
_spawnEntriesByPhaseId.TryGetValue(nextPhase.Id, out IReadOnlyList<DRLevelSpawnEntry> spawnEntries);
|
||||
_enemyManager.BeginPhase(nextPhase, spawnEntries);
|
||||
_state = SchedulerState.RunningPhase;
|
||||
|
||||
Log.Info(
|
||||
"CombatScheduler phase started. Level={0}, Phase={1}, EndType={2}, Entries={3}.",
|
||||
_currentLevel != null ? _currentLevel.Id : 0,
|
||||
_currentPhase.Id,
|
||||
_currentPhase.EndType,
|
||||
_phaseLoopRuntime.DisplayPhaseIndex,
|
||||
nextPhase.EndType,
|
||||
spawnEntries != null ? spawnEntries.Count : 0);
|
||||
}
|
||||
|
||||
private void EnsureEntityEventSubscribed()
|
||||
private void FinishCombat(string reason)
|
||||
{
|
||||
if (_isEntityEventSubscribed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_state = SchedulerState.Completed;
|
||||
CleanupCombatEntities();
|
||||
_enemyManager.EndPhase();
|
||||
|
||||
GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
|
||||
GameEntry.Event.Subscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure);
|
||||
GameEntry.Event.Subscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
|
||||
_isEntityEventSubscribed = true;
|
||||
}
|
||||
|
||||
private void UnsubscribeEntityEvents()
|
||||
{
|
||||
if (!_isEntityEventSubscribed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
|
||||
GameEntry.Event.Unsubscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure);
|
||||
GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
|
||||
_isEntityEventSubscribed = false;
|
||||
Log.Info(
|
||||
"CombatScheduler level completed. Level={0}. Reason={1}",
|
||||
_currentLevel != null ? _currentLevel.Id : 0,
|
||||
reason);
|
||||
GameEntry.CombatNode.EndCombat();
|
||||
}
|
||||
|
||||
private void ResetRuntime()
|
||||
{
|
||||
_phaseBuffer.Clear();
|
||||
_spawnEntriesByPhaseId.Clear();
|
||||
_phaseLoopRuntime.Reset();
|
||||
_loadSession.Reset();
|
||||
_currentLevel = null;
|
||||
_currentPhase = null;
|
||||
_currentPhaseIndex = -1;
|
||||
_currentPhaseElapsed = 0f;
|
||||
_loadingMapEntityId = 0;
|
||||
_loadedMapEntityId = 0;
|
||||
_currentMap = null;
|
||||
_state = SchedulerState.Idle;
|
||||
}
|
||||
|
||||
private void CleanupBattleEntities()
|
||||
private void CleanupCombatEntities()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
_loadSession.Cleanup();
|
||||
_enemyManager.CleanupTrackedEnemies();
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
private void OnShowEntitySuccess(object sender, GameEventArgs e)
|
||||
private void OnShowEntitySuccess(ShowEntitySuccessEventArgs args)
|
||||
{
|
||||
if (!(e is ShowEntitySuccessEventArgs ne))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ne.EntityLogicType != typeof(MapEntity) || ne.Entity.Id != _loadingMapEntityId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_loadedMapEntityId = _loadingMapEntityId;
|
||||
_loadingMapEntityId = 0;
|
||||
_currentMap = ne.Entity.Logic as MapEntity;
|
||||
if (_currentMap == null)
|
||||
var status = _loadSession.HandleShowEntitySuccess(args, out string errorMessage);
|
||||
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
||||
{
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Error("Loaded map entity logic is invalid. EntityId={0}.", ne.Entity.Id);
|
||||
Log.Error("CombatScheduler failed. {0}", errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Info(
|
||||
"Map ready. LevelId={0}, PathCells={1}, FoundationCells={2}, Spawners={3}, House={4}.",
|
||||
_currentLevel != null ? _currentLevel.Id : 0,
|
||||
_currentMap.PathCells.Count,
|
||||
_currentMap.FoundationCells.Count,
|
||||
_currentMap.Spawners.Length,
|
||||
_currentMap.House != null ? _currentMap.House.name : "None");
|
||||
if (status == CombatLoadSession.EventHandleStatus.Succeeded)
|
||||
{
|
||||
MapEntity map = _loadSession.CurrentMap;
|
||||
Log.Info(
|
||||
"Map ready. LevelId={0}, PathCells={1}, FoundationCells={2}, Spawners={3}, House={4}.",
|
||||
_currentLevel != null ? _currentLevel.Id : 0,
|
||||
map.PathCells.Count,
|
||||
map.FoundationCells.Count,
|
||||
map.Spawners.Length,
|
||||
map.House != null ? map.House.name : "None");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShowEntityFailure(object sender, GameEventArgs e)
|
||||
private void OnShowEntityFailure(ShowEntityFailureEventArgs args)
|
||||
{
|
||||
if (!(e is ShowEntityFailureEventArgs ne))
|
||||
var status = _loadSession.HandleShowEntityFailure(args, out string errorMessage);
|
||||
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
||||
{
|
||||
return;
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Error("CombatScheduler failed. LevelId={0}, {1}", _currentLevel != null ? _currentLevel.Id : 0, errorMessage);
|
||||
}
|
||||
|
||||
if (ne.EntityLogicType != typeof(MapEntity) || ne.EntityId != _loadingMapEntityId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_loadingMapEntityId = 0;
|
||||
_currentMap = null;
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Error(
|
||||
"Map load failed. LevelId={0}, Asset='{1}', Error='{2}'.",
|
||||
_currentLevel != null ? _currentLevel.Id : 0,
|
||||
ne.EntityAssetName,
|
||||
ne.ErrorMessage);
|
||||
}
|
||||
|
||||
private void OnHideEntityComplete(object sender, GameEventArgs e)
|
||||
private void OnHideEntityComplete(HideEntityCompleteEventArgs args)
|
||||
{
|
||||
if (!(e is HideEntityCompleteEventArgs ne))
|
||||
{
|
||||
return;
|
||||
}
|
||||
_loadSession.HandleHideEntityComplete(args);
|
||||
}
|
||||
|
||||
if (ne.EntityId == _loadedMapEntityId)
|
||||
private void OnOpenUIFormSuccess(OpenUIFormSuccessEventArgs args)
|
||||
{
|
||||
var status = _loadSession.HandleOpenUIFormSuccess(args, out string errorMessage);
|
||||
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
||||
{
|
||||
_loadedMapEntityId = 0;
|
||||
_currentMap = null;
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Error("CombatScheduler failed. {0}", errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (ne.EntityId == _loadingMapEntityId)
|
||||
private void OnOpenUIFormFailure(OpenUIFormFailureEventArgs args)
|
||||
{
|
||||
var status = _loadSession.HandleOpenUIFormFailure(args, out string errorMessage);
|
||||
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
||||
{
|
||||
_loadingMapEntityId = 0;
|
||||
_state = SchedulerState.Failed;
|
||||
Log.Error("CombatScheduler failed. {0}", errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCloseUIFormComplete(CloseUIFormCompleteEventArgs args)
|
||||
{
|
||||
_loadSession.HandleCloseUIFormComplete(args);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.CustomComponent
|
||||
{
|
||||
internal sealed class PhaseLoopRuntime
|
||||
{
|
||||
private readonly List<DRLevelPhase> _phases = new();
|
||||
|
||||
private DRLevelPhase _currentPhase;
|
||||
private int _currentPhaseIndex = -1;
|
||||
private int _displayPhaseIndex;
|
||||
private int _completedLoopCount;
|
||||
private bool _canEndCombat;
|
||||
private bool _endCombatRequested;
|
||||
private float _currentPhaseElapsed;
|
||||
|
||||
public DRLevelPhase CurrentPhase => _currentPhase;
|
||||
public int DisplayPhaseIndex => _displayPhaseIndex;
|
||||
public bool CanEndCombat => _canEndCombat;
|
||||
public float CurrentPhaseElapsed => _currentPhaseElapsed;
|
||||
public int PhaseCount => _phases.Count;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_phases.Clear();
|
||||
_currentPhase = null;
|
||||
_currentPhaseIndex = -1;
|
||||
_displayPhaseIndex = 0;
|
||||
_completedLoopCount = 0;
|
||||
_canEndCombat = false;
|
||||
_endCombatRequested = false;
|
||||
_currentPhaseElapsed = 0f;
|
||||
}
|
||||
|
||||
public void SetPhases(IReadOnlyList<DRLevelPhase> phases)
|
||||
{
|
||||
_phases.Clear();
|
||||
if (phases == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < phases.Count; i++)
|
||||
{
|
||||
DRLevelPhase phase = phases[i];
|
||||
if (phase != null)
|
||||
{
|
||||
_phases.Add(phase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryRequestEndCombat()
|
||||
{
|
||||
if (!_canEndCombat)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_endCombatRequested = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AdvancePhaseElapsed(float elapseSeconds)
|
||||
{
|
||||
_currentPhaseElapsed += elapseSeconds;
|
||||
}
|
||||
|
||||
public bool ShouldEndCurrentPhase(bool isPhaseSpawnCompleted, int aliveEnemyCount)
|
||||
{
|
||||
if (_currentPhase == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (_currentPhase.EndType)
|
||||
{
|
||||
case PhaseEndType.TimeElapsed:
|
||||
return _currentPhaseElapsed >= ResolveTimeElapsedThreshold(_currentPhase);
|
||||
case PhaseEndType.EnemiesCleared:
|
||||
case PhaseEndType.BossDead:
|
||||
return isPhaseSpawnCompleted && aliveEnemyCount <= 0;
|
||||
case PhaseEndType.None:
|
||||
default:
|
||||
if (_currentPhase.DurationSeconds > 0 && _currentPhaseElapsed >= _currentPhase.DurationSeconds)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return isPhaseSpawnCompleted && aliveEnemyCount <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryEnterNextPhase(out DRLevelPhase nextPhase)
|
||||
{
|
||||
nextPhase = null;
|
||||
if (_phases.Count <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_currentPhaseIndex++;
|
||||
if (_currentPhaseIndex >= _phases.Count)
|
||||
{
|
||||
_completedLoopCount++;
|
||||
_canEndCombat = true;
|
||||
|
||||
if (_endCombatRequested)
|
||||
{
|
||||
_currentPhase = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
_currentPhaseIndex = 0;
|
||||
}
|
||||
|
||||
_currentPhase = _phases[_currentPhaseIndex];
|
||||
_displayPhaseIndex = _completedLoopCount * _phases.Count + _currentPhaseIndex + 1;
|
||||
_currentPhaseElapsed = 0f;
|
||||
nextPhase = _currentPhase;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static float ResolveTimeElapsedThreshold(DRLevelPhase phase)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(phase.EndParam) &&
|
||||
float.TryParse(phase.EndParam, NumberStyles.Float, CultureInfo.InvariantCulture, out float parsed))
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
|
||||
if (phase.DurationSeconds > 0)
|
||||
{
|
||||
return phase.DurationSeconds;
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2bdde0f128bc4f249a3e1254b8fd2d06
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user