CombatNode 相关逻辑补充

- CombatFinishForm:战斗节点的结算页面(杀死的敌人、获得的金币、获得的道具)
- CombatSelectForm:战斗节点建造防御塔的选择 UI 以及对防御塔进行操作的 UI(只实现了建造逻辑,在 MapEntity 里做射线检测)
- 构建了基本的防御塔三组件(ShooterMuzzleComp/BasicBearingComp/BasicBaseComp)和防御塔主控(DefenseTowerController)以及相应的实体类(DefenseTowerEntity)
This commit is contained in:
SepComet
2026-03-01 23:11:52 +08:00
parent 92cca14503
commit 564817d752
96 changed files with 7294 additions and 121 deletions
@@ -0,0 +1,66 @@
using System;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.Entity.EntityData
{
[Serializable]
public class BulletData : EntityDataBase
{
[SerializeField] private Transform _target = null;
[SerializeField] private int _damage = 0;
[SerializeField] private float _speed = 0f;
[SerializeField] private float _maxLifetime = 3f;
[SerializeField] private AttackPropertyType _attackPropertyType = AttackPropertyType.None;
public BulletData(
int entityId,
int typeId,
Vector3 position,
Quaternion rotation,
Transform target,
int damage,
float speed,
AttackPropertyType attackPropertyType = AttackPropertyType.None,
float maxLifetime = 3f) : base(entityId, typeId)
{
Position = position;
Rotation = rotation;
_target = target;
_damage = damage;
_speed = speed;
_attackPropertyType = attackPropertyType;
_maxLifetime = maxLifetime;
}
public Transform Target
{
get => _target;
set => _target = value;
}
public int Damage
{
get => _damage;
set => _damage = value;
}
public float Speed
{
get => _speed;
set => _speed = value;
}
public float MaxLifetime
{
get => _maxLifetime;
set => _maxLifetime = value;
}
public AttackPropertyType AttackPropertyType
{
get => _attackPropertyType;
set => _attackPropertyType = value;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6f7248061a2a4f4f8ecf8d5db99f0a82
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,26 @@
using System;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.Entity.EntityData
{
[Serializable]
public class DefenseTowerData : EntityDataBase
{
[SerializeField] private DefenseTowerStatsData _stats = new DefenseTowerStatsData();
public DefenseTowerData(int entityId, int typeId, Vector3 position, Quaternion rotation, DefenseTowerStatsData stats)
: base(entityId, typeId)
{
Position = position;
Rotation = rotation;
_stats = stats ?? new DefenseTowerStatsData();
}
public DefenseTowerStatsData Stats
{
get => _stats;
set => _stats = value ?? new DefenseTowerStatsData();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0d46035f451d4e01a5a21ed5984ce42b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -41,16 +41,21 @@ namespace GeometryTD
entityComponent.AttachEntity(entityBase.Entity, ownerId, parentTransformPath, userData);
}
public static void ShowPlayer(this EntityComponent entityComponent, PlayerData data)
{
entityComponent.ShowEntity(typeof(Player), "Player", Constant.AssetPriority.PlayerAsset, data);
}
public static void ShowEnemy(this EntityComponent entityComponent, EnemyData data)
{
entityComponent.ShowEntity(typeof(EnemyEntity), "Enemy", Constant.AssetPriority.EnemyAsset, data);
}
public static void ShowDefenseTower(this EntityComponent entityComponent, DefenseTowerData data)
{
entityComponent.ShowEntity(typeof(DefenseTowerEntity), "Tower", Constant.AssetPriority.EnemyAsset, data);
}
public static void ShowBullet(this EntityComponent entityComponent, BulletData data)
{
entityComponent.ShowEntity(typeof(BulletEntity), "Bullet", Constant.AssetPriority.BulletAsset, data);
}
public static void ShowMap(this EntityComponent entityComponent, MapData data)
{
ShowMap(entityComponent, data, null);
@@ -0,0 +1,68 @@
using Components;
using GeometryTD.Entity.EntityData;
using UnityGameFramework.Runtime;
namespace GeometryTD.Entity
{
public class BulletEntity : EntityBase
{
private ShooterBullet _shooterBullet;
protected override void OnInit(object userData)
{
base.OnInit(userData);
_shooterBullet = GetComponent<ShooterBullet>();
if (_shooterBullet == null)
{
Log.Error("ShooterBullet component is missing on bullet entity '{0}'.", name);
}
}
protected override void OnShow(object userData)
{
base.OnShow(userData);
if (_shooterBullet == null)
{
GameEntry.Entity.HideEntity(Entity);
return;
}
if (userData is not BulletData bulletData)
{
Log.Warning("BulletData is invalid for bullet entity '{0}'.", Id);
GameEntry.Entity.HideEntity(Entity);
return;
}
_shooterBullet.OnShow(bulletData);
if (_shooterBullet.TryConsumeDespawnRequest())
{
GameEntry.Entity.HideEntity(Entity);
}
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
if (_shooterBullet == null)
{
return;
}
_shooterBullet.Tick(elapseSeconds);
if (_shooterBullet.TryConsumeDespawnRequest())
{
GameEntry.Entity.HideEntity(Entity);
}
}
protected override void OnHide(bool isShutdown, object userData)
{
_shooterBullet?.OnReset();
base.OnHide(isShutdown, userData);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e4912a44cc6c4f3d85f6d54868780f2f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,55 @@
using Components;
using GeometryTD.Entity.EntityData;
using UnityGameFramework.Runtime;
namespace GeometryTD.Entity
{
public class DefenseTowerEntity : EntityBase
{
private DefenseTowerController _towerController;
protected override void OnInit(object userData)
{
base.OnInit(userData);
_towerController = GetComponent<DefenseTowerController>();
if (_towerController == null)
{
Log.Error("DefenseTowerController is missing on tower entity '{0}'.", name);
}
}
protected override void OnShow(object userData)
{
base.OnShow(userData);
if (_towerController == null)
{
return;
}
if (userData is not DefenseTowerData towerData)
{
Log.Warning("DefenseTowerData is invalid for tower entity '{0}'.", Id);
_towerController.OnReset();
GameEntry.Entity.HideEntity(Entity);
return;
}
_towerController.SetAutoUpdate(false);
_towerController.OnInit(towerData.Stats);
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
_towerController?.OnUpdate(elapseSeconds);
}
protected override void OnHide(bool isShutdown, object userData)
{
_towerController?.OnReset();
base.OnHide(isShutdown, userData);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d87f56efd9024709a3baf8ef8a6f4fd3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,21 +1,33 @@
using Components;
using System.Collections.Generic;
using GeometryTD.Definition;
using GeometryTD.Entity.EntityData;
using UnityEngine;
namespace GeometryTD.Entity
{
public class EnemyEntity : EntityBase
public class EnemyEntity : EntityBase, IDamageReceiver
{
private const float WaypointReachDistance = 0.05f;
private static readonly List<EnemyEntity> _activeEnemies = new();
private static readonly HashSet<int> _killedEnemyEntityIds = new();
private Transform _target;
private float _speed;
private int _maxHealth;
private int _currentHealth;
private MovementComponent _movementComponent;
private readonly List<Vector3> _pathPoints = new List<Vector3>();
private readonly List<Vector3> _pathPoints = new();
private int _pathPointIndex;
private bool _isDespawnRequested;
public static IReadOnlyList<EnemyEntity> ActiveEnemies => _activeEnemies;
public static bool TryConsumeKilledFlag(int entityId)
{
return _killedEnemyEntityIds.Remove(entityId);
}
protected override void OnInit(object userData)
{
base.OnInit(userData);
@@ -31,10 +43,14 @@ namespace GeometryTD.Entity
_pathPoints.Clear();
_pathPointIndex = 0;
_isDespawnRequested = false;
_maxHealth = 1;
_currentHealth = 1;
if (userData is EnemyData enemyData)
{
_speed = enemyData.Speed;
_target = enemyData.Player;
_maxHealth = Mathf.Max(1, enemyData.MaxHealth);
_currentHealth = _maxHealth;
if (enemyData.HasPath)
{
IReadOnlyList<Vector3> pathPoints = enemyData.PathPoints;
@@ -47,6 +63,11 @@ namespace GeometryTD.Entity
_movementComponent.OnInit(_speed, CachedTransform);
_movementComponent.SetMove(true);
_killedEnemyEntityIds.Remove(Id);
if (!_activeEnemies.Contains(this))
{
_activeEnemies.Add(this);
}
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
@@ -66,10 +87,45 @@ namespace GeometryTD.Entity
_pathPoints.Clear();
_pathPointIndex = 0;
_isDespawnRequested = false;
_maxHealth = 0;
_currentHealth = 0;
_activeEnemies.Remove(this);
_killedEnemyEntityIds.Remove(Id);
base.OnHide(isShutdown, userData);
}
private void OnDestroy()
{
_activeEnemies.Remove(this);
if (Entity != null)
{
_killedEnemyEntityIds.Remove(Entity.Id);
}
}
public void TakeDamage(int damage, AttackPropertyType attackPropertyType)
{
_ = attackPropertyType;
if (_isDespawnRequested || damage <= 0 || _currentHealth <= 0)
{
return;
}
int previousHealth = _currentHealth;
_currentHealth = Mathf.Max(0, _currentHealth - damage);
if (_maxHealth > 0)
{
GameEntry.HPBar?.ShowHPBar(this, (float)previousHealth / _maxHealth, (float)_currentHealth / _maxHealth);
}
if (_currentHealth <= 0)
{
_killedEnemyEntityIds.Add(Id);
RequestDespawn();
}
}
private void UpdatePathMovement(float elapseSeconds, float realElapseSeconds)
{
if (_isDespawnRequested)
@@ -120,6 +176,11 @@ namespace GeometryTD.Entity
}
private void DespawnOnReachHouse()
{
RequestDespawn();
}
private void RequestDespawn()
{
if (_isDespawnRequested)
{
@@ -131,4 +192,4 @@ namespace GeometryTD.Entity
GameEntry.Entity.HideEntity(Entity);
}
}
}
}
@@ -1,8 +1,12 @@
using System;
using System.Collections.Generic;
using GeometryTD;
using GeometryTD.Definition;
using GeometryTD.Entity.EntityData;
using GeometryTD.Pathfinding;
using GeometryTD.UI;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Tilemaps;
using UnityGameFramework.Runtime;
@@ -12,6 +16,7 @@ namespace GeometryTD.Entity
{
private const string PathTileName = "Path";
private const string FoundationTileName = "Foundation";
private const int DefaultTowerTypeId = 401;
private static readonly Spawner[] EmptySpawners = Array.Empty<Spawner>();
private readonly List<Vector3Int> _pathCells = new();
@@ -22,11 +27,25 @@ namespace GeometryTD.Entity
private readonly List<Vector3Int> _pathCellBuffer = new();
private readonly Dictionary<Spawner, Vector3Int> _spawnerPathStartByRef = new();
private readonly Dictionary<Spawner, List<Vector3Int>> _defaultPathCellsBySpawner = new();
private readonly Dictionary<Vector3Int, int> _towerEntityIdByFoundationCell = new();
private readonly Dictionary<int, Vector3Int> _foundationCellByTowerEntityId = new();
private readonly Dictionary<int, DefenseTowerStatsData> _towerStatsByEntityId = new();
private readonly List<int> _towerEntityIdBuffer = new();
[SerializeField] private bool _enableCombatSelectInput = true;
[SerializeField] private int _towerTypeId = DefaultTowerTypeId;
[SerializeField] private int[] _buildTowerCosts = { 80, 120, 160, 220 };
[SerializeField] private int _upgradeCost = 80;
[SerializeField] private int _destroyGain = 40;
private MapDataRefs _mapDataRefs;
private MapData _mapData;
private bool _hasHousePathCell;
private Vector3Int _housePathCell;
private CombatSelectFormUseCase _combatSelectFormUseCase;
private bool _hasSelectedFoundationCell;
private Vector3Int _selectedFoundationCell;
private int _selectedTowerEntityId;
public IReadOnlyList<Vector3Int> PathCells => _pathCells;
public IReadOnlyList<Vector3Int> FoundationCells => _foundationCells;
@@ -156,6 +175,8 @@ namespace GeometryTD.Entity
{
Log.Error("MapDataRefs is missing on map entity '{0}'.", name);
}
InitializeCombatSelectUseCase();
}
protected override void OnShow(object userData)
@@ -169,14 +190,24 @@ namespace GeometryTD.Entity
}
RefreshTiles();
ConfigureCombatSelectUseCase();
HideCombatSelectForm();
}
protected override void OnHide(bool isShutdown, object userData)
{
HideCombatSelectForm();
ClearPlacedTowers();
ClearRuntimeData();
base.OnHide(isShutdown, userData);
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
HandleCombatSelectInput();
}
private void RefreshTiles()
{
ClearRuntimeData();
@@ -295,6 +326,479 @@ namespace GeometryTD.Entity
_housePathCell = default;
_spawnerPathStartByRef.Clear();
_defaultPathCellsBySpawner.Clear();
_towerEntityIdByFoundationCell.Clear();
_foundationCellByTowerEntityId.Clear();
_towerStatsByEntityId.Clear();
_towerEntityIdBuffer.Clear();
ClearSelectedObject();
}
private void InitializeCombatSelectUseCase()
{
if (_combatSelectFormUseCase == null)
{
_combatSelectFormUseCase = new CombatSelectFormUseCase();
}
GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatSelectForm, _combatSelectFormUseCase);
}
private void ConfigureCombatSelectUseCase()
{
if (_combatSelectFormUseCase == null)
{
return;
}
_combatSelectFormUseCase.SetCoinProvider(GetCurrentCoin);
for (int i = 0; i < 4; i++)
{
int buildIndex = i;
_combatSelectFormUseCase.SetBuildAction(
buildIndex,
() => TryBuildTower(buildIndex),
GetBuildTowerCost(buildIndex));
}
_combatSelectFormUseCase.SetUpgradeAction(
TryUpgradeTower,
Mathf.Max(0, _upgradeCost));
_combatSelectFormUseCase.SetDestroyAction(
TryDestroyTower,
Mathf.Max(0, _destroyGain));
}
private void HandleCombatSelectInput()
{
if (!_enableCombatSelectInput || !Input.GetMouseButtonDown(0))
{
return;
}
if (EventSystem.current != null && EventSystem.current.IsPointerOverGameObject())
{
return;
}
if (!TryBuildCombatSelectUserData(out CombatSelectFormUserData userData))
{
userData = new CombatSelectFormUserData
{
ClickObjectType = CombatSelectClickObjectType.None
};
}
ApplySelectedObject(userData);
GameEntry.UIRouter.OpenUI(UIFormType.CombatSelectForm, userData);
}
private bool TryBuildCombatSelectUserData(out CombatSelectFormUserData userData)
{
userData = null;
if (Tilemap == null || !TryGetPointerWorldPosition(out Vector3 worldPosition, out Vector2 contentPosition))
{
return false;
}
Vector3Int clickedCell = Tilemap.WorldToCell(worldPosition);
CombatSelectClickObjectType clickObjectType = CombatSelectClickObjectType.None;
int towerEntityId = 0;
if (_towerEntityIdByFoundationCell.TryGetValue(clickedCell, out int occupiedTowerEntityId))
{
clickObjectType = CombatSelectClickObjectType.Tower;
towerEntityId = occupiedTowerEntityId;
}
else if (IsFoundationCell(clickedCell))
{
clickObjectType = CombatSelectClickObjectType.Foundation;
}
userData = new CombatSelectFormUserData
{
ClickObjectType = clickObjectType,
ContentPosition = contentPosition,
WorldPosition = worldPosition,
CellPosition = clickedCell,
TowerEntityId = towerEntityId,
UpgradeCost = Mathf.Max(0, _upgradeCost),
DestroyGain = Mathf.Max(0, _destroyGain)
};
return true;
}
private bool TryGetPointerWorldPosition(out Vector3 worldPosition, out Vector2 contentPosition)
{
worldPosition = Vector3.zero;
contentPosition = Vector2.zero;
Camera mainCamera = GameEntry.Scene != null ? GameEntry.Scene.MainCamera : Camera.main;
if (mainCamera == null)
{
return false;
}
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
float mapPlaneZ = Tilemap != null ? Tilemap.transform.position.z : CachedTransform.position.z;
Vector3 planeNormal = mainCamera.transform.forward.sqrMagnitude > Mathf.Epsilon
? -mainCamera.transform.forward
: Vector3.forward;
Plane mapPlane = new Plane(planeNormal, new Vector3(0f, 0f, mapPlaneZ));
if (!mapPlane.Raycast(ray, out float enterDistance))
{
return false;
}
worldPosition = ray.GetPoint(enterDistance);
contentPosition = BuildContentPosition(Input.mousePosition);
return true;
}
private static Vector2 BuildContentPosition(Vector3 pointerScreenPosition)
{
return new Vector2(
pointerScreenPosition.x - Screen.width * 0.5f,
pointerScreenPosition.y - Screen.height * 0.5f);
}
private void ApplySelectedObject(CombatSelectFormUserData userData)
{
if (userData == null)
{
ClearSelectedObject();
return;
}
switch (userData.ClickObjectType)
{
case CombatSelectClickObjectType.Foundation:
_hasSelectedFoundationCell = true;
_selectedFoundationCell = userData.CellPosition;
_selectedTowerEntityId = 0;
break;
case CombatSelectClickObjectType.Tower:
_hasSelectedFoundationCell = true;
_selectedFoundationCell = userData.CellPosition;
_selectedTowerEntityId = userData.TowerEntityId;
break;
default:
ClearSelectedObject();
break;
}
}
private bool TryBuildTower(int buildIndex)
{
if (!_hasSelectedFoundationCell || !IsFoundationCell(_selectedFoundationCell))
{
return false;
}
if (_towerEntityIdByFoundationCell.ContainsKey(_selectedFoundationCell))
{
return false;
}
int buildCost = GetBuildTowerCost(buildIndex);
if (!TryConsumeCoin(buildCost))
{
return false;
}
DefenseTowerStatsData towerStats = BuildTowerStats(buildIndex);
if (!TryShowTowerEntity(_selectedFoundationCell, towerStats, out int towerEntityId))
{
GameEntry.CombatNode?.AddCoin(buildCost);
return false;
}
_towerEntityIdByFoundationCell[_selectedFoundationCell] = towerEntityId;
_foundationCellByTowerEntityId[towerEntityId] = _selectedFoundationCell;
_towerStatsByEntityId[towerEntityId] = CloneTowerStats(towerStats);
_selectedTowerEntityId = towerEntityId;
return true;
}
private bool TryUpgradeTower()
{
if (!TryGetSelectedTower(out int towerEntityId, out Vector3Int foundationCell))
{
return false;
}
int upgradeCost = Mathf.Max(0, _upgradeCost);
if (!TryConsumeCoin(upgradeCost))
{
return false;
}
DefenseTowerStatsData oldStats = _towerStatsByEntityId.TryGetValue(towerEntityId, out DefenseTowerStatsData cachedStats)
? CloneTowerStats(cachedStats)
: BuildTowerStats(0);
DefenseTowerStatsData upgradedStats = CloneTowerStats(oldStats);
ApplyUpgradeToStats(upgradedStats);
HideTowerEntity(towerEntityId);
_towerEntityIdByFoundationCell.Remove(foundationCell);
_foundationCellByTowerEntityId.Remove(towerEntityId);
_towerStatsByEntityId.Remove(towerEntityId);
if (!TryShowTowerEntity(foundationCell, upgradedStats, out int newTowerEntityId))
{
if (TryShowTowerEntity(foundationCell, oldStats, out int fallbackTowerEntityId))
{
_towerEntityIdByFoundationCell[foundationCell] = fallbackTowerEntityId;
_foundationCellByTowerEntityId[fallbackTowerEntityId] = foundationCell;
_towerStatsByEntityId[fallbackTowerEntityId] = CloneTowerStats(oldStats);
_selectedTowerEntityId = fallbackTowerEntityId;
}
GameEntry.CombatNode?.AddCoin(upgradeCost);
return false;
}
_towerEntityIdByFoundationCell[foundationCell] = newTowerEntityId;
_foundationCellByTowerEntityId[newTowerEntityId] = foundationCell;
_towerStatsByEntityId[newTowerEntityId] = CloneTowerStats(upgradedStats);
_hasSelectedFoundationCell = true;
_selectedFoundationCell = foundationCell;
_selectedTowerEntityId = newTowerEntityId;
return true;
}
private bool TryDestroyTower()
{
if (!TryGetSelectedTower(out int towerEntityId, out Vector3Int foundationCell))
{
return false;
}
HideTowerEntity(towerEntityId);
_towerEntityIdByFoundationCell.Remove(foundationCell);
_foundationCellByTowerEntityId.Remove(towerEntityId);
_towerStatsByEntityId.Remove(towerEntityId);
GameEntry.CombatNode?.AddCoin(Mathf.Max(0, _destroyGain));
ClearSelectedObject();
return true;
}
private bool TryGetSelectedTower(out int towerEntityId, out Vector3Int foundationCell)
{
towerEntityId = 0;
foundationCell = default;
if (_selectedTowerEntityId == 0)
{
return false;
}
if (!_foundationCellByTowerEntityId.TryGetValue(_selectedTowerEntityId, out foundationCell))
{
return false;
}
towerEntityId = _selectedTowerEntityId;
return true;
}
private bool TryShowTowerEntity(Vector3Int foundationCell, DefenseTowerStatsData towerStats, out int towerEntityId)
{
towerEntityId = 0;
if (GameEntry.Entity == null)
{
return false;
}
int entityId = GameEntry.Entity.GenerateSerialId();
int typeId = _towerTypeId > 0 ? _towerTypeId : DefaultTowerTypeId;
Vector3 towerPosition = Tilemap != null ? Tilemap.GetCellCenterWorld(foundationCell) : foundationCell;
towerPosition.z = 0f;
var towerData = new DefenseTowerData(entityId, typeId, towerPosition, Quaternion.identity, towerStats);
GameEntry.Entity.ShowDefenseTower(towerData);
towerEntityId = entityId;
return true;
}
private static void HideTowerEntity(int towerEntityId)
{
if (towerEntityId == 0 || GameEntry.Entity == null)
{
return;
}
UnityGameFramework.Runtime.Entity towerEntity = GameEntry.Entity.GetEntity(towerEntityId);
if (towerEntity != null)
{
GameEntry.Entity.HideEntity(towerEntity);
}
}
private void ClearPlacedTowers()
{
_towerEntityIdBuffer.Clear();
foreach (KeyValuePair<int, Vector3Int> pair in _foundationCellByTowerEntityId)
{
_towerEntityIdBuffer.Add(pair.Key);
}
for (int i = 0; i < _towerEntityIdBuffer.Count; i++)
{
HideTowerEntity(_towerEntityIdBuffer[i]);
}
_towerEntityIdByFoundationCell.Clear();
_foundationCellByTowerEntityId.Clear();
_towerStatsByEntityId.Clear();
_towerEntityIdBuffer.Clear();
ClearSelectedObject();
}
private void HideCombatSelectForm()
{
_combatSelectFormUseCase?.Hide();
GameEntry.UIRouter.CloseUI(UIFormType.CombatSelectForm);
}
private void ClearSelectedObject()
{
_hasSelectedFoundationCell = false;
_selectedFoundationCell = default;
_selectedTowerEntityId = 0;
}
private int GetBuildTowerCost(int buildIndex)
{
if (_buildTowerCosts == null || buildIndex < 0 || buildIndex >= _buildTowerCosts.Length)
{
return 0;
}
return Mathf.Max(0, _buildTowerCosts[buildIndex]);
}
private static bool TryConsumeCoin(int cost)
{
int requiredCoin = Mathf.Max(0, cost);
if (requiredCoin <= 0)
{
return true;
}
if (GameEntry.CombatNode == null)
{
return false;
}
return GameEntry.CombatNode.TryConsumeCoin(requiredCoin);
}
private static int GetCurrentCoin()
{
return GameEntry.CombatNode != null ? Mathf.Max(0, GameEntry.CombatNode.CurrentCoin) : 0;
}
private static DefenseTowerStatsData BuildTowerStats(int buildIndex)
{
switch (buildIndex)
{
case 0:
return new DefenseTowerStatsData
{
AttackDamage = 10,
DamageRandomRate = 0f,
RotateSpeed = 200f,
AttackRange = 4.5f,
AttackSpeed = 1.5f,
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Physics,
Tags = Array.Empty<TagType>()
};
case 1:
return new DefenseTowerStatsData
{
AttackDamage = 13,
DamageRandomRate = 0f,
RotateSpeed = 160f,
AttackRange = 5f,
AttackSpeed = 1.2f,
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Fire,
Tags = Array.Empty<TagType>()
};
case 2:
return new DefenseTowerStatsData
{
AttackDamage = 17,
DamageRandomRate = 0f,
RotateSpeed = 140f,
AttackRange = 5.5f,
AttackSpeed = 0.95f,
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Ice,
Tags = Array.Empty<TagType>()
};
case 3:
return new DefenseTowerStatsData
{
AttackDamage = 22,
DamageRandomRate = 0f,
RotateSpeed = 120f,
AttackRange = 6f,
AttackSpeed = 0.75f,
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Poison,
Tags = Array.Empty<TagType>()
};
default:
return new DefenseTowerStatsData
{
AttackDamage = 10,
DamageRandomRate = 0f,
RotateSpeed = 180f,
AttackRange = 5f,
AttackSpeed = 1f,
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Physics,
Tags = Array.Empty<TagType>()
};
}
}
private static DefenseTowerStatsData CloneTowerStats(DefenseTowerStatsData source)
{
if (source == null)
{
return BuildTowerStats(0);
}
TagType[] copiedTags = source.Tags != null ? (TagType[])source.Tags.Clone() : Array.Empty<TagType>();
return new DefenseTowerStatsData
{
AttackDamage = source.AttackDamage,
DamageRandomRate = source.DamageRandomRate,
RotateSpeed = source.RotateSpeed,
AttackRange = source.AttackRange,
AttackSpeed = source.AttackSpeed,
AttackMethodType = source.AttackMethodType,
AttackPropertyType = source.AttackPropertyType,
Tags = copiedTags
};
}
private static void ApplyUpgradeToStats(DefenseTowerStatsData stats)
{
if (stats == null)
{
return;
}
stats.AttackDamage = Mathf.Max(1, stats.AttackDamage + 3);
stats.AttackSpeed = Mathf.Max(0.01f, stats.AttackSpeed + 0.2f);
stats.AttackRange = Mathf.Max(0.1f, stats.AttackRange + 0.4f);
stats.RotateSpeed = Mathf.Max(1f, stats.RotateSpeed + 10f);
}
}
}
}