CombatNode 相关逻辑补充
- CombatFinishForm:战斗节点的结算页面(杀死的敌人、获得的金币、获得的道具) - CombatSelectForm:战斗节点建造防御塔的选择 UI 以及对防御塔进行操作的 UI(只实现了建造逻辑,在 MapEntity 里做射线检测) - 构建了基本的防御塔三组件(ShooterMuzzleComp/BasicBearingComp/BasicBaseComp)和防御塔主控(DefenseTowerController)以及相应的实体类(DefenseTowerEntity)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user