Update CombatNodeArchitecture.md

This commit is contained in:
SepComet
2026-03-06 21:02:40 +08:00
parent 8a478982f8
commit 34446ae42a
5 changed files with 694 additions and 229 deletions
@@ -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);