Update CombatNodeArchitecture.md
This commit is contained in:
parent
8a478982f8
commit
34446ae42a
|
|
@ -0,0 +1,158 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.UI;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Entity
|
||||
{
|
||||
public sealed class CombatSelectUseCaseConfigurator
|
||||
{
|
||||
private const int BuildOptionCount = 4;
|
||||
|
||||
private readonly BuildTowerVisualInfo[] _buildTowerVisualInfos = new BuildTowerVisualInfo[BuildOptionCount];
|
||||
|
||||
public void Configure(
|
||||
CombatSelectFormUseCase useCase,
|
||||
Func<int> coinProvider,
|
||||
Func<int, Func<bool>> buildActionFactory,
|
||||
Func<bool> upgradeAction,
|
||||
int upgradeCost,
|
||||
Func<bool> destroyAction,
|
||||
int destroyGain,
|
||||
int[] buildTowerCosts,
|
||||
int currentBuildTowerCount,
|
||||
BackpackInventoryData inventorySnapshot,
|
||||
IReadOnlyList<TowerItemData> participantTowers)
|
||||
{
|
||||
if (useCase == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
useCase.SetCoinProvider(coinProvider);
|
||||
|
||||
Dictionary<long, MuzzleCompItemData> muzzleMap = BuildComponentMap(inventorySnapshot?.MuzzleComponents);
|
||||
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(inventorySnapshot?.BearingComponents);
|
||||
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(inventorySnapshot?.BaseComponents);
|
||||
|
||||
int availableBuildCount = Mathf.Clamp(currentBuildTowerCount, 0, BuildOptionCount);
|
||||
for (int i = 0; i < BuildOptionCount; i++)
|
||||
{
|
||||
bool isBuildAvailable = i < availableBuildCount;
|
||||
BuildTowerVisualInfo buildVisual = ResolveBuildTowerVisual(participantTowers, i, muzzleMap, bearingMap, baseMap);
|
||||
_buildTowerVisualInfos[i] = buildVisual;
|
||||
useCase.SetBuildAction(
|
||||
i,
|
||||
isBuildAvailable && buildActionFactory != null ? buildActionFactory.Invoke(i) : null,
|
||||
GetBuildTowerCost(buildTowerCosts, i),
|
||||
null,
|
||||
isBuildAvailable,
|
||||
buildVisual.BaseColor,
|
||||
buildVisual.BearingColor,
|
||||
buildVisual.MuzzleColor);
|
||||
useCase.SetBuildVisible(i, isBuildAvailable);
|
||||
}
|
||||
|
||||
useCase.SetUpgradeAction(upgradeAction, Mathf.Max(0, upgradeCost));
|
||||
useCase.SetDestroyAction(destroyAction, Mathf.Max(0, destroyGain));
|
||||
}
|
||||
|
||||
public BuildTowerVisualInfo GetBuildVisualInfo(int buildIndex)
|
||||
{
|
||||
if (buildIndex < 0 || buildIndex >= _buildTowerVisualInfos.Length)
|
||||
{
|
||||
return BuildTowerVisualInfo.Default;
|
||||
}
|
||||
|
||||
return _buildTowerVisualInfos[buildIndex];
|
||||
}
|
||||
|
||||
private static int GetBuildTowerCost(int[] buildTowerCosts, int buildIndex)
|
||||
{
|
||||
if (buildTowerCosts == null || buildIndex < 0 || buildIndex >= buildTowerCosts.Length)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Mathf.Max(0, buildTowerCosts[buildIndex]);
|
||||
}
|
||||
|
||||
private static BuildTowerVisualInfo ResolveBuildTowerVisual(
|
||||
IReadOnlyList<TowerItemData> participantTowers,
|
||||
int buildIndex,
|
||||
IReadOnlyDictionary<long, MuzzleCompItemData> muzzleMap,
|
||||
IReadOnlyDictionary<long, BearingCompItemData> bearingMap,
|
||||
IReadOnlyDictionary<long, BaseCompItemData> baseMap)
|
||||
{
|
||||
if (participantTowers == null || buildIndex < 0 || buildIndex >= participantTowers.Count)
|
||||
{
|
||||
return BuildTowerVisualInfo.Default;
|
||||
}
|
||||
|
||||
TowerItemData tower = participantTowers[buildIndex];
|
||||
if (tower == null)
|
||||
{
|
||||
return BuildTowerVisualInfo.Default;
|
||||
}
|
||||
|
||||
Color muzzleColor = ResolveComponentColor(muzzleMap, tower.MuzzleComponentInstanceId);
|
||||
Color bearingColor = ResolveComponentColor(bearingMap, tower.BearingComponentInstanceId);
|
||||
Color baseColor = ResolveComponentColor(baseMap, tower.BaseComponentInstanceId);
|
||||
return new BuildTowerVisualInfo(muzzleColor, bearingColor, baseColor);
|
||||
}
|
||||
|
||||
private static Color ResolveComponentColor<TComp>(IReadOnlyDictionary<long, TComp> componentMap, long componentId)
|
||||
where TComp : TowerCompItemData
|
||||
{
|
||||
if (componentMap == null || componentId <= 0)
|
||||
{
|
||||
return Color.white;
|
||||
}
|
||||
|
||||
return componentMap.TryGetValue(componentId, out TComp component) && component != null
|
||||
? IconColorGenerator.GenerateForComponent(component)
|
||||
: Color.white;
|
||||
}
|
||||
|
||||
private static Dictionary<long, TComp> BuildComponentMap<TComp>(IReadOnlyList<TComp> items)
|
||||
where TComp : TowerCompItemData
|
||||
{
|
||||
Dictionary<long, TComp> map = new Dictionary<long, TComp>();
|
||||
if (items == null)
|
||||
{
|
||||
return map;
|
||||
}
|
||||
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
TComp item = items[i];
|
||||
if (item == null || item.InstanceId <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
map[item.InstanceId] = item;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct BuildTowerVisualInfo
|
||||
{
|
||||
public static BuildTowerVisualInfo Default => new BuildTowerVisualInfo(Color.white, Color.white, Color.white);
|
||||
|
||||
public BuildTowerVisualInfo(Color muzzleColor, Color bearingColor, Color baseColor)
|
||||
{
|
||||
MuzzleColor = muzzleColor;
|
||||
BearingColor = bearingColor;
|
||||
BaseColor = baseColor;
|
||||
}
|
||||
|
||||
public Color MuzzleColor { get; }
|
||||
public Color BearingColor { get; }
|
||||
public Color BaseColor { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.CustomComponent;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.Map;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Entity.EntityData;
|
||||
|
|
@ -37,7 +36,7 @@ namespace GeometryTD.Entity
|
|||
private TowerPlacementService _towerPlacementService;
|
||||
private TowerSelectionPresenter _towerSelectionPresenter;
|
||||
|
||||
private readonly BuildTowerVisualInfo[] _buildTowerVisualInfos = new BuildTowerVisualInfo[4];
|
||||
private CombatSelectUseCaseConfigurator _combatSelectUseCaseConfigurator;
|
||||
|
||||
public IReadOnlyList<Vector3Int> PathCells => _mapTopologyService != null
|
||||
? _mapTopologyService.PathCells
|
||||
|
|
@ -102,6 +101,7 @@ namespace GeometryTD.Entity
|
|||
}
|
||||
|
||||
InitializeCombatSelectUseCase();
|
||||
InitializeCombatSelectUseCaseConfigurator();
|
||||
InitializeCombatSelectInputService();
|
||||
InitializeMapTopologyService();
|
||||
InitializeTowerPlacementService();
|
||||
|
|
@ -127,7 +127,9 @@ namespace GeometryTD.Entity
|
|||
{
|
||||
HideCombatSelectForm();
|
||||
_towerPlacementService?.HideAndClearAllPlacedTowers();
|
||||
ClearRuntimeData();
|
||||
ClearSelectionState();
|
||||
ClearTowerTracking();
|
||||
ClearMapTopology();
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +141,9 @@ namespace GeometryTD.Entity
|
|||
|
||||
private void RefreshTiles()
|
||||
{
|
||||
ClearRuntimeData();
|
||||
ClearMapTopology();
|
||||
ClearTowerTracking();
|
||||
ClearSelectionState();
|
||||
|
||||
if (_mapDataRefs == null)
|
||||
{
|
||||
|
|
@ -161,10 +165,18 @@ namespace GeometryTD.Entity
|
|||
_mapTopologyService?.Refresh(tilemap, Spawners, House, name, _mapData != null ? _mapData.LevelId : 0);
|
||||
}
|
||||
|
||||
private void ClearRuntimeData()
|
||||
private void ClearMapTopology()
|
||||
{
|
||||
_mapTopologyService?.Clear();
|
||||
}
|
||||
|
||||
private void ClearTowerTracking()
|
||||
{
|
||||
_towerPlacementService?.ClearTracking();
|
||||
}
|
||||
|
||||
private void ClearSelectionState()
|
||||
{
|
||||
_towerSelectionPresenter?.ClearSelectedObject();
|
||||
}
|
||||
|
||||
|
|
@ -178,6 +190,14 @@ namespace GeometryTD.Entity
|
|||
GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatSelectForm, _combatSelectFormUseCase);
|
||||
}
|
||||
|
||||
private void InitializeCombatSelectUseCaseConfigurator()
|
||||
{
|
||||
if (_combatSelectUseCaseConfigurator == null)
|
||||
{
|
||||
_combatSelectUseCaseConfigurator = new CombatSelectUseCaseConfigurator();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeTowerSelectionPresenter()
|
||||
{
|
||||
if (_towerSelectionPresenter == null)
|
||||
|
|
@ -212,11 +232,6 @@ namespace GeometryTD.Entity
|
|||
|
||||
private void ConfigureCombatSelectUseCase()
|
||||
{
|
||||
if (_combatSelectFormUseCase == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_combatSelectFormUseCase.SetCoinProvider(GetCurrentCoin);
|
||||
BackpackInventoryData inventorySnapshot = GameEntry.PlayerInventory != null
|
||||
? GameEntry.PlayerInventory.GetInventorySnapshot()
|
||||
|
|
@ -224,32 +239,18 @@ namespace GeometryTD.Entity
|
|||
IReadOnlyList<TowerItemData> participantTowers = GameEntry.PlayerInventory != null
|
||||
? GameEntry.PlayerInventory.GetParticipantTowerSnapshot()
|
||||
: null;
|
||||
Dictionary<long, MuzzleCompItemData> muzzleMap = BuildComponentMap(inventorySnapshot?.MuzzleComponents);
|
||||
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(inventorySnapshot?.BearingComponents);
|
||||
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(inventorySnapshot?.BaseComponents);
|
||||
|
||||
int currentBuildTowerCount = GetCurrentBuildTowerCount();
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int buildIndex = i;
|
||||
int buildCost = GetBuildTowerCost(buildIndex);
|
||||
bool isBuildAvailable = buildIndex < currentBuildTowerCount;
|
||||
BuildTowerVisualInfo buildVisual = ResolveBuildTowerVisual(participantTowers, buildIndex, muzzleMap, bearingMap, baseMap);
|
||||
_buildTowerVisualInfos[buildIndex] = buildVisual;
|
||||
_combatSelectFormUseCase.SetBuildAction(
|
||||
buildIndex,
|
||||
isBuildAvailable ? () => TryBuildTower(buildIndex) : (Func<bool>)null,
|
||||
buildCost,
|
||||
null,
|
||||
isBuildAvailable,
|
||||
buildVisual.BaseColor,
|
||||
buildVisual.BearingColor,
|
||||
buildVisual.MuzzleColor);
|
||||
_combatSelectFormUseCase.SetBuildVisible(buildIndex, isBuildAvailable);
|
||||
}
|
||||
|
||||
_combatSelectFormUseCase.SetUpgradeAction(TryUpgradeTower, Mathf.Max(0, _upgradeCost));
|
||||
_combatSelectFormUseCase.SetDestroyAction(TryDestroyTower, Mathf.Max(0, _destroyGain));
|
||||
_combatSelectUseCaseConfigurator?.Configure(
|
||||
_combatSelectFormUseCase,
|
||||
GetCurrentCoin,
|
||||
buildIndex => () => TryBuildTower(buildIndex),
|
||||
TryUpgradeTower,
|
||||
_upgradeCost,
|
||||
TryDestroyTower,
|
||||
_destroyGain,
|
||||
_buildTowerCosts,
|
||||
GetCurrentBuildTowerCount(),
|
||||
inventorySnapshot,
|
||||
participantTowers);
|
||||
}
|
||||
|
||||
private void HandleCombatSelectInput()
|
||||
|
|
@ -306,8 +307,8 @@ namespace GeometryTD.Entity
|
|||
return false;
|
||||
}
|
||||
|
||||
BuildTowerVisualInfo buildVisual = buildIndex >= 0 && buildIndex < _buildTowerVisualInfos.Length
|
||||
? _buildTowerVisualInfos[buildIndex]
|
||||
BuildTowerVisualInfo buildVisual = _combatSelectUseCaseConfigurator != null
|
||||
? _combatSelectUseCaseConfigurator.GetBuildVisualInfo(buildIndex)
|
||||
: BuildTowerVisualInfo.Default;
|
||||
|
||||
if (!_towerPlacementService.TryBuildTower(selectedFoundationCell, IsFoundationCell, buildIndex, _buildTowerCosts,
|
||||
|
|
@ -375,16 +376,6 @@ namespace GeometryTD.Entity
|
|||
GameEntry.UIRouter.CloseUI(UIFormType.CombatSelectForm);
|
||||
}
|
||||
|
||||
private int GetBuildTowerCost(int buildIndex)
|
||||
{
|
||||
if (_buildTowerCosts == null || buildIndex < 0 || buildIndex >= _buildTowerCosts.Length)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Mathf.Max(0, _buildTowerCosts[buildIndex]);
|
||||
}
|
||||
|
||||
private static int GetCurrentBuildTowerCount()
|
||||
{
|
||||
if (GameEntry.CombatNode == null)
|
||||
|
|
@ -395,67 +386,6 @@ namespace GeometryTD.Entity
|
|||
return Mathf.Clamp(GameEntry.CombatNode.CurrentBuildTowerCount, 0, 4);
|
||||
}
|
||||
|
||||
|
||||
private static BuildTowerVisualInfo ResolveBuildTowerVisual(
|
||||
IReadOnlyList<TowerItemData> participantTowers,
|
||||
int buildIndex,
|
||||
IReadOnlyDictionary<long, MuzzleCompItemData> muzzleMap,
|
||||
IReadOnlyDictionary<long, BearingCompItemData> bearingMap,
|
||||
IReadOnlyDictionary<long, BaseCompItemData> baseMap)
|
||||
{
|
||||
if (participantTowers == null || buildIndex < 0 || buildIndex >= participantTowers.Count)
|
||||
{
|
||||
return BuildTowerVisualInfo.Default;
|
||||
}
|
||||
|
||||
TowerItemData tower = participantTowers[buildIndex];
|
||||
if (tower == null)
|
||||
{
|
||||
return BuildTowerVisualInfo.Default;
|
||||
}
|
||||
|
||||
Color muzzleColor = ResolveComponentColor(muzzleMap, tower.MuzzleComponentInstanceId);
|
||||
Color bearingColor = ResolveComponentColor(bearingMap, tower.BearingComponentInstanceId);
|
||||
Color baseColor = ResolveComponentColor(baseMap, tower.BaseComponentInstanceId);
|
||||
return new BuildTowerVisualInfo(muzzleColor, bearingColor, baseColor);
|
||||
}
|
||||
|
||||
private static Color ResolveComponentColor<TComp>(IReadOnlyDictionary<long, TComp> componentMap, long componentId)
|
||||
where TComp : TowerCompItemData
|
||||
{
|
||||
if (componentMap == null || componentId <= 0)
|
||||
{
|
||||
return Color.white;
|
||||
}
|
||||
|
||||
return componentMap.TryGetValue(componentId, out TComp component) && component != null
|
||||
? IconColorGenerator.GenerateForComponent(component)
|
||||
: Color.white;
|
||||
}
|
||||
|
||||
private static Dictionary<long, TComp> BuildComponentMap<TComp>(IReadOnlyList<TComp> items)
|
||||
where TComp : TowerCompItemData
|
||||
{
|
||||
Dictionary<long, TComp> map = new Dictionary<long, TComp>();
|
||||
if (items == null)
|
||||
{
|
||||
return map;
|
||||
}
|
||||
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
TComp item = items[i];
|
||||
if (item == null || item.InstanceId <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
map[item.InstanceId] = item;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static bool TryConsumeCoin(int cost)
|
||||
{
|
||||
int requiredCoin = Mathf.Max(0, cost);
|
||||
|
|
@ -477,22 +407,6 @@ namespace GeometryTD.Entity
|
|||
return GameEntry.CombatNode != null ? Mathf.Max(0, GameEntry.CombatNode.CurrentCoin) : 0;
|
||||
}
|
||||
|
||||
private readonly struct BuildTowerVisualInfo
|
||||
{
|
||||
public static BuildTowerVisualInfo Default => new BuildTowerVisualInfo(Color.white, Color.white, Color.white);
|
||||
|
||||
public BuildTowerVisualInfo(Color muzzleColor, Color bearingColor, Color baseColor)
|
||||
{
|
||||
MuzzleColor = muzzleColor;
|
||||
BearingColor = bearingColor;
|
||||
BaseColor = baseColor;
|
||||
}
|
||||
|
||||
public Color MuzzleColor { get; }
|
||||
public Color BearingColor { get; }
|
||||
public Color BaseColor { get; }
|
||||
}
|
||||
|
||||
private static void AddCoin(int coin)
|
||||
{
|
||||
int amount = Mathf.Max(0, coin);
|
||||
|
|
|
|||
|
|
@ -1,127 +1,497 @@
|
|||
# CombatNode Architecture
|
||||
# CombatNode 设计规范(开发约束)
|
||||
|
||||
最后更新:2026-03-02
|
||||
最后更新:2026-03-06
|
||||
|
||||
## 1. 总览
|
||||
CombatNode 当前分为三层:
|
||||
- `CombatNodeComponent`:入口与关卡数据装配。
|
||||
- `CombatScheduler`:战斗状态机与阶段推进,以及关卡内资源结算。
|
||||
- `EnemyManager`:敌人系统 Facade(对外接口保持稳定,内部由多个子服务协作)。
|
||||
## 1. 适用范围与目标
|
||||
|
||||
本文重点记录 `EnemyManager` 与 `CombatScheduler` 的职责边界(尤其资源管理收口后的模型)。
|
||||
本文描述 `CombatNode` 域的后续开发标准。
|
||||
|
||||
## 2. EnemyManager 相关类
|
||||
说明:
|
||||
- 本文是“目标架构约束”,不要求当前代码已经完全达成。
|
||||
- 后续新增功能、重构、拆分类、review 职责边界时,以本文为准。
|
||||
- 如果当前实现与本文不一致,新增代码优先向本文收敛,而不是继续扩大旧结构。
|
||||
|
||||
核心目标:
|
||||
- `CombatScheduler` 收敛为“状态机管理器”,不再继续堆积加载、结算、奖励选择等业务细节。
|
||||
- 战斗内资源收口到独立资源服务,由内部管理,不再由 `CombatNodeComponent` 直接持有真值。
|
||||
- `MapEntity` 通过 `MapData + Event` 获取战斗上下文,不反查 `CombatNode` 域内部状态。
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构总览
|
||||
|
||||
### 2.1 CombatNodeComponent(入口 Facade)
|
||||
|
||||
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatNodeComponent.cs`
|
||||
|
||||
长期职责:
|
||||
- 读取并缓存 `DRLevel / DRLevelPhase / DRLevelSpawnEntry`。
|
||||
- 按主题筛选关卡。
|
||||
- 启动/停止 `CombatScheduler`。
|
||||
- 对外暴露只读运行时属性。
|
||||
- 提供少量用户入口,例如 `StartCombat`、`TryEndCombatByPlayer`。
|
||||
|
||||
长期不负责:
|
||||
- 不直接持有 `Coin / Gold / BaseHp / Loot Backpack` 的真值。
|
||||
- 不直接缓存本局建塔属性快照。
|
||||
- 不直接发布战斗流程事件。
|
||||
- 不直接处理敌人掉落、结算、奖励选择、地图加载。
|
||||
|
||||
### 2.2 CombatScheduler(状态机管理器)
|
||||
|
||||
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs`
|
||||
|
||||
长期职责:
|
||||
- 持有共享运行时数据与共享服务实例。
|
||||
- 管理状态实例。
|
||||
- 提供统一的 `ChangeState(...)` 状态迁移入口。
|
||||
- 提供敌人事件的公共处理入口。
|
||||
- 作为状态机生命周期边界,统一做运行时重置。
|
||||
|
||||
长期不负责:
|
||||
- 不直接硬编码加载流程。
|
||||
- 不直接硬编码结算流程。
|
||||
- 不直接硬编码奖励选择 UI 逻辑。
|
||||
- 不直接硬编码 `PhaseEndType` 结束条件。
|
||||
|
||||
推荐状态类命名:
|
||||
- `CombatLoadingState`
|
||||
- `CombatRunningPhaseState`
|
||||
- `CombatWaitingForPhaseEndState`
|
||||
- `CombatSettlementState`
|
||||
- `CombatRewardSelectionState`
|
||||
- `CombatFinishFormState`
|
||||
- `CombatWaitingForReturnState`
|
||||
- `CombatFailedState`
|
||||
|
||||
实现约束:
|
||||
- 上述状态类作为 `CombatScheduler` 的嵌套类实现。
|
||||
- 共享数据与共享服务统一放在 `CombatScheduler` 上。
|
||||
- 所有状态切换只能通过 `CombatScheduler.ChangeState(...)` 完成。
|
||||
- 状态类不能彼此直接操控。
|
||||
|
||||
### 2.3 EnemyManager(敌人域 Facade)
|
||||
|
||||
### 2.1 EnemyManager(Facade)
|
||||
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyManager.cs`
|
||||
|
||||
长期职责:
|
||||
- 对状态机提供统一敌人域接口。
|
||||
- 编排敌人子服务。
|
||||
- 暴露只读事实:
|
||||
- `AliveEnemyCount`
|
||||
- `IsPhaseSpawnCompleted`
|
||||
- `HasAliveBoss`
|
||||
- 在敌人死亡或到家时,通过公共入口向 `CombatScheduler` 上报:
|
||||
- `OnEnemyDefeated(DREnemy enemy)`
|
||||
- `OnEnemyReachedBase(DREnemy enemy)`
|
||||
|
||||
长期不负责:
|
||||
- 不直接给资源入账。
|
||||
- 不直接扣基地血量。
|
||||
- 不直接决定状态切换。
|
||||
|
||||
---
|
||||
|
||||
## 3. 状态机模型
|
||||
|
||||
### 3.1 状态列表(目标)
|
||||
|
||||
- `Loading`
|
||||
- `RunningPhase`
|
||||
- `WaitingForPhaseEnd`
|
||||
- `Settlement`
|
||||
- `RewardSelection`
|
||||
- `FinishForm`
|
||||
- `WaitingForReturn`
|
||||
- `Failed`
|
||||
|
||||
说明:
|
||||
- 正常结束流只有一条状态链:
|
||||
- `Settlement -> RewardSelection(可选) -> FinishForm -> WaitingForReturn`
|
||||
- 正常通关、玩家主动结束、基地血量归零都走同一条结束链。
|
||||
- `Failed` 仅用于异常失败,不用于“基地被击破”这类正常战斗失败。
|
||||
|
||||
### 3.2 CombatLoadingState
|
||||
|
||||
职责:
|
||||
- 对 `CombatScheduler` 提供统一接口:`OnInit / BeginPhase / OnUpdate / EndPhase / OnDestroy`。
|
||||
- 编排敌人域子服务,不承载具体业务细节。
|
||||
- 转发状态:`AliveEnemyCount`、`IsPhaseRunning`、`IsPhaseSpawnCompleted`。
|
||||
- 处理敌人实体事件(Show/Hide)后的结果上报:
|
||||
- 击杀:上报 `coin/gold` 到 `CombatScheduler.OnEnemyDefeatedRewardResolved(...)`
|
||||
- 到家:上报 `baseDamage` 到 `CombatScheduler.OnEnemyReachedBase(...)`
|
||||
- 通过 `CombatLoadSession` 执行地图与基础战斗 UI 加载。
|
||||
- 从局内资源管理器读取本局快照。
|
||||
- 组装 `MapData` 并发起 `ShowEntity(MapEntity)`。
|
||||
|
||||
约束:
|
||||
- 只负责加载,不负责初始化局内资源。
|
||||
- 局内资源必须在进入状态机前初始化完成。
|
||||
|
||||
### 3.3 CombatRunningPhaseState
|
||||
|
||||
职责:
|
||||
- 执行当前 `DRLevelPhase` 的行为。
|
||||
- 推进 `SpawnEntry` 时序与出怪。
|
||||
- 管理 `EnemySpawnDirector` 的阶段级初始化与重置。
|
||||
- 在新 phase 开始时发布:
|
||||
- `CombatProcessEventArgs`
|
||||
- `CombatEnemyHpRateChangedEventArgs`
|
||||
|
||||
退出条件:
|
||||
- 当前 phase 的所有 `SpawnEntry` 已执行完毕时,进入 `WaitingForPhaseEnd`。
|
||||
- 若共享“结束战斗请求标记”已置位,也可结束当前运行态并转入正常结束链。
|
||||
|
||||
不负责:
|
||||
- 不直接维护刷怪时间轴。
|
||||
- 不直接维护出生点缓存与路径查询。
|
||||
- 不直接维护敌人追踪结构。
|
||||
- 不直接维护掉落池抽样与背包库存。
|
||||
- 不直接维护敌人配置兜底和血量倍率算法。
|
||||
- 不根据 `PhaseEndType` 判断 phase 是否真正结束。
|
||||
- 不直接根据 `BaseHp` 或敌人死亡事件切状态。
|
||||
|
||||
### 3.4 CombatWaitingForPhaseEndState
|
||||
|
||||
职责:
|
||||
- 不再生成新敌人。
|
||||
- 根据 `PhaseEndType` 判断当前 phase 是否结束。
|
||||
|
||||
约束:
|
||||
- `PhaseEndType` 的判断由独立判定服务负责,不在状态内硬编码。
|
||||
- 每种 `PhaseEndType` 对应一个实现类。
|
||||
- 该判定服务为本状态专用,不作为全局共享服务常驻在 `CombatScheduler` 上。
|
||||
|
||||
### 3.5 CombatSettlementState
|
||||
|
||||
职责:
|
||||
- 进入时统一构造结算上下文。
|
||||
- 根据共享资源状态完成结算修正。
|
||||
- 决定后续进入 `RewardSelection` 还是 `FinishForm`。
|
||||
|
||||
负责的逻辑包括:
|
||||
- 基地血量奖励/惩罚。
|
||||
- 满血奖励选择的准入判断。
|
||||
- 生成最终展示摘要。
|
||||
- 准备待合并的结算背包快照。
|
||||
|
||||
约束:
|
||||
- 不依赖单独的 `CombatEndReason` 字段。
|
||||
- `BaseHp <= 0` 表示基地被击破。
|
||||
- 正常通关与玩家主动结束在结算产出上不区分原因。
|
||||
|
||||
### 3.6 CombatRewardSelectionState
|
||||
|
||||
职责:
|
||||
- 绑定、配置、打开、关闭 `RewardSelectForm`。
|
||||
- 处理奖励选择过程。
|
||||
- 将奖励选择结果写入“结束状态链持有的结算上下文”。
|
||||
|
||||
约束:
|
||||
- 不重新判断“是否应该出现奖励选择”。
|
||||
- 只处理选择过程本身。
|
||||
|
||||
### 3.7 CombatFinishFormState
|
||||
|
||||
职责:
|
||||
- 绑定、配置、打开、关闭 `CombatFinishForm`。
|
||||
- 读取结算上下文并展示最终结算结果。
|
||||
|
||||
### 3.8 CombatWaitingForReturnState
|
||||
|
||||
职责:
|
||||
- 等待玩家从结算返回。
|
||||
- 完成地图与战斗基础 UI 清理。
|
||||
- 完成正常退出收尾。
|
||||
- 在整场战斗真正退出时发布 `NodeCompleteEventArgs`。
|
||||
|
||||
### 3.9 CombatFailedState
|
||||
|
||||
职责:
|
||||
- 表示异常失败。
|
||||
- 保存并展示错误信息。
|
||||
- 执行异常收尾与剩余资源回收。
|
||||
|
||||
约束:
|
||||
- `Failed` 只处理异常路径。
|
||||
- “基地血量为 0”不进入 `Failed`。
|
||||
|
||||
---
|
||||
|
||||
## 4. 共享服务与推荐命名
|
||||
|
||||
### 4.1 CombatLoadSession
|
||||
|
||||
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatLoadSession.cs`
|
||||
|
||||
长期定位:
|
||||
- 长期保留的独立加载服务。
|
||||
- 专门负责地图与战斗内基础 UI 的加载/清理。
|
||||
|
||||
职责:
|
||||
- 加载地图实体。
|
||||
- 打开/关闭 `CombatInfoForm`。
|
||||
- 跟踪加载成功/失败状态。
|
||||
- 对外提供 `CurrentMap` 与 `IsReady`。
|
||||
|
||||
### 4.2 PhaseLoopRuntime
|
||||
|
||||
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/PhaseLoopRuntime.cs`
|
||||
|
||||
长期定位:
|
||||
- 长期保留的独立 phase runtime 服务。
|
||||
|
||||
职责:
|
||||
- 维护当前 `DRLevelPhase`。
|
||||
- 维护 `DisplayPhaseIndex`、`PhaseCount`。
|
||||
- 维护统一的 phase 时间基准,例如 `phaseElapsedTime` 或 `phaseStartTime`。
|
||||
- 负责进入下一 phase。
|
||||
- 持有统一“请求结束战斗”标记。
|
||||
|
||||
约束:
|
||||
- 只做 phase 运行时数据管理,不直接切状态。
|
||||
|
||||
### 4.3 CombatInRunResourceManager(推荐命名)
|
||||
|
||||
当前相关文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatResourceManager.cs`
|
||||
|
||||
目标职责:
|
||||
- 持有本局 `Coin` 真值。
|
||||
- 持有本局累计 `Gold` 真值。
|
||||
- 持有本局 `BaseHp` 真值。
|
||||
- 持有本局战利品背包。
|
||||
- 持有本局建塔属性快照。
|
||||
- 提供只读快照给结束状态链与加载状态使用。
|
||||
- 发布资源变化事件:
|
||||
- `CombatCoinChangedEventArgs`
|
||||
- `CombatBaseHpChangedEventArgs`
|
||||
|
||||
初始化约束:
|
||||
- 在进入状态机前完成初始化。
|
||||
- 由内部从 `PlayerInventory` 获取并缓存本局建塔快照。
|
||||
|
||||
事件约束:
|
||||
- `Coin / BaseHp` 变化事件同时携带“当前值”和“变化量”。
|
||||
- `Gold` 只是结算累计值,不要求战斗内实时事件驱动。
|
||||
|
||||
### 4.4 EnemyDropResolver(推荐命名)
|
||||
|
||||
当前相关文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatResourceManager.cs`
|
||||
|
||||
目标职责:
|
||||
- 只负责敌人死亡后的掉落判定。
|
||||
- 输入:
|
||||
- `DREnemy`
|
||||
- 当前阶段索引
|
||||
- 当前主题或关卡上下文
|
||||
- 输出:
|
||||
- 掉落结果对象(`coin / gold / loot`)
|
||||
|
||||
约束:
|
||||
- 不直接修改资源状态。
|
||||
- 不直接读取 `CombatNodeComponent`、`MapEntity`、`EnemyManager` 内部状态。
|
||||
|
||||
### 4.5 IPhaseEndCondition(推荐命名)
|
||||
|
||||
目标职责:
|
||||
- 作为 `PhaseEndType` 判定接口。
|
||||
- 每种 `PhaseEndType` 对应一个实现类。
|
||||
|
||||
只读输入:
|
||||
- 当前 `DRLevelPhase`
|
||||
- phase 时间信息
|
||||
- `AliveEnemyCount`
|
||||
- `IsPhaseSpawnCompleted`
|
||||
- `HasAliveBoss`
|
||||
|
||||
输出:
|
||||
- `bool ShouldExit`
|
||||
|
||||
约束:
|
||||
- 不直接切状态。
|
||||
- 不直接发事件。
|
||||
- 不直接改资源。
|
||||
|
||||
---
|
||||
|
||||
## 5. EnemyManager 子服务边界
|
||||
|
||||
### 5.1 EnemySpawnDirector
|
||||
|
||||
### 2.2 EnemySpawnDirector(刷怪时序)
|
||||
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemySpawnDirector.cs`
|
||||
|
||||
职责:
|
||||
- 管理 `DRLevelSpawnEntry` 的运行时实例(`SpawnEntryRuntime`)。
|
||||
- 按时间推进 `Stream / Burst / Boss` 生成逻辑。
|
||||
- 输出“当前应生成多少敌人”,通过回调交给 `EnemyManager.SpawnEnemies` 执行。
|
||||
- 维护阶段刷怪状态:`IsPhaseRunning`、`IsPhaseSpawnCompleted`。
|
||||
- 长期保留为独立服务。
|
||||
- 基于 `spawnEntries + phase time` 计算当前应执行的刷怪行为。
|
||||
- 提供“当前 phase 的 `SpawnEntry` 是否已全部执行完”的事实。
|
||||
|
||||
生命周期:
|
||||
- 由 `CombatRunningPhaseState` 在状态进入/退出时初始化与重置。
|
||||
|
||||
### 5.2 SpawnerResolver
|
||||
|
||||
### 2.3 SpawnerResolver(出生点与路径)
|
||||
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/SpawnerResolver.cs`
|
||||
|
||||
职责:
|
||||
- 缓存当前地图可用 `Spawner`。
|
||||
- 支持 `SpawnOrder` 映射与 fallback 轮询。
|
||||
- 对外提供 `TryResolveSpawnPath(...)`,返回世界坐标路径点。
|
||||
- 在地图切换时刷新缓存,避免每次刷怪全量扫描。
|
||||
- 提供出生点与路径解析。
|
||||
|
||||
### 5.3 EnemyLifecycleTracker
|
||||
|
||||
### 2.4 EnemyLifecycleTracker(敌人生命周期追踪)
|
||||
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyLifecycleTracker.cs`
|
||||
|
||||
职责:
|
||||
- 追踪本局敌人 `entityId -> DREnemy`。
|
||||
- 维护 `AliveEnemyCount`。
|
||||
- 处理 `ShowSuccess / ShowFailure / HideComplete` 对追踪状态的变更。
|
||||
- 提供批量导出 tracked ids,用于 `CleanupTrackedEnemies` 清场。
|
||||
- 维护 `AliveEnemyCount` 真值。
|
||||
- 维护 `HasAliveBoss` 真值。
|
||||
- 追踪本局 tracked 敌人。
|
||||
- 导出 tracked ids 供清场使用。
|
||||
|
||||
Boss 识别规则:
|
||||
- Boss 身份由 `DRLevelSpawnEntry.EntryType == Boss` 决定。
|
||||
- 不由 `DREnemy` 自身类型决定。
|
||||
|
||||
### 5.4 EnemyConfigService
|
||||
|
||||
### 2.5 EnemyConfigService(敌人配置与倍率)
|
||||
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyConfigService.cs`
|
||||
|
||||
职责:
|
||||
- 读取 `DREnemy`,处理默认配置兜底。
|
||||
- 计算循环周目下的基础血量倍率(按 `displayPhaseIndex / phaseCount` 推导 loop)。
|
||||
- 缓存数据表引用并在 `Reset` 时清理。
|
||||
- 读取 `DREnemy`。
|
||||
- 处理默认配置兜底。
|
||||
- 计算循环周目下的基础血量倍率。
|
||||
|
||||
## 3. CombatScheduler 资源收口
|
||||
---
|
||||
|
||||
### 3.1 CombatResourceManager(关卡内资源统一管理)
|
||||
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatResourceManager.cs`
|
||||
## 6. 事件与数据流规范
|
||||
|
||||
职责:
|
||||
- 统一维护关卡内资源状态:
|
||||
- `GainedCoin`、`GainedGold`
|
||||
- `BackpackInventoryData`(结算背包快照)
|
||||
- 处理击杀奖励入账:`AddEnemyDefeatedReward(...)`
|
||||
- 处理局外掉落抽样与物品构建:`TryRollOutGameItemDrop(...)`
|
||||
- 对结算 UI 提供只读快照:`GetRewardInventorySnapshot()`
|
||||
### 6.1 MapEntity 与 Combat 域解耦
|
||||
|
||||
### 3.2 CombatScheduler 与资源类关系
|
||||
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs`
|
||||
必须保持:
|
||||
- `MapEntity` 不直接查询 `CombatNodeComponent` 的运行时资源字段。
|
||||
- 战斗初始上下文通过 `MapData` 注入。
|
||||
- `Coin` 初值通过 `MapData` 传入。
|
||||
- 后续 `Coin` 变化通过 `CombatCoinChangedEventArgs` 同步。
|
||||
- `TowerStatsData` 等本局不变量直接放进 `MapData`。
|
||||
- `MapEntity` 不反查 Combat 域内部服务。
|
||||
|
||||
当前模型:
|
||||
- `CombatScheduler` 持有 `_combatResourceManager`。
|
||||
- `GainedCoin/GainedGold` 属性透传资源类。
|
||||
- `OnEnemyDefeatedRewardResolved(...)` 统一触发:
|
||||
- 货币入账
|
||||
- 关卡内掉落判定
|
||||
- `EnterFinishFlow(...)` 从资源类取 `BackpackInventoryData` 作为结算数据。
|
||||
`MapData` 组装规则:
|
||||
- 由 `CombatLoadingState` 从局内资源管理器读取快照。
|
||||
- 由 `CombatLoadingState` 打包成 `MapData` 后再 `ShowEntity(MapEntity)`。
|
||||
|
||||
## 4. 协作流程(简版)
|
||||
1. `CombatScheduler.BeginNextPhase()` 调 `EnemyManager.BeginPhase(...)`。
|
||||
2. `EnemyManager` 刷新 `SpawnerResolver` 缓存,并通知 `EnemySpawnDirector.BeginPhase(...)`。
|
||||
3. 每帧 `EnemyManager.OnUpdate(...)`:
|
||||
- `SpawnerResolver.RefreshCache(...)`
|
||||
- `EnemySpawnDirector.OnUpdate(..., SpawnEnemies)`
|
||||
4. `SpawnEnemies(...)` 执行实际生成:
|
||||
- `SpawnerResolver.TryResolveSpawnPath(...)`
|
||||
- `EnemyConfigService.GetEnemyConfig(...)`
|
||||
- `EnemyConfigService.ResolveScaledEnemyBaseHp(...)`
|
||||
- `EnemyLifecycleTracker.TrackEnemy(...)`
|
||||
- `GameEntry.Entity.ShowEnemy(...)`
|
||||
5. 敌人回收时 `EnemyManager.OnHideEntityComplete(...)`:
|
||||
- `EnemyLifecycleTracker.TryHandleHideComplete(...)`
|
||||
- 若击杀:上报 `coin/gold` 给 `CombatScheduler.OnEnemyDefeatedRewardResolved(...)`
|
||||
- 若到家:上报 `baseDamage` 给 `CombatScheduler.OnEnemyReachedBase(...)`
|
||||
6. `CombatScheduler.OnEnemyDefeatedRewardResolved(...)`:
|
||||
- `CombatResourceManager.AddEnemyDefeatedReward(...)`
|
||||
- `CombatResourceManager.TryRollOutGameItemDrop(...)`
|
||||
7. `CombatScheduler.EnterFinishFlow(...)`:
|
||||
- 读取 `CombatResourceManager.GetRewardInventorySnapshot()`
|
||||
- 打开结算 UI
|
||||
### 6.2 敌人事件处理
|
||||
|
||||
## 5. 关键不变量
|
||||
- 存活敌人数以 `EnemyLifecycleTracker.AliveEnemyCount` 为唯一真值来源。
|
||||
- 刷怪阶段状态以 `EnemySpawnDirector` 为唯一真值来源。
|
||||
- 关卡内资源以 `CombatResourceManager` 为唯一真值来源。
|
||||
- `EnemyManager` 只做敌人域编排与结果上报,不再持有奖励库存。
|
||||
- 清场必须只作用于本局 tracked 敌人,避免误伤其他实体。
|
||||
统一边界:
|
||||
- `EnemyManager` 只上报:
|
||||
- `OnEnemyDefeated(DREnemy enemy)`
|
||||
- `OnEnemyReachedBase(DREnemy enemy)`
|
||||
- `CombatScheduler` 公共层负责处理敌人事件的通用副作用:
|
||||
- 击杀:调用 `EnemyDropResolver`,再调用局内资源管理器入账。
|
||||
- 到家:调用局内资源管理器扣减 `BaseHp`。
|
||||
|
||||
## 6. 维护建议
|
||||
- 新增刷怪类型:优先改 `EnemySpawnDirector`。
|
||||
- 新增路径/出生规则:优先改 `SpawnerResolver`。
|
||||
- 新增敌人追踪策略:优先改 `EnemyLifecycleTracker`。
|
||||
- 新增敌人配置兜底或倍率策略:优先改 `EnemyConfigService`。
|
||||
- 新增货币/掉落/结算背包规则:优先改 `CombatResourceManager`。
|
||||
约束:
|
||||
- 敌人事件入口不直接调用 `ChangeState(...)`。
|
||||
- `BaseHp <= 0` 的判断由当前状态在 `OnUpdate` 中处理。
|
||||
|
||||
### 6.3 战斗流程事件
|
||||
|
||||
发布边界:
|
||||
- 资源变化事件由局内资源管理器发布。
|
||||
- 流程/阶段事件由状态机或具体状态发布。
|
||||
|
||||
发布时间:
|
||||
- `NodeEnterEventArgs`:`Loading` 完成并正式进入首个 `RunningPhase` 时。
|
||||
- `CombatProcessEventArgs`:新 phase 的 `RunningPhase.OnEnter`。
|
||||
- `CombatEnemyHpRateChangedEventArgs`:与 `CombatProcessEventArgs` 同时发布。
|
||||
- `NodeCompleteEventArgs`:`WaitingForReturn` 完成清理、整场战斗真正退出时。
|
||||
|
||||
---
|
||||
|
||||
## 7. 结束链与结算上下文
|
||||
|
||||
### 7.1 统一结束链
|
||||
|
||||
正常结束统一走:
|
||||
- `Settlement`
|
||||
- `RewardSelection`(可选)
|
||||
- `FinishForm`
|
||||
- `WaitingForReturn`
|
||||
|
||||
### 7.2 结算上下文
|
||||
|
||||
归属:
|
||||
- 作为 `CombatScheduler` 上的共享字段存在。
|
||||
- `Settlement` 在 `OnEnter` 时统一构造。
|
||||
- `RewardSelection` 只追加奖励结果。
|
||||
- `FinishForm` 与 `WaitingForReturn` 只读取。
|
||||
|
||||
最小字段集合:
|
||||
- 最终结算的 `Gold/Coin` 结果
|
||||
- 待合并的背包快照
|
||||
- `BaseHp` 结算结果
|
||||
- 是否进入过奖励选择
|
||||
- `FinishForm` 所需摘要数据
|
||||
|
||||
奖励选择约束:
|
||||
- 满血奖励选择结果只写入结算上下文。
|
||||
- 不直接写入局内资源管理器。
|
||||
- 最终由结束状态链统一合并到玩家背包。
|
||||
|
||||
---
|
||||
|
||||
## 8. 核心不变量(必须保持)
|
||||
|
||||
1. `CombatScheduler` 只做状态机管理与共享运行时收口,不继续吸收具体业务细节。
|
||||
2. `CombatNodeComponent` 不再持有战斗内资源真值。
|
||||
3. 局内 `Coin / Gold / BaseHp / Loot Backpack / BuildTowerSnapshots` 以 `CombatInRunResourceManager` 为唯一真值来源。
|
||||
4. 敌人死亡掉落判定以 `EnemyDropResolver` 为唯一判定入口。
|
||||
5. 存活敌人数与 `HasAliveBoss` 以 `EnemyLifecycleTracker` 为唯一真值来源。
|
||||
6. Phase 运行时信息与统一结束标记以 `PhaseLoopRuntime` 为唯一真值来源。
|
||||
7. `PhaseEndType` 的退出条件以 `IPhaseEndCondition` 实现类为唯一判定入口。
|
||||
8. 状态切换只能通过 `CombatScheduler.ChangeState(...)` 完成。
|
||||
9. 敌人事件处理入口不直接切状态,状态只能在自己的 `OnUpdate` 中决定迁移。
|
||||
10. `MapEntity` 通过 `MapData + Event` 获取战斗上下文,不反查 Combat 域内部运行时。
|
||||
|
||||
---
|
||||
|
||||
## 9. 清理职责
|
||||
|
||||
- 敌人清理:`EnemyManager`,且只清理本局 tracked 敌人。
|
||||
- 地图与战斗基础 UI 清理:`CombatLoadSession`。
|
||||
- 结算/奖励 UI 清理:结束状态链或 `Failed` 状态。
|
||||
- 运行时数据重置:`CombatScheduler` 在状态机生命周期边界统一执行。
|
||||
|
||||
---
|
||||
|
||||
## 10. 扩展开发规范
|
||||
|
||||
### 10.1 新增刷怪类型或 SpawnEntry 行为
|
||||
|
||||
优先改 `EnemySpawnDirector`,不要把时序细节塞进 `CombatRunningPhaseState`。
|
||||
|
||||
### 10.2 新增 Phase 结束条件
|
||||
|
||||
新增 `IPhaseEndCondition` 实现类,不要在 `CombatWaitingForPhaseEndState` 里写大分支。
|
||||
|
||||
### 10.3 新增敌人掉落规则
|
||||
|
||||
优先改 `EnemyDropResolver`,不要在 `EnemyManager` 或状态类里直接计算掉落。
|
||||
|
||||
### 10.4 新增战斗内资源或建塔快照规则
|
||||
|
||||
优先改 `CombatInRunResourceManager`,不要回流到 `CombatNodeComponent`。
|
||||
|
||||
### 10.5 新增地图/战斗基础 UI 加载规则
|
||||
|
||||
优先改 `CombatLoadSession` 或 `CombatLoadingState`,不要把加载细节塞回 `CombatScheduler` 本体。
|
||||
|
||||
### 10.6 新增强化结算、奖励选择、结算 UI 逻辑
|
||||
|
||||
优先改结束状态链:
|
||||
- `CombatSettlementState`
|
||||
- `CombatRewardSelectionState`
|
||||
- `CombatFinishFormState`
|
||||
- `CombatWaitingForReturnState`
|
||||
|
||||
### 10.7 新增战斗流程事件
|
||||
|
||||
优先由具体状态或局内资源管理器发布,不要回流到 `CombatNodeComponent`。
|
||||
|
||||
---
|
||||
|
||||
## 11. 代码变更检查清单(PR 自检)
|
||||
|
||||
1. 新逻辑是否落在正确的状态或服务,而不是继续堆进 `CombatScheduler` 本体?
|
||||
2. `CombatNodeComponent` 是否仍然保持为轻量入口 Facade?
|
||||
3. 是否破坏了局内资源、掉落判定、phase runtime、phase end 判定的唯一真值来源?
|
||||
4. 敌人事件处理是否仍然只做公共副作用,而不直接切状态?
|
||||
5. 状态迁移是否仍然统一走 `ChangeState(...)`?
|
||||
6. `MapEntity` 是否仍然只通过 `MapData + Event` 获取战斗上下文?
|
||||
7. 清理是否仍按“敌人 / 地图基础 UI / 结算 UI / 运行时数据”分工执行?
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# MapEntity 设计规范(开发约束)
|
||||
|
||||
最后更新:2026-03-02
|
||||
最后更新:2026-03-06
|
||||
|
||||
## 1. 目标与边界
|
||||
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
- 防御塔映射字典的增删改查细节。
|
||||
- 选中状态与攻击范围显示细节。
|
||||
- 鼠标拾取与 `CombatSelectFormUserData` 组装细节。
|
||||
- 战斗选择 UI 的库存快照解析、颜色映射与 Build 选项配置细节。
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -28,10 +29,11 @@
|
|||
- 子服务初始化与清理
|
||||
- UI 用例绑定(`CombatSelectFormUseCase`)
|
||||
- 将输入结果分发到选择器/建造器
|
||||
- 收集运行时上下文并委托给专用服务配置战斗选择 UI
|
||||
|
||||
### 2.2 MapTopologyService(地图拓扑层)
|
||||
|
||||
当前文件:`Assets/GameMain/Scripts/Scene/Map/TowerPlacementService.cs`(同文件内)
|
||||
文件:`Assets/GameMain/Scripts/Scene/Map/MapTopologyService.cs`
|
||||
|
||||
职责:
|
||||
- 扫描 Tilemap,构建 `PathCells` / `FoundationCells`
|
||||
|
|
@ -58,7 +60,21 @@
|
|||
- 只读上下文,不改变游戏状态。
|
||||
- Foundation/Tower 点击时,UI 定位由 Cell 中心决定(稳定定位)。
|
||||
|
||||
### 2.4 TowerPlacementService(塔部署层)
|
||||
### 2.4 CombatSelectUseCaseConfigurator(战斗选择 UI 配置层)
|
||||
|
||||
文件:`Assets/GameMain/Scripts/Entity/EntityLogic/CombatSelectUseCaseConfigurator.cs`
|
||||
|
||||
职责:
|
||||
- 读取库存快照与参战塔快照
|
||||
- 构建组件映射与塔外观颜色
|
||||
- 配置 `CombatSelectFormUseCase` 的 Build/Upgrade/Destroy action 与显示参数
|
||||
- 缓存当前 Build 槽位对应的视觉信息,供建塔流程复用
|
||||
|
||||
约束:
|
||||
- 只负责 UI 选项配置与只读数据组装,不直接改变战斗状态。
|
||||
- 不处理鼠标输入、地图拓扑、塔实体生命周期。
|
||||
|
||||
### 2.5 TowerPlacementService(塔部署层)
|
||||
|
||||
当前文件:`Assets/GameMain/Scripts/Scene/Map/TowerPlacementService.cs`
|
||||
|
||||
|
|
@ -73,13 +89,13 @@
|
|||
约束:
|
||||
- 仅处理塔生命周期与映射,不处理选中态和 UI。
|
||||
|
||||
### 2.5 TowerSelectionPresenter(选择展示层)
|
||||
### 2.6 TowerSelectionPresenter(选择展示层)
|
||||
|
||||
文件:`Assets/GameMain/Scripts/Scene/Map/TowerSelectionPresenter.cs`
|
||||
|
||||
职责:
|
||||
- 维护当前选中对象
|
||||
- 根据选中状态切换攻击范围显示(通过 `DefenseTowerEntity.SetAttackRangeVisible`)
|
||||
- 根据选中状态切换攻击范围显示(通过 `TowerEntity.SetAttackRangeVisible`)
|
||||
|
||||
约束:
|
||||
- 不做建造/升级/销毁。
|
||||
|
|
@ -88,12 +104,14 @@
|
|||
|
||||
## 3. 运行时主流程(简版)
|
||||
|
||||
1. `MapEntity.OnInit` 初始化 4 个服务:
|
||||
1. `MapEntity.OnInit` 初始化并绑定 6 个对象:
|
||||
- `MapTopologyService`
|
||||
- `CombatSelectInputService`
|
||||
- `CombatSelectUseCaseConfigurator`
|
||||
- `TowerPlacementService`
|
||||
- `TowerSelectionPresenter`
|
||||
2. `MapEntity.OnShow` 刷新地图拓扑,配置 UI action。
|
||||
- `CombatSelectFormUseCase`
|
||||
2. `MapEntity.OnShow` 刷新地图拓扑,配置 UI action 与 Build 视觉数据。
|
||||
3. 每帧 `OnUpdate`:
|
||||
- 采集输入(InputService)
|
||||
- 更新选中对象(SelectionPresenter)
|
||||
|
|
@ -103,8 +121,9 @@
|
|||
- 通过 SelectionPresenter 同步选中和范围显示
|
||||
5. `OnHide`:
|
||||
- 关闭 UI
|
||||
- 清理塔实体
|
||||
- 清理拓扑/选择/映射运行时状态
|
||||
- 隐藏并清理塔实体
|
||||
- 清理选择态与塔映射运行时状态
|
||||
- 清理拓扑缓存
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -114,7 +133,7 @@
|
|||
2. `TowerPlacementService` 是塔映射状态的唯一写入口。
|
||||
3. `MapTopologyService` 是 Path/Foundation 数据的唯一来源。
|
||||
4. “当前可见攻击范围”同一时刻最多一个塔。
|
||||
5. 清场顺序固定:先隐藏塔,再清空映射与选择,再清理拓扑缓存。
|
||||
5. 清场顺序固定:先关闭 UI,再隐藏塔,再清空选择与映射,再清理拓扑缓存。
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -132,7 +151,11 @@
|
|||
|
||||
优先改 `CombatSelectInputService`。
|
||||
|
||||
### 5.4 新增选中表现(特效、描边、信息面板联动)
|
||||
### 5.4 新增 Build 选项展示、塔外观颜色或库存驱动 UI 配置
|
||||
|
||||
优先改 `CombatSelectUseCaseConfigurator`。
|
||||
|
||||
### 5.5 新增选中表现(特效、描边、信息面板联动)
|
||||
|
||||
优先改 `TowerSelectionPresenter`。
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue