diff --git a/Assets/GameMain/Scripts/Base/GameEntry.Builtin.cs b/Assets/GameMain/Scripts/Base/GameEntry.Builtin.cs
index 70535a7..b9ace14 100644
--- a/Assets/GameMain/Scripts/Base/GameEntry.Builtin.cs
+++ b/Assets/GameMain/Scripts/Base/GameEntry.Builtin.cs
@@ -5,14 +5,12 @@
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------
-using UnityEngine;
using UnityGameFramework.Runtime;
-
///
/// 游戏入口。
///
-public partial class GameEntry : MonoBehaviour
+public partial class GameEntry
{
///
/// 获取游戏基础组件。
diff --git a/Assets/GameMain/Scripts/Base/GameEntry.Custom.cs b/Assets/GameMain/Scripts/Base/GameEntry.Custom.cs
index c1bfed6..a2e6a8f 100644
--- a/Assets/GameMain/Scripts/Base/GameEntry.Custom.cs
+++ b/Assets/GameMain/Scripts/Base/GameEntry.Custom.cs
@@ -8,6 +8,8 @@ public partial class GameEntry
{
public static BuiltinDataComponent BuiltinData { get; private set; }
+ public static PlayerInventoryComponent PlayerInventory { get; private set; }
+
public static HPBarComponent HPBar { get; private set; }
public static UIRouterComponent UIRouter { get; private set; }
@@ -23,16 +25,12 @@ public partial class GameEntry
private static void InitCustomComponents()
{
BuiltinData = UnityGameFramework.Runtime.GameEntry.GetComponent();
+ PlayerInventory = UnityGameFramework.Runtime.GameEntry.GetComponent();
HPBar = UnityGameFramework.Runtime.GameEntry.GetComponent();
UIRouter = UnityGameFramework.Runtime.GameEntry.GetComponent();
EventNode = UnityGameFramework.Runtime.GameEntry.GetComponent();
CombatNode = UnityGameFramework.Runtime.GameEntry.GetComponent();
ShopNode = UnityGameFramework.Runtime.GameEntry.GetComponent();
ResolutionAdapter = UnityGameFramework.Runtime.GameEntry.GetComponent();
- if (ResolutionAdapter == null)
- {
- UnityGameFramework.Runtime.Log.Warning(
- "ResolutionAdapterComponent is missing. Please add it in Launcher scene and inject UI roots.");
- }
}
-}
+}
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Components/DefenseTowerController.cs b/Assets/GameMain/Scripts/Components/DefenseTowerController.cs
index 123bbc7..7bcc268 100644
--- a/Assets/GameMain/Scripts/Components/DefenseTowerController.cs
+++ b/Assets/GameMain/Scripts/Components/DefenseTowerController.cs
@@ -8,6 +8,8 @@ namespace Components
public class DefenseTowerController : MonoBehaviour
{
private const string AttackRangeIndicatorObjectName = "AttackRangeIndicator";
+ private const int MinTowerLevel = 0;
+ private const int MaxTowerLevel = 4;
private static Material s_AttackRangeSharedMaterial;
[SerializeField] private ShooterMuzzleComp _muzzleComp;
@@ -15,7 +17,6 @@ namespace Components
[SerializeField] private BasicBaseComp _baseComp;
[SerializeField] private Transform _scanOrigin;
[SerializeField] [Min(0.02f)] private float _retargetInterval = 0.1f;
- [SerializeField] private bool _autoUpdate = true;
[SerializeField] private LineRenderer _attackRangeRenderer;
[SerializeField] [Min(12)] private int _attackRangeSegments = 64;
[SerializeField] [Min(0.005f)] private float _attackRangeLineWidth = 0.08f;
@@ -25,6 +26,7 @@ namespace Components
private Transform _currentTarget;
private float _retargetTimer;
private float _attackRange;
+ private int _towerLevel;
public Transform CurrentTarget => _currentTarget;
@@ -33,17 +35,12 @@ namespace Components
ResolveComponents();
}
- private void Update()
+ public void OnInit(DefenseTowerStatsData stats)
{
- if (!_autoUpdate)
- {
- return;
- }
-
- OnUpdate(Time.deltaTime);
+ OnInit(stats, MinTowerLevel);
}
- public void OnInit(DefenseTowerStatsData stats)
+ public void OnInit(DefenseTowerStatsData stats, int towerLevel)
{
ResolveComponents();
if (stats == null)
@@ -51,10 +48,16 @@ namespace Components
return;
}
- _muzzleComp?.OnInit(stats.AttackDamage, stats.AttackMethodType);
- _bearingComp?.OnInit(stats.RotateSpeed, stats.AttackRange);
- _baseComp?.OnInit(stats.AttackSpeed, stats.AttackPropertyType);
- SetAttackRange(stats.AttackRange);
+ _towerLevel = Mathf.Clamp(towerLevel, MinTowerLevel, MaxTowerLevel);
+ int attackDamage = ResolveIntValue(stats.AttackDamage, _towerLevel, 1, 1);
+ float rotateSpeed = ResolveFloatValue(stats.RotateSpeed, _towerLevel, 180f, 1f);
+ float attackRange = ResolveFloatValue(stats.AttackRange, _towerLevel, 5f, 0.1f);
+ float attackSpeed = ResolveFloatValue(stats.AttackSpeed, _towerLevel, 1f, 0.01f);
+
+ _muzzleComp?.OnInit(attackDamage, stats.AttackMethodType);
+ _bearingComp?.OnInit(rotateSpeed, attackRange);
+ _baseComp?.OnInit(attackSpeed, stats.AttackPropertyType);
+ SetAttackRange(attackRange);
SetAttackRangeVisible(false);
_currentTarget = null;
_retargetTimer = 0f;
@@ -65,6 +68,7 @@ namespace Components
SetAttackRangeVisible(false);
_currentTarget = null;
_retargetTimer = 0f;
+ _towerLevel = MinTowerLevel;
_muzzleComp?.OnReset();
_bearingComp?.OnReset();
_baseComp?.OnReset();
@@ -86,11 +90,6 @@ namespace Components
RebuildAttackRangeGeometry();
}
- public void SetAutoUpdate(bool autoUpdate)
- {
- _autoUpdate = autoUpdate;
- }
-
public void SetTarget(Transform target)
{
_currentTarget = target;
@@ -195,6 +194,30 @@ namespace Components
return target != null && target.gameObject.activeInHierarchy;
}
+ private static int ResolveIntValue(int[] values, int level, int fallback, int minValue)
+ {
+ int resolved = Mathf.Max(minValue, fallback);
+ if (values == null || values.Length <= 0)
+ {
+ return resolved;
+ }
+
+ int index = Mathf.Clamp(level, 0, values.Length - 1);
+ return Mathf.Max(minValue, values[index]);
+ }
+
+ private static float ResolveFloatValue(float[] values, int level, float fallback, float minValue)
+ {
+ float resolved = Mathf.Max(minValue, fallback);
+ if (values == null || values.Length <= 0)
+ {
+ return resolved;
+ }
+
+ int index = Mathf.Clamp(level, 0, values.Length - 1);
+ return Mathf.Max(minValue, values[index]);
+ }
+
private void EnsureAttackRangeRenderer()
{
if (_attackRangeRenderer == null)
diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs
index 17d0d03..4e1088b 100644
--- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs
+++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs
@@ -245,6 +245,7 @@ namespace GeometryTD.CustomComponent
{
int defeatedEnemyCount = _enemyManager.DefeatedEnemyCount;
BackpackInventoryData rewardInventory = _combatResourceManager.GetRewardInventorySnapshot();
+ GameEntry.PlayerInventory?.MergeInventory(rewardInventory);
// Step 1: stop runtime and clear enemy entities only.
_enemyManager.EndPhase();
diff --git a/Assets/GameMain/Scripts/CustomComponent/PlayerInventoryComponent.cs b/Assets/GameMain/Scripts/CustomComponent/PlayerInventoryComponent.cs
new file mode 100644
index 0000000..efd7bcd
--- /dev/null
+++ b/Assets/GameMain/Scripts/CustomComponent/PlayerInventoryComponent.cs
@@ -0,0 +1,408 @@
+using System;
+using GeometryTD.Definition;
+using GeometryTD.UI;
+using UnityEngine;
+using UnityGameFramework.Runtime;
+
+namespace GeometryTD.CustomComponent
+{
+ public class PlayerInventoryComponent : GameFrameworkComponent
+ {
+ private BackpackInventoryData _inventory = new BackpackInventoryData();
+ private long _nextInstanceId = 1;
+ private bool _initialized;
+
+ public int Gold
+ {
+ get
+ {
+ EnsureInitialized();
+ return _inventory.Gold;
+ }
+ }
+
+ public void OnInit()
+ {
+ _inventory = CloneInventory(RepoFormUseCase.SampleInventory());
+ RebuildNextInstanceId();
+ _initialized = true;
+ Log.Info(
+ "PlayerInventory initialized. Gold={0}, Tower={1}, Muzzle={2}, Bearing={3}, Base={4}.",
+ _inventory.Gold,
+ _inventory.Towers.Count,
+ _inventory.MuzzleComponents.Count,
+ _inventory.BearingComponents.Count,
+ _inventory.BaseComponents.Count);
+ }
+
+ public BackpackInventoryData GetInventorySnapshot()
+ {
+ EnsureInitialized();
+ return CloneInventory(_inventory);
+ }
+
+ public void MergeInventory(BackpackInventoryData gainedInventory)
+ {
+ EnsureInitialized();
+ if (gainedInventory == null)
+ {
+ return;
+ }
+
+ int gainedGold = Mathf.Max(0, gainedInventory.Gold);
+ int gainedMuzzleCount = 0;
+ int gainedBearingCount = 0;
+ int gainedBaseCount = 0;
+ int gainedTowerCount = 0;
+
+ if (gainedGold > 0)
+ {
+ _inventory.Gold += gainedGold;
+ }
+
+ if (gainedInventory.MuzzleComponents != null)
+ {
+ for (int i = 0; i < gainedInventory.MuzzleComponents.Count; i++)
+ {
+ MuzzleCompItemData source = gainedInventory.MuzzleComponents[i];
+ if (source == null)
+ {
+ continue;
+ }
+
+ MuzzleCompItemData cloned = CloneMuzzleComp(source);
+ cloned.InstanceId = AllocateInstanceId();
+ _inventory.MuzzleComponents.Add(cloned);
+ gainedMuzzleCount++;
+ }
+ }
+
+ if (gainedInventory.BearingComponents != null)
+ {
+ for (int i = 0; i < gainedInventory.BearingComponents.Count; i++)
+ {
+ BearingCompItemData source = gainedInventory.BearingComponents[i];
+ if (source == null)
+ {
+ continue;
+ }
+
+ BearingCompItemData cloned = CloneBearingComp(source);
+ cloned.InstanceId = AllocateInstanceId();
+ _inventory.BearingComponents.Add(cloned);
+ gainedBearingCount++;
+ }
+ }
+
+ if (gainedInventory.BaseComponents != null)
+ {
+ for (int i = 0; i < gainedInventory.BaseComponents.Count; i++)
+ {
+ BaseCompItemData source = gainedInventory.BaseComponents[i];
+ if (source == null)
+ {
+ continue;
+ }
+
+ BaseCompItemData cloned = CloneBaseComp(source);
+ cloned.InstanceId = AllocateInstanceId();
+ _inventory.BaseComponents.Add(cloned);
+ gainedBaseCount++;
+ }
+ }
+
+ if (gainedInventory.Towers != null)
+ {
+ for (int i = 0; i < gainedInventory.Towers.Count; i++)
+ {
+ DefenseTowerItemData source = gainedInventory.Towers[i];
+ if (source == null)
+ {
+ continue;
+ }
+
+ DefenseTowerItemData cloned = CloneTower(source);
+ cloned.InstanceId = AllocateInstanceId();
+ _inventory.Towers.Add(cloned);
+ gainedTowerCount++;
+ }
+ }
+
+ if (gainedGold > 0 || gainedMuzzleCount > 0 || gainedBearingCount > 0 || gainedBaseCount > 0 ||
+ gainedTowerCount > 0)
+ {
+ Log.Info(
+ "PlayerInventory merged reward. Gold+{0}, Tower+{1}, Muzzle+{2}, Bearing+{3}, Base+{4}.",
+ gainedGold,
+ gainedTowerCount,
+ gainedMuzzleCount,
+ gainedBearingCount,
+ gainedBaseCount);
+ }
+ }
+
+ public bool TryConsumeGold(int costGold)
+ {
+ EnsureInitialized();
+ int resolvedCost = Mathf.Max(0, costGold);
+ if (resolvedCost <= 0)
+ {
+ return true;
+ }
+
+ if (_inventory.Gold < resolvedCost)
+ {
+ return false;
+ }
+
+ _inventory.Gold -= resolvedCost;
+ return true;
+ }
+
+ public void AddGold(int gainGold)
+ {
+ EnsureInitialized();
+ int resolvedGain = Mathf.Max(0, gainGold);
+ if (resolvedGain <= 0)
+ {
+ return;
+ }
+
+ _inventory.Gold += resolvedGain;
+ }
+
+ private void EnsureInitialized()
+ {
+ if (_initialized)
+ {
+ return;
+ }
+
+ OnInit();
+ }
+
+ private long AllocateInstanceId()
+ {
+ if (_nextInstanceId < 1)
+ {
+ _nextInstanceId = 1;
+ }
+
+ return _nextInstanceId++;
+ }
+
+ private void RebuildNextInstanceId()
+ {
+ long maxInstanceId = 0;
+ if (_inventory.Towers != null)
+ {
+ for (int i = 0; i < _inventory.Towers.Count; i++)
+ {
+ DefenseTowerItemData item = _inventory.Towers[i];
+ if (item != null)
+ {
+ maxInstanceId = Math.Max(maxInstanceId, item.InstanceId);
+ }
+ }
+ }
+
+ if (_inventory.MuzzleComponents != null)
+ {
+ for (int i = 0; i < _inventory.MuzzleComponents.Count; i++)
+ {
+ MuzzleCompItemData item = _inventory.MuzzleComponents[i];
+ if (item != null)
+ {
+ maxInstanceId = Math.Max(maxInstanceId, item.InstanceId);
+ }
+ }
+ }
+
+ if (_inventory.BearingComponents != null)
+ {
+ for (int i = 0; i < _inventory.BearingComponents.Count; i++)
+ {
+ BearingCompItemData item = _inventory.BearingComponents[i];
+ if (item != null)
+ {
+ maxInstanceId = Math.Max(maxInstanceId, item.InstanceId);
+ }
+ }
+ }
+
+ if (_inventory.BaseComponents != null)
+ {
+ for (int i = 0; i < _inventory.BaseComponents.Count; i++)
+ {
+ BaseCompItemData item = _inventory.BaseComponents[i];
+ if (item != null)
+ {
+ maxInstanceId = Math.Max(maxInstanceId, item.InstanceId);
+ }
+ }
+ }
+
+ _nextInstanceId = Math.Max(1, maxInstanceId + 1);
+ }
+
+ private static BackpackInventoryData CloneInventory(BackpackInventoryData source)
+ {
+ BackpackInventoryData cloned = new BackpackInventoryData();
+ if (source == null)
+ {
+ return cloned;
+ }
+
+ cloned.Gold = Mathf.Max(0, source.Gold);
+
+ if (source.MuzzleComponents != null)
+ {
+ for (int i = 0; i < source.MuzzleComponents.Count; i++)
+ {
+ MuzzleCompItemData item = source.MuzzleComponents[i];
+ if (item != null)
+ {
+ cloned.MuzzleComponents.Add(CloneMuzzleComp(item));
+ }
+ }
+ }
+
+ if (source.BearingComponents != null)
+ {
+ for (int i = 0; i < source.BearingComponents.Count; i++)
+ {
+ BearingCompItemData item = source.BearingComponents[i];
+ if (item != null)
+ {
+ cloned.BearingComponents.Add(CloneBearingComp(item));
+ }
+ }
+ }
+
+ if (source.BaseComponents != null)
+ {
+ for (int i = 0; i < source.BaseComponents.Count; i++)
+ {
+ BaseCompItemData item = source.BaseComponents[i];
+ if (item != null)
+ {
+ cloned.BaseComponents.Add(CloneBaseComp(item));
+ }
+ }
+ }
+
+ if (source.Towers != null)
+ {
+ for (int i = 0; i < source.Towers.Count; i++)
+ {
+ DefenseTowerItemData item = source.Towers[i];
+ if (item != null)
+ {
+ cloned.Towers.Add(CloneTower(item));
+ }
+ }
+ }
+
+ return cloned;
+ }
+
+ private static MuzzleCompItemData CloneMuzzleComp(MuzzleCompItemData source)
+ {
+ return new MuzzleCompItemData
+ {
+ InstanceId = source.InstanceId,
+ ConfigId = source.ConfigId,
+ Name = source.Name,
+ Rarity = source.Rarity,
+ Endurance = source.Endurance,
+ Constraint = source.Constraint,
+ Tags = CloneTags(source.Tags),
+ AttackDamage = CloneIntArray(source.AttackDamage),
+ DamageRandomRate = source.DamageRandomRate,
+ AttackMethodType = source.AttackMethodType
+ };
+ }
+
+ private static BearingCompItemData CloneBearingComp(BearingCompItemData source)
+ {
+ return new BearingCompItemData
+ {
+ InstanceId = source.InstanceId,
+ ConfigId = source.ConfigId,
+ Name = source.Name,
+ Rarity = source.Rarity,
+ Endurance = source.Endurance,
+ Constraint = source.Constraint,
+ Tags = CloneTags(source.Tags),
+ RotateSpeed = CloneFloatArray(source.RotateSpeed),
+ AttackRange = CloneFloatArray(source.AttackRange)
+ };
+ }
+
+ private static BaseCompItemData CloneBaseComp(BaseCompItemData source)
+ {
+ return new BaseCompItemData
+ {
+ InstanceId = source.InstanceId,
+ ConfigId = source.ConfigId,
+ Name = source.Name,
+ Rarity = source.Rarity,
+ Endurance = source.Endurance,
+ Constraint = source.Constraint,
+ Tags = CloneTags(source.Tags),
+ AttackSpeed = CloneFloatArray(source.AttackSpeed),
+ AttackPropertyType = source.AttackPropertyType
+ };
+ }
+
+ private static DefenseTowerItemData CloneTower(DefenseTowerItemData source)
+ {
+ return new DefenseTowerItemData
+ {
+ InstanceId = source.InstanceId,
+ Name = source.Name,
+ Rarity = source.Rarity,
+ Endurance = source.Endurance,
+ MuzzleComponentInstanceId = source.MuzzleComponentInstanceId,
+ BearingComponentInstanceId = source.BearingComponentInstanceId,
+ BaseComponentInstanceId = source.BaseComponentInstanceId,
+ Stats = CloneTowerStats(source.Stats)
+ };
+ }
+
+ private static DefenseTowerStatsData CloneTowerStats(DefenseTowerStatsData source)
+ {
+ if (source == null)
+ {
+ return new DefenseTowerStatsData();
+ }
+
+ 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 = CloneTags(source.Tags)
+ };
+ }
+
+ private static int[] CloneIntArray(int[] source)
+ {
+ return source != null ? (int[])source.Clone() : Array.Empty();
+ }
+
+ private static float[] CloneFloatArray(float[] source)
+ {
+ return source != null ? (float[])source.Clone() : Array.Empty();
+ }
+
+ private static TagType[] CloneTags(TagType[] source)
+ {
+ return source != null ? (TagType[])source.Clone() : Array.Empty();
+ }
+ }
+}
diff --git a/Assets/GameMain/Scripts/CustomComponent/PlayerInventoryComponent.cs.meta b/Assets/GameMain/Scripts/CustomComponent/PlayerInventoryComponent.cs.meta
new file mode 100644
index 0000000..bf5412d
--- /dev/null
+++ b/Assets/GameMain/Scripts/CustomComponent/PlayerInventoryComponent.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5db8d7bb243947fdbb9fe75a9d234d10
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GameMain/Scripts/Definition/DataStruct/DefenseTowerItemData.cs b/Assets/GameMain/Scripts/Definition/DataStruct/DefenseTowerItemData.cs
index fe3a52f..de9140f 100644
--- a/Assets/GameMain/Scripts/Definition/DataStruct/DefenseTowerItemData.cs
+++ b/Assets/GameMain/Scripts/Definition/DataStruct/DefenseTowerItemData.cs
@@ -9,11 +9,11 @@ namespace GeometryTD.Definition
[Serializable]
public sealed class DefenseTowerStatsData
{
- public int AttackDamage { get; set; }
- public float DamageRandomRate { get; set; }
- public float RotateSpeed { get; set; }
- public float AttackRange { get; set; }
- public float AttackSpeed { get; set; }
+ public int[] AttackDamage { get; set; }
+ public float[] DamageRandomRate { get; set; }
+ public float[] RotateSpeed { get; set; }
+ public float[] AttackRange { get; set; }
+ public float[] AttackSpeed { get; set; }
public AttackMethodType AttackMethodType { get; set; }
public AttackPropertyType AttackPropertyType { get; set; }
public TagType[] Tags { get; set; }
diff --git a/Assets/GameMain/Scripts/Entity/EntityData/DefenseTowerData.cs b/Assets/GameMain/Scripts/Entity/EntityData/DefenseTowerData.cs
index c67fe36..00ac405 100644
--- a/Assets/GameMain/Scripts/Entity/EntityData/DefenseTowerData.cs
+++ b/Assets/GameMain/Scripts/Entity/EntityData/DefenseTowerData.cs
@@ -8,13 +8,16 @@ namespace GeometryTD.Entity.EntityData
public class DefenseTowerData : EntityDataBase
{
[SerializeField] private DefenseTowerStatsData _stats = new DefenseTowerStatsData();
+ [SerializeField] private int _towerLevel = 0;
- public DefenseTowerData(int entityId, int typeId, Vector3 position, Quaternion rotation, DefenseTowerStatsData stats)
+ public DefenseTowerData(int entityId, int typeId, Vector3 position, Quaternion rotation,
+ DefenseTowerStatsData stats, int towerLevel = 0)
: base(entityId, typeId)
{
Position = position;
Rotation = rotation;
_stats = stats ?? new DefenseTowerStatsData();
+ _towerLevel = Mathf.Max(0, towerLevel);
}
public DefenseTowerStatsData Stats
@@ -22,5 +25,11 @@ namespace GeometryTD.Entity.EntityData
get => _stats;
set => _stats = value ?? new DefenseTowerStatsData();
}
+
+ public int TowerLevel
+ {
+ get => _towerLevel;
+ set => _towerLevel = Mathf.Max(0, value);
+ }
}
}
diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/CombatSelectInputService.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/CombatSelectInputService.cs
index 2cbcb65..47c4fc6 100644
--- a/Assets/GameMain/Scripts/Entity/EntityLogic/CombatSelectInputService.cs
+++ b/Assets/GameMain/Scripts/Entity/EntityLogic/CombatSelectInputService.cs
@@ -8,9 +8,15 @@ namespace GeometryTD.Entity
{
public sealed class CombatSelectInputService
{
- public bool TryBuildUserData(Tilemap tilemap, Transform mapTransform,
- IReadOnlyDictionary towerEntityIdByFoundationCell, Func isFoundationCell,
- int upgradeCost, int destroyGain, out CombatSelectFormUserData userData)
+ public bool TryBuildUserData(
+ Tilemap tilemap,
+ Transform mapTransform,
+ IReadOnlyDictionary towerEntityIdByFoundationCell,
+ Func isFoundationCell,
+ Func isTowerAtMaxLevel,
+ int upgradeCost,
+ int destroyGain,
+ out CombatSelectFormUserData userData)
{
userData = null;
if (tilemap == null || !TryGetPointerWorldPosition(tilemap, mapTransform, out Vector3 worldPosition,
@@ -44,13 +50,15 @@ namespace GeometryTD.Entity
WorldPosition = worldPosition,
CellPosition = clickedCell,
TowerEntityId = towerEntityId,
+ IsTowerAtMaxLevel = towerEntityId != 0 && isTowerAtMaxLevel != null && isTowerAtMaxLevel(towerEntityId),
UpgradeCost = Mathf.Max(0, upgradeCost),
DestroyGain = Mathf.Max(0, destroyGain)
};
return true;
}
- private static bool TryGetPointerWorldPosition(Tilemap tilemap, Transform mapTransform, out Vector3 worldPosition,
+ private static bool TryGetPointerWorldPosition(Tilemap tilemap, Transform mapTransform,
+ out Vector3 worldPosition,
out Vector2 contentPosition)
{
worldPosition = Vector3.zero;
@@ -63,7 +71,9 @@ namespace GeometryTD.Entity
}
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
- float mapPlaneZ = tilemap != null ? tilemap.transform.position.z : (mapTransform != null ? mapTransform.position.z : 0f);
+ float mapPlaneZ = tilemap != null
+ ? tilemap.transform.position.z
+ : (mapTransform != null ? mapTransform.position.z : 0f);
Vector3 planeNormal = mainCamera.transform.forward.sqrMagnitude > Mathf.Epsilon
? -mainCamera.transform.forward
: Vector3.forward;
diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/DefenseTowerEntity.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/DefenseTowerEntity.cs
index 18505dd..2856455 100644
--- a/Assets/GameMain/Scripts/Entity/EntityLogic/DefenseTowerEntity.cs
+++ b/Assets/GameMain/Scripts/Entity/EntityLogic/DefenseTowerEntity.cs
@@ -1,4 +1,5 @@
using Components;
+using GeometryTD.Definition;
using GeometryTD.Entity.EntityData;
using UnityGameFramework.Runtime;
@@ -13,6 +14,17 @@ namespace GeometryTD.Entity
_towerController?.SetAttackRangeVisible(visible);
}
+ public bool TryApplyStats(DefenseTowerStatsData stats, int towerLevel)
+ {
+ if (_towerController == null || stats == null)
+ {
+ return false;
+ }
+
+ _towerController.OnInit(stats, towerLevel);
+ return true;
+ }
+
protected override void OnInit(object userData)
{
base.OnInit(userData);
@@ -41,8 +53,7 @@ namespace GeometryTD.Entity
return;
}
- _towerController.SetAutoUpdate(false);
- _towerController.OnInit(towerData.Stats);
+ _towerController.OnInit(towerData.Stats, towerData.TowerLevel);
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/MapEntity.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/MapEntity.cs
index 3e51c15..68e94de 100644
--- a/Assets/GameMain/Scripts/Entity/EntityLogic/MapEntity.cs
+++ b/Assets/GameMain/Scripts/Entity/EntityLogic/MapEntity.cs
@@ -242,7 +242,8 @@ namespace GeometryTD.Entity
if (_combatSelectInputService == null ||
!_combatSelectInputService.TryBuildUserData(Tilemap, CachedTransform,
_towerPlacementService != null ? _towerPlacementService.TowerEntityIdByFoundationCell : null,
- IsFoundationCell, _upgradeCost, _destroyGain, out CombatSelectFormUserData userData))
+ IsFoundationCell, IsTowerAtMaxLevel, _upgradeCost, _destroyGain,
+ out CombatSelectFormUserData userData))
{
userData = new CombatSelectFormUserData
{
@@ -254,6 +255,11 @@ namespace GeometryTD.Entity
GameEntry.UIRouter.OpenUI(UIFormType.CombatSelectForm, userData);
}
+ private bool IsTowerAtMaxLevel(int towerEntityId)
+ {
+ return _towerPlacementService != null && _towerPlacementService.IsTowerAtMaxLevel(towerEntityId);
+ }
+
private void ApplySelectedObject(CombatSelectFormUserData userData)
{
_towerSelectionPresenter?.ApplySelectedObject(userData);
diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureMenu.cs b/Assets/GameMain/Scripts/Procedure/ProcedureMenu.cs
index 4d29aee..5bb8a26 100644
--- a/Assets/GameMain/Scripts/Procedure/ProcedureMenu.cs
+++ b/Assets/GameMain/Scripts/Procedure/ProcedureMenu.cs
@@ -35,6 +35,7 @@ namespace GeometryTD.Procedure
GameEntry.EventNode.OnInit();
GameEntry.CombatNode.OnInit(LevelThemeType.Plain);
GameEntry.ShopNode.OnInit();
+ GameEntry.PlayerInventory?.OnInit();
_repoFormUseCase = new RepoFormUseCase();
GameEntry.UIRouter.BindUIUseCase(UIFormType.RepoForm, _repoFormUseCase);
diff --git a/Assets/GameMain/Scripts/Scene/Map/TowerPlacementService.cs b/Assets/GameMain/Scripts/Scene/Map/TowerPlacementService.cs
index a1f152f..7e7e493 100644
--- a/Assets/GameMain/Scripts/Scene/Map/TowerPlacementService.cs
+++ b/Assets/GameMain/Scripts/Scene/Map/TowerPlacementService.cs
@@ -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 _towerEntityIdByFoundationCell = new();
private readonly Dictionary _foundationCellByTowerEntityId = new();
private readonly Dictionary _towerStatsByEntityId = new();
+ private readonly Dictionary _towerLevelByEntityId = new();
private readonly List _towerEntityIdBuffer = new();
public IReadOnlyDictionary TowerEntityIdByFoundationCell => _towerEntityIdByFoundationCell;
public IReadOnlyDictionary 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 isFoundationCell, int buildIndex,
int[] buildTowerCosts, int towerTypeId, Tilemap tilemap, Func tryConsumeCoin,
Action 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()
@@ -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()
@@ -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()
@@ -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()
@@ -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()
@@ -273,28 +290,63 @@ namespace GeometryTD.Map
TagType[] copiedTags = source.Tags != null ? (TagType[])source.Tags.Clone() : Array.Empty();
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(),
+ DamageRandomRate = source.DamageRandomRate != null
+ ? (float[])source.DamageRandomRate.Clone()
+ : Array.Empty(),
+ RotateSpeed = source.RotateSpeed != null ? (float[])source.RotateSpeed.Clone() : Array.Empty(),
+ AttackRange = source.AttackRange != null ? (float[])source.AttackRange.Clone() : Array.Empty(),
+ AttackSpeed = source.AttackSpeed != null ? (float[])source.AttackSpeed.Clone() : Array.Empty(),
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[] values)
+ {
+ return values != null ? values.Length : 0;
}
}
}
diff --git a/Assets/GameMain/Scripts/UI/Combat/Controller/CombatSelectFormController.cs b/Assets/GameMain/Scripts/UI/Combat/Controller/CombatSelectFormController.cs
index c380d18..fb67ffa 100644
--- a/Assets/GameMain/Scripts/UI/Combat/Controller/CombatSelectFormController.cs
+++ b/Assets/GameMain/Scripts/UI/Combat/Controller/CombatSelectFormController.cs
@@ -98,10 +98,12 @@ namespace GeometryTD.UI
switch (userData.ClickObjectType)
{
case CombatSelectClickObjectType.Foundation:
+ _useCase.SetUpgradeVisible(true);
_useCase.ShowForFoundation(userData.ContentPosition);
break;
case CombatSelectClickObjectType.Tower:
_useCase.SetUpgradePrice(userData.UpgradeCost);
+ _useCase.SetUpgradeVisible(!userData.IsTowerAtMaxLevel);
_useCase.SetDestroyGain(userData.DestroyGain);
_useCase.ShowForTower(userData.ContentPosition);
break;
@@ -133,6 +135,7 @@ namespace GeometryTD.UI
return null;
}
+ _useCase.SetUpgradeVisible(true);
_useCase.ShowForFoundation(contentPosition);
return OpenUI(_useCase.TryRefresh());
}
@@ -146,6 +149,7 @@ namespace GeometryTD.UI
}
_useCase.SetUpgradePrice(upgradeCost);
+ _useCase.SetUpgradeVisible(true);
_useCase.SetDestroyGain(destroyGain);
_useCase.ShowForTower(contentPosition);
return OpenUI(_useCase.TryRefresh());
diff --git a/Assets/GameMain/Scripts/UI/Combat/RawData/CombatSelectFormUserData.cs b/Assets/GameMain/Scripts/UI/Combat/RawData/CombatSelectFormUserData.cs
index 510056e..a9c0b9a 100644
--- a/Assets/GameMain/Scripts/UI/Combat/RawData/CombatSelectFormUserData.cs
+++ b/Assets/GameMain/Scripts/UI/Combat/RawData/CombatSelectFormUserData.cs
@@ -9,6 +9,7 @@ namespace GeometryTD.UI
public Vector3 WorldPosition;
public Vector3Int CellPosition;
public int TowerEntityId;
+ public bool IsTowerAtMaxLevel;
public int UpgradeCost;
public int DestroyGain;
}
diff --git a/Assets/GameMain/Scripts/UI/Combat/UseCase/CombatFinishFormUseCase.cs b/Assets/GameMain/Scripts/UI/Combat/UseCase/CombatFinishFormUseCase.cs
index 177b1e7..1499d84 100644
--- a/Assets/GameMain/Scripts/UI/Combat/UseCase/CombatFinishFormUseCase.cs
+++ b/Assets/GameMain/Scripts/UI/Combat/UseCase/CombatFinishFormUseCase.cs
@@ -6,7 +6,6 @@ namespace GeometryTD.UI
{
public class CombatFinishFormUseCase : IUIUseCase
{
- private readonly RepoFormUseCase _repoFormUseCase = new RepoFormUseCase();
private CombatScheduler _combatScheduler;
private int _defeatedEnemyCount;
private int _gainedGold;
@@ -57,8 +56,7 @@ namespace GeometryTD.UI
BackpackInventoryData rewardInventory = _rewardInventory;
if (rewardInventory == null)
{
- RepoFormRawData repoRawData = _repoFormUseCase.CreateInitialModel();
- rewardInventory = repoRawData != null ? repoRawData.Inventory : null;
+ rewardInventory = RepoFormUseCase.SampleInventory();
}
return new CombatFinishFormRawData
diff --git a/Assets/GameMain/Scripts/UI/Combat/UseCase/CombatSelectFormUseCase.cs b/Assets/GameMain/Scripts/UI/Combat/UseCase/CombatSelectFormUseCase.cs
index 2843949..8940786 100644
--- a/Assets/GameMain/Scripts/UI/Combat/UseCase/CombatSelectFormUseCase.cs
+++ b/Assets/GameMain/Scripts/UI/Combat/UseCase/CombatSelectFormUseCase.cs
@@ -134,6 +134,11 @@ namespace GeometryTD.UI
_upgradeOption.Price = Mathf.Max(0, cost);
}
+ public void SetUpgradeVisible(bool visible)
+ {
+ _upgradeOption.IsVisible = visible;
+ }
+
public void SetDestroyGain(int gain)
{
_destroyOption.Price = Mathf.Max(0, gain);
diff --git a/Assets/GameMain/Scripts/UI/Game/UseCase/RepoFormUseCase.cs b/Assets/GameMain/Scripts/UI/Game/UseCase/RepoFormUseCase.cs
index 1b2d1ee..9b3f188 100644
--- a/Assets/GameMain/Scripts/UI/Game/UseCase/RepoFormUseCase.cs
+++ b/Assets/GameMain/Scripts/UI/Game/UseCase/RepoFormUseCase.cs
@@ -7,14 +7,16 @@ namespace GeometryTD.UI
{
public RepoFormRawData CreateInitialModel()
{
- BackpackInventoryData sample = BuildSampleInventory();
+ BackpackInventoryData sample = GameEntry.PlayerInventory != null
+ ? GameEntry.PlayerInventory.GetInventorySnapshot()
+ : SampleInventory();
return new RepoFormRawData
{
Inventory = sample
};
}
- private static BackpackInventoryData BuildSampleInventory()
+ public static BackpackInventoryData SampleInventory()
{
BackpackInventoryData inventory = new BackpackInventoryData
{
@@ -75,11 +77,11 @@ namespace GeometryTD.UI
BaseComponentInstanceId = baseComp.InstanceId,
Stats = new DefenseTowerStatsData
{
- AttackDamage = 30,
- DamageRandomRate = 0.05f,
- RotateSpeed = 12f,
- AttackRange = 2f,
- 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.Fire,
Tags = new[] { TagType.Fire, TagType.BurnSpread }
@@ -98,11 +100,11 @@ namespace GeometryTD.UI
BaseComponentInstanceId = 0,
Stats = new DefenseTowerStatsData
{
- AttackDamage = 50,
- DamageRandomRate = 0.03f,
- RotateSpeed = 20f,
- AttackRange = 4f,
- 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 = new[] { TagType.Pierce }
diff --git a/Assets/Launcher.unity b/Assets/Launcher.unity
index fb1889a..47d7be6 100644
--- a/Assets/Launcher.unity
+++ b/Assets/Launcher.unity
@@ -153,11 +153,9 @@ Transform:
m_Children:
- {fileID: 513208573}
- {fileID: 1604812193}
- - {fileID: 159392563}
- - {fileID: 1549230541}
- - {fileID: 1758164286}
- {fileID: 2007255511}
- {fileID: 428539048}
+ - {fileID: 1322505022}
m_Father: {fileID: 1852670053}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &120093239
@@ -275,12 +273,12 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 159392562}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
- m_Father: {fileID: 119167776}
+ m_Father: {fileID: 1322505022}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &159392564
MonoBehaviour:
@@ -883,6 +881,85 @@ LightingSettings:
m_PVRTiledBaking: 0
m_NumRaysToShootPerTexel: -1
m_RespectSceneVisibilityWhenBakingGI: 0
+--- !u!1 &1239157580
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1239157581}
+ - component: {fileID: 1239157582}
+ m_Layer: 0
+ m_Name: PlayerInventory
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &1239157581
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1239157580}
+ serializedVersion: 2
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 1322505022}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &1239157582
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1239157580}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5db8d7bb243947fdbb9fe75a9d234d10, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+--- !u!1 &1322505021
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1322505022}
+ m_Layer: 0
+ m_Name: TowerDefense
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &1322505022
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1322505021}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children:
+ - {fileID: 1239157581}
+ - {fileID: 159392563}
+ - {fileID: 1549230541}
+ - {fileID: 1758164286}
+ m_Father: {fileID: 119167776}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1454214586
GameObject:
m_ObjectHideFlags: 0
@@ -991,12 +1068,12 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1549230540}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
- m_Father: {fileID: 119167776}
+ m_Father: {fileID: 1322505022}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1549230542
MonoBehaviour:
@@ -1184,12 +1261,12 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1758164285}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
- m_Father: {fileID: 119167776}
+ m_Father: {fileID: 1322505022}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1758164287
MonoBehaviour:
diff --git a/docs/TODO.md b/docs/TODO.md
index 12a1b83..e54f6c5 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -14,11 +14,11 @@
| 状态 | ID | 任务 | 交付物路径 | 验收标准 |
|-----|-------|-------------------------------------|----------------------------------------------------------------------------------------|---------------------------|
| [x] | P0-01 | 冻结 MVP 范围(只保留:战斗节点/事件节点/商店节点/节点后组装) | `docs/MVP-Scope.md` | 明确“做/不做”清单,团队评审通过 |
-| [ ] | P0-02 | 补齐数据表:组件、配件、敌人、波次、节点、事件、商店商品 | `Assets/GameMain/DataTables/*.txt` | 游戏启动可无报错加载全部新增表 |
-| [ ] | P0-03 | 新增/完善 DataRow 解析类 | `Assets/GameMain/Scripts/DataTable/*.cs` | 每个新增表都有对应 DR 类并成功解析 |
-| [ ] | P0-04 | 建立单局 Run 状态模型(金币、生命、库存、当前节点) | `Assets/GameMain/Scripts/Procedure/` | 进入/退出节点时状态可持续传递 |
+| [x] | P0-02 | 补齐数据表:组件、配件、敌人、波次、节点、事件、商店商品 | `Assets/GameMain/DataTables/*.txt` | 游戏启动可无报错加载全部新增表 |
+| [x] | P0-03 | 新增/完善 DataRow 解析类 | `Assets/GameMain/Scripts/DataTable/*.cs` | 每个新增表都有对应 DR 类并成功解析 |
+| [x] | P0-04 | 建立单局 Run 状态模型(金币、生命、库存、当前节点) | `Assets/GameMain/Scripts/Procedure/` | 进入/退出节点时状态可持续传递 |
| [ ] | P0-05 | 实现 10 节点地图生成(最后节点固定 Boss) | `Assets/GameMain/Scripts/Scene/` | 每局都生成 10 节点且第 10 节点为 Boss |
-| [ ] | P0-06 | 实现节点选择与跳转流程(战斗/事件/商店) | `Assets/GameMain/Scripts/Procedure/` | 节点完成后可返回地图并进入下个节点 |
+| [x] | P0-06 | 实现节点选择与跳转流程(战斗/事件/商店) | `Assets/GameMain/Scripts/Procedure/` | 节点完成后可返回地图并进入下个节点 |
| [ ] | P0-07 | 战斗节点基础玩法:放置塔、出怪、基地扣血、胜负判定 | `Assets/GameMain/Scripts/Entity/`
`Assets/GameMain/Scripts/Scene/` | 可完整打一场并得到胜利/失败结果 |
| [ ] | P0-08 | 胜利波次与结算规则(100/80/50/<50) | `Assets/GameMain/Scripts/Procedure/` | 结算奖励与惩罚严格匹配设计文档 |
| [ ] | P0-09 | 敌人掉落与关卡奖励(组件/配件/金币) | `Assets/GameMain/Scripts/Entity/` | 战斗结束能发放掉落并写入库存 |