refactor & fix

- 将 `CombatScheduler` 的职责分散到 3 个类中
    - `PhaseLoopRuntime`:负责关卡各波次的调度
    - `CombatLoadSession`:负责地图与 UI 等资源的加载
    - `CombatEventBridge`:负责相关事件的处理
- 统一了战斗的命名为 `Combat`
- 调整了各个地图的坐标,使其位于摄像头中心
- 新增了 CombatInfoForm 展示战斗内的信息
- 补充了循环执行 LevelPhase,手动结束战斗的逻辑
This commit is contained in:
SepComet
2026-03-01 13:15:53 +08:00
parent 26dc1a5600
commit 92cca14503
99 changed files with 2418 additions and 243 deletions
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 638583d0231ea31459f5c3dd4e934ce5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f613b24c1e065e542b7bb847939acba3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,11 @@
namespace GeometryTD.UI
{
public class CombatInfoFormContext : UIContext
{
public string LevelMetaText;
public string LevelPhaseText;
public string CoinText;
public bool CanPause;
public bool CanEnd;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0f486ea7237d4edb9e56147cf540ae69
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a49c1dd9119c8d34c95ea2a22dcd7d2c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,151 @@
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using GameFramework.Event;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class CombatInfoFormController : UIFormControllerCommonBase<CombatInfoFormContext, CombatInfoForm>
{
private CombatInfoFormUseCase _useCase;
protected override UIFormType UIFormTypeId => UIFormType.CombatInfoForm;
protected override void RefreshUI(CombatInfoForm form, CombatInfoFormContext context)
{
form.RefreshUI(context);
}
protected override void SubscribeCustomEvents()
{
GameEntry.Event.Subscribe(CombatPauseEventArgs.EventId, OnCombatPauseButtonClicked);
GameEntry.Event.Subscribe(CombatEndEventArgs.EventId, OnCombatEndButtonClicked);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(CombatPauseEventArgs.EventId, OnCombatPauseButtonClicked);
GameEntry.Event.Unsubscribe(CombatEndEventArgs.EventId, OnCombatEndButtonClicked);
}
public int? OpenUI(CombatInfoFormRawData rawData)
{
CombatInfoFormContext context = BuildContext(rawData);
return OpenUIInternal(context);
}
public override int? OpenUI(object userData = null)
{
if (userData is CombatInfoFormContext context)
{
return OpenUIInternal(context);
}
if (userData is CombatInfoFormRawData rawDataFromUserData)
{
return OpenUI(rawDataFromUserData);
}
if (userData != null)
{
Log.Warning("CombatInfoFormController.OpenUI() userData type is invalid.");
return null;
}
if (_useCase == null)
{
Log.Error("CombatInfoFormController.OpenUI() useCase is null.");
return null;
}
CombatInfoFormRawData rawData = _useCase.CreateInitialModel();
return OpenUI(rawData);
}
public override void BindUseCase(IUIUseCase useCase)
{
if (!(useCase is CombatInfoFormUseCase combatInfoFormUseCase))
{
Log.Error("CombatInfoFormController.BindUseCase() useCase is invalid.");
return;
}
_useCase = combatInfoFormUseCase;
}
private static CombatInfoFormContext BuildContext(CombatInfoFormRawData rawData)
{
if (rawData == null)
{
return null;
}
return new CombatInfoFormContext
{
LevelMetaText = BuildLevelMetaText(rawData.LevelThemeType, rawData.LevelId),
LevelPhaseText = BuildPhaseText(rawData.CurrentPhaseIndex, rawData.TotalPhaseCount),
CoinText = BuildCoinText(rawData.Coin),
CanPause = rawData.CanPause,
CanEnd = rawData.CanEnd
};
}
private static string BuildLevelMetaText(LevelThemeType themeType, int levelId)
{
if (levelId <= 0)
{
return "Map -";
}
return $"{themeType} Map.{levelId}";
}
private static string BuildPhaseText(int currentPhaseIndex, int totalPhaseCount)
{
int phaseIndex = currentPhaseIndex < 0 ? 0 : currentPhaseIndex;
if (totalPhaseCount <= 0)
{
return $"{phaseIndex}/?";
}
return $"Wave: {phaseIndex}/{totalPhaseCount}";
}
private static string BuildCoinText(int coin)
{
if (coin < 0)
{
return "Coin: -";
}
return $"Coin: {coin}";
}
private void OnCombatPauseButtonClicked(object sender, GameEventArgs e)
{
if (!(sender is CombatInfoForm) || !(e is CombatPauseEventArgs))
{
return;
}
GameEntry.UIRouter.OpenUI(UIFormType.DialogForm, new DialogFormRawData
{
Mode = 1,
Title = "Pause",
Message = "Game paused.",
PauseGame = true,
ConfirmText = "Continue"
});
}
private void OnCombatEndButtonClicked(object sender, GameEventArgs e)
{
if (!(sender is CombatInfoForm) || !(e is CombatEndEventArgs))
{
return;
}
_useCase?.TryEndCombat();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cb66e5d005d549938b7b4bb68c6d66e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ca167152dba83864bbcd398e6d984d89
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using GeometryTD.Definition;
namespace GeometryTD.UI
{
public class CombatInfoFormRawData
{
public LevelThemeType LevelThemeType;
public int LevelId;
public int CurrentPhaseIndex;
public int TotalPhaseCount;
public int Coin;
public bool CanPause;
public bool CanEnd;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cf24b0e63c6b4863a90d4a3261378106
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 28613671adff3fc44b3c6ace7957869d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,50 @@
using GeometryTD.Definition;
namespace GeometryTD.UI
{
public class CombatInfoFormUseCase : IUIUseCase
{
public CombatInfoFormRawData CreateInitialModel()
{
return BuildModel();
}
public CombatInfoFormRawData TryRefresh()
{
return BuildModel();
}
public bool TryEndCombat()
{
if (GameEntry.CombatNode == null || !GameEntry.CombatNode.CanEndCombat)
{
return false;
}
return GameEntry.CombatNode.TryEndCombatByPlayer();
}
private static CombatInfoFormRawData BuildModel()
{
if (GameEntry.CombatNode == null)
{
return null;
}
var level = GameEntry.CombatNode.CurrentLevel;
LevelThemeType themeType = level != null ? level.LevelThemeType : GameEntry.CombatNode.CurrentThemeType;
int levelId = level != null ? level.Id : 0;
return new CombatInfoFormRawData
{
LevelThemeType = themeType,
LevelId = levelId,
CurrentPhaseIndex = GameEntry.CombatNode.CurrentPhaseIndex,
TotalPhaseCount = GameEntry.CombatNode.CurrentLevelPhaseCount,
Coin = GameEntry.CombatNode.CurrentCoin,
CanPause = true,
CanEnd = GameEntry.CombatNode.CanEndCombat
};
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7bc6a5500e554439bf8c760a779ae87c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f1d770766799a63488692f296e0d42f7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,107 @@
using GeometryTD.CustomEvent;
using TMPro;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class CombatInfoForm : UGuiForm
{
[SerializeField] private TMP_Text _levelMetaText;
[SerializeField] private TMP_Text _levelPhaseText;
[SerializeField] private TMP_Text _coinText;
[SerializeField] private CommonButton _pauseButton;
[SerializeField] private CommonButton _endButton;
private CombatInfoFormContext _context;
public void RefreshUI(CombatInfoFormContext context)
{
_context = context;
if (_levelMetaText != null)
{
_levelMetaText.text = context?.LevelMetaText ?? string.Empty;
}
if (_levelPhaseText != null)
{
_levelPhaseText.text = context?.LevelPhaseText ?? string.Empty;
}
if (_coinText != null)
{
_coinText.text = context?.CoinText ?? "0";
}
if (_pauseButton != null)
{
_pauseButton.Interactive = context?.CanPause ?? false;
}
if (_endButton != null)
{
_endButton.gameObject.SetActive(context?.CanEnd ?? false);
}
}
public void OnPauseButtonClick()
{
if (_context != null && !_context.CanPause)
{
return;
}
GameEntry.Event.Fire(this, CombatPauseEventArgs.Create());
}
public void OnEndButtonClick()
{
if (_context != null && !_context.CanEnd)
{
return;
}
GameEntry.Event.Fire(this, CombatEndEventArgs.Create());
}
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
if (userData is CombatInfoFormContext context)
{
RefreshUI(context);
return;
}
Log.Warning("CombatInfoForm requires CombatInfoFormContext as userData.");
}
protected override void OnClose(bool isShutdown, object userData)
{
_context = null;
if (_levelMetaText != null)
{
_levelMetaText.text = string.Empty;
}
if (_levelPhaseText != null)
{
_levelPhaseText.text = string.Empty;
}
if (_coinText != null)
{
_coinText.text = string.Empty;
}
base.OnClose(isShutdown, userData);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b3971fa7b9e17b04a97d7f10d1472a80
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: