仓库组件 + 防御塔升级
- `PlayerInventoryComponent`:玩家库存组件,收集金币/组件/防御塔等道具 - 补全防御塔的升级逻辑,最高 5 级
This commit is contained in:
@@ -1,26 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Entity;
|
||||
using GeometryTD.Entity.EntityData;
|
||||
using GeometryTD.Pathfinding;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.Map
|
||||
{
|
||||
public sealed class TowerPlacementService
|
||||
{
|
||||
private const int DefaultTowerTypeId = 401;
|
||||
private const int MinTowerLevel = 0;
|
||||
private const int MaxTowerLevel = 4;
|
||||
|
||||
private readonly Dictionary<Vector3Int, int> _towerEntityIdByFoundationCell = new();
|
||||
private readonly Dictionary<int, Vector3Int> _foundationCellByTowerEntityId = new();
|
||||
private readonly Dictionary<int, DefenseTowerStatsData> _towerStatsByEntityId = new();
|
||||
private readonly Dictionary<int, int> _towerLevelByEntityId = new();
|
||||
private readonly List<int> _towerEntityIdBuffer = new();
|
||||
|
||||
public IReadOnlyDictionary<Vector3Int, int> TowerEntityIdByFoundationCell => _towerEntityIdByFoundationCell;
|
||||
public IReadOnlyDictionary<int, Vector3Int> FoundationCellByTowerEntityId => _foundationCellByTowerEntityId;
|
||||
|
||||
public bool IsTowerAtMaxLevel(int towerEntityId)
|
||||
{
|
||||
if (towerEntityId == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DefenseTowerStatsData towerStats =
|
||||
_towerStatsByEntityId.TryGetValue(towerEntityId, out DefenseTowerStatsData cachedStats)
|
||||
? cachedStats
|
||||
: null;
|
||||
int currentLevel = GetTowerLevel(towerEntityId);
|
||||
int maxLevel = ResolveMaxTowerLevel(towerStats);
|
||||
return currentLevel >= maxLevel;
|
||||
}
|
||||
|
||||
public bool TryBuildTower(Vector3Int foundationCell, Func<Vector3Int, bool> isFoundationCell, int buildIndex,
|
||||
int[] buildTowerCosts, int towerTypeId, Tilemap tilemap, Func<int, bool> tryConsumeCoin,
|
||||
Action<int> addCoin,
|
||||
@@ -53,6 +71,7 @@ namespace GeometryTD.Map
|
||||
_towerEntityIdByFoundationCell[foundationCell] = newTowerEntityId;
|
||||
_foundationCellByTowerEntityId[newTowerEntityId] = foundationCell;
|
||||
_towerStatsByEntityId[newTowerEntityId] = CloneTowerStats(towerStats);
|
||||
_towerLevelByEntityId[newTowerEntityId] = MinTowerLevel;
|
||||
towerEntityId = newTowerEntityId;
|
||||
return true;
|
||||
}
|
||||
@@ -68,42 +87,36 @@ namespace GeometryTD.Map
|
||||
return false;
|
||||
}
|
||||
|
||||
int requiredUpgradeCost = Mathf.Max(0, upgradeCost);
|
||||
if (tryConsumeCoin != null && !tryConsumeCoin(requiredUpgradeCost))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DefenseTowerStatsData oldStats =
|
||||
DefenseTowerStatsData towerStats =
|
||||
_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, towerTypeId, tilemap, out int newTowerEntityId))
|
||||
int currentTowerLevel = GetTowerLevel(towerEntityId);
|
||||
int maxTowerLevel = ResolveMaxTowerLevel(towerStats);
|
||||
if (currentTowerLevel >= maxTowerLevel)
|
||||
{
|
||||
if (TryShowTowerEntity(foundationCell, oldStats, towerTypeId, tilemap, out int fallbackTowerEntityId))
|
||||
{
|
||||
_towerEntityIdByFoundationCell[foundationCell] = fallbackTowerEntityId;
|
||||
_foundationCellByTowerEntityId[fallbackTowerEntityId] = foundationCell;
|
||||
_towerStatsByEntityId[fallbackTowerEntityId] = CloneTowerStats(oldStats);
|
||||
resultTowerEntityId = fallbackTowerEntityId;
|
||||
}
|
||||
|
||||
addCoin?.Invoke(requiredUpgradeCost);
|
||||
resultTowerEntityId = towerEntityId;
|
||||
return false;
|
||||
}
|
||||
|
||||
_towerEntityIdByFoundationCell[foundationCell] = newTowerEntityId;
|
||||
_foundationCellByTowerEntityId[newTowerEntityId] = foundationCell;
|
||||
_towerStatsByEntityId[newTowerEntityId] = CloneTowerStats(upgradedStats);
|
||||
resultTowerEntityId = newTowerEntityId;
|
||||
int requiredUpgradeCost = Mathf.Max(0, upgradeCost);
|
||||
if (tryConsumeCoin != null && !tryConsumeCoin(requiredUpgradeCost))
|
||||
{
|
||||
resultTowerEntityId = towerEntityId;
|
||||
return false;
|
||||
}
|
||||
|
||||
int nextTowerLevel = Mathf.Clamp(currentTowerLevel + 1, MinTowerLevel, maxTowerLevel);
|
||||
if (!TryApplyTowerStats(towerEntityId, towerStats, nextTowerLevel))
|
||||
{
|
||||
addCoin?.Invoke(requiredUpgradeCost);
|
||||
resultTowerEntityId = towerEntityId;
|
||||
return false;
|
||||
}
|
||||
|
||||
_towerStatsByEntityId[towerEntityId] = CloneTowerStats(towerStats);
|
||||
_towerLevelByEntityId[towerEntityId] = nextTowerLevel;
|
||||
resultTowerEntityId = towerEntityId;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -120,6 +133,7 @@ namespace GeometryTD.Map
|
||||
_towerEntityIdByFoundationCell.Remove(foundationCell);
|
||||
_foundationCellByTowerEntityId.Remove(towerEntityId);
|
||||
_towerStatsByEntityId.Remove(towerEntityId);
|
||||
_towerLevelByEntityId.Remove(towerEntityId);
|
||||
addCoin?.Invoke(Mathf.Max(0, destroyGain));
|
||||
return true;
|
||||
}
|
||||
@@ -140,6 +154,7 @@ namespace GeometryTD.Map
|
||||
_towerEntityIdByFoundationCell.Clear();
|
||||
_foundationCellByTowerEntityId.Clear();
|
||||
_towerStatsByEntityId.Clear();
|
||||
_towerLevelByEntityId.Clear();
|
||||
_towerEntityIdBuffer.Clear();
|
||||
}
|
||||
|
||||
@@ -148,6 +163,7 @@ namespace GeometryTD.Map
|
||||
_towerEntityIdByFoundationCell.Clear();
|
||||
_foundationCellByTowerEntityId.Clear();
|
||||
_towerStatsByEntityId.Clear();
|
||||
_towerLevelByEntityId.Clear();
|
||||
_towerEntityIdBuffer.Clear();
|
||||
}
|
||||
|
||||
@@ -175,7 +191,8 @@ namespace GeometryTD.Map
|
||||
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);
|
||||
var towerData = new DefenseTowerData(entityId, typeId, towerPosition, Quaternion.identity, towerStats,
|
||||
MinTowerLevel);
|
||||
GameEntry.Entity.ShowDefenseTower(towerData);
|
||||
|
||||
towerEntityId = entityId;
|
||||
@@ -203,11 +220,11 @@ namespace GeometryTD.Map
|
||||
case 0:
|
||||
return new DefenseTowerStatsData
|
||||
{
|
||||
AttackDamage = 200,
|
||||
DamageRandomRate = 0f,
|
||||
RotateSpeed = 200f,
|
||||
AttackRange = 4.5f,
|
||||
AttackSpeed = 1.5f,
|
||||
AttackDamage = new[] { 200, 220, 240, 260, 300 },
|
||||
DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f },
|
||||
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
|
||||
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
|
||||
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
|
||||
AttackMethodType = AttackMethodType.NormalBullet,
|
||||
AttackPropertyType = AttackPropertyType.Physics,
|
||||
Tags = Array.Empty<TagType>()
|
||||
@@ -215,11 +232,11 @@ namespace GeometryTD.Map
|
||||
case 1:
|
||||
return new DefenseTowerStatsData
|
||||
{
|
||||
AttackDamage = 260,
|
||||
DamageRandomRate = 0f,
|
||||
RotateSpeed = 160f,
|
||||
AttackRange = 5f,
|
||||
AttackSpeed = 1.2f,
|
||||
AttackDamage = new[] { 200, 220, 240, 260, 300 },
|
||||
DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f },
|
||||
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
|
||||
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
|
||||
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
|
||||
AttackMethodType = AttackMethodType.NormalBullet,
|
||||
AttackPropertyType = AttackPropertyType.Fire,
|
||||
Tags = Array.Empty<TagType>()
|
||||
@@ -227,11 +244,11 @@ namespace GeometryTD.Map
|
||||
case 2:
|
||||
return new DefenseTowerStatsData
|
||||
{
|
||||
AttackDamage = 340,
|
||||
DamageRandomRate = 0f,
|
||||
RotateSpeed = 140f,
|
||||
AttackRange = 5.5f,
|
||||
AttackSpeed = 0.95f,
|
||||
AttackDamage = new[] { 200, 220, 240, 260, 300 },
|
||||
DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f },
|
||||
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
|
||||
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
|
||||
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
|
||||
AttackMethodType = AttackMethodType.NormalBullet,
|
||||
AttackPropertyType = AttackPropertyType.Ice,
|
||||
Tags = Array.Empty<TagType>()
|
||||
@@ -239,11 +256,11 @@ namespace GeometryTD.Map
|
||||
case 3:
|
||||
return new DefenseTowerStatsData
|
||||
{
|
||||
AttackDamage = 440,
|
||||
DamageRandomRate = 0f,
|
||||
RotateSpeed = 120f,
|
||||
AttackRange = 6f,
|
||||
AttackSpeed = 0.75f,
|
||||
AttackDamage = new[] { 200, 220, 240, 260, 300 },
|
||||
DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f },
|
||||
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
|
||||
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
|
||||
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
|
||||
AttackMethodType = AttackMethodType.NormalBullet,
|
||||
AttackPropertyType = AttackPropertyType.Poison,
|
||||
Tags = Array.Empty<TagType>()
|
||||
@@ -251,11 +268,11 @@ namespace GeometryTD.Map
|
||||
default:
|
||||
return new DefenseTowerStatsData
|
||||
{
|
||||
AttackDamage = 200,
|
||||
DamageRandomRate = 0f,
|
||||
RotateSpeed = 180f,
|
||||
AttackRange = 5f,
|
||||
AttackSpeed = 1f,
|
||||
AttackDamage = new[] { 200, 220, 240, 260, 300 },
|
||||
DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f },
|
||||
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
|
||||
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
|
||||
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
|
||||
AttackMethodType = AttackMethodType.NormalBullet,
|
||||
AttackPropertyType = AttackPropertyType.Physics,
|
||||
Tags = Array.Empty<TagType>()
|
||||
@@ -273,28 +290,63 @@ namespace GeometryTD.Map
|
||||
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,
|
||||
AttackDamage = source.AttackDamage != null ? (int[])source.AttackDamage.Clone() : Array.Empty<int>(),
|
||||
DamageRandomRate = source.DamageRandomRate != null
|
||||
? (float[])source.DamageRandomRate.Clone()
|
||||
: Array.Empty<float>(),
|
||||
RotateSpeed = source.RotateSpeed != null ? (float[])source.RotateSpeed.Clone() : Array.Empty<float>(),
|
||||
AttackRange = source.AttackRange != null ? (float[])source.AttackRange.Clone() : Array.Empty<float>(),
|
||||
AttackSpeed = source.AttackSpeed != null ? (float[])source.AttackSpeed.Clone() : Array.Empty<float>(),
|
||||
AttackMethodType = source.AttackMethodType,
|
||||
AttackPropertyType = source.AttackPropertyType,
|
||||
Tags = copiedTags
|
||||
};
|
||||
}
|
||||
|
||||
private static void ApplyUpgradeToStats(DefenseTowerStatsData stats)
|
||||
private int GetTowerLevel(int towerEntityId)
|
||||
{
|
||||
if (stats == null)
|
||||
if (towerEntityId == 0 || !_towerLevelByEntityId.TryGetValue(towerEntityId, out int towerLevel))
|
||||
{
|
||||
return;
|
||||
return MinTowerLevel;
|
||||
}
|
||||
|
||||
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);
|
||||
return Mathf.Clamp(towerLevel, MinTowerLevel, MaxTowerLevel);
|
||||
}
|
||||
|
||||
private static int ResolveMaxTowerLevel(DefenseTowerStatsData stats)
|
||||
{
|
||||
int maxCount = Mathf.Max(
|
||||
GetLength(stats?.AttackDamage),
|
||||
GetLength(stats?.DamageRandomRate),
|
||||
GetLength(stats?.RotateSpeed),
|
||||
GetLength(stats?.AttackRange),
|
||||
GetLength(stats?.AttackSpeed));
|
||||
if (maxCount <= 0)
|
||||
{
|
||||
return MinTowerLevel;
|
||||
}
|
||||
|
||||
return Mathf.Clamp(maxCount - 1, MinTowerLevel, MaxTowerLevel);
|
||||
}
|
||||
|
||||
private static bool TryApplyTowerStats(int towerEntityId, DefenseTowerStatsData towerStats, int towerLevel)
|
||||
{
|
||||
if (towerEntityId == 0 || towerStats == null || GameEntry.Entity == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GameEntry.Entity.GetGameEntity(towerEntityId) is not DefenseTowerEntity towerEntity)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return towerEntity.TryApplyStats(towerStats, towerLevel);
|
||||
}
|
||||
|
||||
private static int GetLength<T>(T[] values)
|
||||
{
|
||||
return values != null ? values.Length : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user