仓库组件 + 防御塔升级
- `PlayerInventoryComponent`:玩家库存组件,收集金币/组件/防御塔等道具 - 补全防御塔的升级逻辑,最高 5 级
This commit is contained in:
parent
5ba94828a8
commit
daba9cbdf9
|
|
@ -5,14 +5,12 @@
|
||||||
// Feedback: mailto:ellan@gameframework.cn
|
// Feedback: mailto:ellan@gameframework.cn
|
||||||
//------------------------------------------------------------
|
//------------------------------------------------------------
|
||||||
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 游戏入口。
|
/// 游戏入口。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class GameEntry : MonoBehaviour
|
public partial class GameEntry
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取游戏基础组件。
|
/// 获取游戏基础组件。
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ public partial class GameEntry
|
||||||
{
|
{
|
||||||
public static BuiltinDataComponent BuiltinData { get; private set; }
|
public static BuiltinDataComponent BuiltinData { get; private set; }
|
||||||
|
|
||||||
|
public static PlayerInventoryComponent PlayerInventory { get; private set; }
|
||||||
|
|
||||||
public static HPBarComponent HPBar { get; private set; }
|
public static HPBarComponent HPBar { get; private set; }
|
||||||
|
|
||||||
public static UIRouterComponent UIRouter { get; private set; }
|
public static UIRouterComponent UIRouter { get; private set; }
|
||||||
|
|
@ -23,16 +25,12 @@ public partial class GameEntry
|
||||||
private static void InitCustomComponents()
|
private static void InitCustomComponents()
|
||||||
{
|
{
|
||||||
BuiltinData = UnityGameFramework.Runtime.GameEntry.GetComponent<BuiltinDataComponent>();
|
BuiltinData = UnityGameFramework.Runtime.GameEntry.GetComponent<BuiltinDataComponent>();
|
||||||
|
PlayerInventory = UnityGameFramework.Runtime.GameEntry.GetComponent<PlayerInventoryComponent>();
|
||||||
HPBar = UnityGameFramework.Runtime.GameEntry.GetComponent<HPBarComponent>();
|
HPBar = UnityGameFramework.Runtime.GameEntry.GetComponent<HPBarComponent>();
|
||||||
UIRouter = UnityGameFramework.Runtime.GameEntry.GetComponent<UIRouterComponent>();
|
UIRouter = UnityGameFramework.Runtime.GameEntry.GetComponent<UIRouterComponent>();
|
||||||
EventNode = UnityGameFramework.Runtime.GameEntry.GetComponent<EventNodeComponent>();
|
EventNode = UnityGameFramework.Runtime.GameEntry.GetComponent<EventNodeComponent>();
|
||||||
CombatNode = UnityGameFramework.Runtime.GameEntry.GetComponent<CombatNodeComponent>();
|
CombatNode = UnityGameFramework.Runtime.GameEntry.GetComponent<CombatNodeComponent>();
|
||||||
ShopNode = UnityGameFramework.Runtime.GameEntry.GetComponent<ShopNodeComponent>();
|
ShopNode = UnityGameFramework.Runtime.GameEntry.GetComponent<ShopNodeComponent>();
|
||||||
ResolutionAdapter = UnityGameFramework.Runtime.GameEntry.GetComponent<ResolutionAdapterComponent>();
|
ResolutionAdapter = UnityGameFramework.Runtime.GameEntry.GetComponent<ResolutionAdapterComponent>();
|
||||||
if (ResolutionAdapter == null)
|
|
||||||
{
|
|
||||||
UnityGameFramework.Runtime.Log.Warning(
|
|
||||||
"ResolutionAdapterComponent is missing. Please add it in Launcher scene and inject UI roots.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,6 +8,8 @@ namespace Components
|
||||||
public class DefenseTowerController : MonoBehaviour
|
public class DefenseTowerController : MonoBehaviour
|
||||||
{
|
{
|
||||||
private const string AttackRangeIndicatorObjectName = "AttackRangeIndicator";
|
private const string AttackRangeIndicatorObjectName = "AttackRangeIndicator";
|
||||||
|
private const int MinTowerLevel = 0;
|
||||||
|
private const int MaxTowerLevel = 4;
|
||||||
private static Material s_AttackRangeSharedMaterial;
|
private static Material s_AttackRangeSharedMaterial;
|
||||||
|
|
||||||
[SerializeField] private ShooterMuzzleComp _muzzleComp;
|
[SerializeField] private ShooterMuzzleComp _muzzleComp;
|
||||||
|
|
@ -15,7 +17,6 @@ namespace Components
|
||||||
[SerializeField] private BasicBaseComp _baseComp;
|
[SerializeField] private BasicBaseComp _baseComp;
|
||||||
[SerializeField] private Transform _scanOrigin;
|
[SerializeField] private Transform _scanOrigin;
|
||||||
[SerializeField] [Min(0.02f)] private float _retargetInterval = 0.1f;
|
[SerializeField] [Min(0.02f)] private float _retargetInterval = 0.1f;
|
||||||
[SerializeField] private bool _autoUpdate = true;
|
|
||||||
[SerializeField] private LineRenderer _attackRangeRenderer;
|
[SerializeField] private LineRenderer _attackRangeRenderer;
|
||||||
[SerializeField] [Min(12)] private int _attackRangeSegments = 64;
|
[SerializeField] [Min(12)] private int _attackRangeSegments = 64;
|
||||||
[SerializeField] [Min(0.005f)] private float _attackRangeLineWidth = 0.08f;
|
[SerializeField] [Min(0.005f)] private float _attackRangeLineWidth = 0.08f;
|
||||||
|
|
@ -25,6 +26,7 @@ namespace Components
|
||||||
private Transform _currentTarget;
|
private Transform _currentTarget;
|
||||||
private float _retargetTimer;
|
private float _retargetTimer;
|
||||||
private float _attackRange;
|
private float _attackRange;
|
||||||
|
private int _towerLevel;
|
||||||
|
|
||||||
public Transform CurrentTarget => _currentTarget;
|
public Transform CurrentTarget => _currentTarget;
|
||||||
|
|
||||||
|
|
@ -33,17 +35,12 @@ namespace Components
|
||||||
ResolveComponents();
|
ResolveComponents();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Update()
|
public void OnInit(DefenseTowerStatsData stats)
|
||||||
{
|
{
|
||||||
if (!_autoUpdate)
|
OnInit(stats, MinTowerLevel);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
OnUpdate(Time.deltaTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnInit(DefenseTowerStatsData stats)
|
public void OnInit(DefenseTowerStatsData stats, int towerLevel)
|
||||||
{
|
{
|
||||||
ResolveComponents();
|
ResolveComponents();
|
||||||
if (stats == null)
|
if (stats == null)
|
||||||
|
|
@ -51,10 +48,16 @@ namespace Components
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_muzzleComp?.OnInit(stats.AttackDamage, stats.AttackMethodType);
|
_towerLevel = Mathf.Clamp(towerLevel, MinTowerLevel, MaxTowerLevel);
|
||||||
_bearingComp?.OnInit(stats.RotateSpeed, stats.AttackRange);
|
int attackDamage = ResolveIntValue(stats.AttackDamage, _towerLevel, 1, 1);
|
||||||
_baseComp?.OnInit(stats.AttackSpeed, stats.AttackPropertyType);
|
float rotateSpeed = ResolveFloatValue(stats.RotateSpeed, _towerLevel, 180f, 1f);
|
||||||
SetAttackRange(stats.AttackRange);
|
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);
|
SetAttackRangeVisible(false);
|
||||||
_currentTarget = null;
|
_currentTarget = null;
|
||||||
_retargetTimer = 0f;
|
_retargetTimer = 0f;
|
||||||
|
|
@ -65,6 +68,7 @@ namespace Components
|
||||||
SetAttackRangeVisible(false);
|
SetAttackRangeVisible(false);
|
||||||
_currentTarget = null;
|
_currentTarget = null;
|
||||||
_retargetTimer = 0f;
|
_retargetTimer = 0f;
|
||||||
|
_towerLevel = MinTowerLevel;
|
||||||
_muzzleComp?.OnReset();
|
_muzzleComp?.OnReset();
|
||||||
_bearingComp?.OnReset();
|
_bearingComp?.OnReset();
|
||||||
_baseComp?.OnReset();
|
_baseComp?.OnReset();
|
||||||
|
|
@ -86,11 +90,6 @@ namespace Components
|
||||||
RebuildAttackRangeGeometry();
|
RebuildAttackRangeGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetAutoUpdate(bool autoUpdate)
|
|
||||||
{
|
|
||||||
_autoUpdate = autoUpdate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetTarget(Transform target)
|
public void SetTarget(Transform target)
|
||||||
{
|
{
|
||||||
_currentTarget = target;
|
_currentTarget = target;
|
||||||
|
|
@ -195,6 +194,30 @@ namespace Components
|
||||||
return target != null && target.gameObject.activeInHierarchy;
|
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()
|
private void EnsureAttackRangeRenderer()
|
||||||
{
|
{
|
||||||
if (_attackRangeRenderer == null)
|
if (_attackRangeRenderer == null)
|
||||||
|
|
|
||||||
|
|
@ -245,6 +245,7 @@ namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
int defeatedEnemyCount = _enemyManager.DefeatedEnemyCount;
|
int defeatedEnemyCount = _enemyManager.DefeatedEnemyCount;
|
||||||
BackpackInventoryData rewardInventory = _combatResourceManager.GetRewardInventorySnapshot();
|
BackpackInventoryData rewardInventory = _combatResourceManager.GetRewardInventorySnapshot();
|
||||||
|
GameEntry.PlayerInventory?.MergeInventory(rewardInventory);
|
||||||
|
|
||||||
// Step 1: stop runtime and clear enemy entities only.
|
// Step 1: stop runtime and clear enemy entities only.
|
||||||
_enemyManager.EndPhase();
|
_enemyManager.EndPhase();
|
||||||
|
|
|
||||||
|
|
@ -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<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float[] CloneFloatArray(float[] source)
|
||||||
|
{
|
||||||
|
return source != null ? (float[])source.Clone() : Array.Empty<float>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TagType[] CloneTags(TagType[] source)
|
||||||
|
{
|
||||||
|
return source != null ? (TagType[])source.Clone() : Array.Empty<TagType>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5db8d7bb243947fdbb9fe75a9d234d10
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -9,11 +9,11 @@ namespace GeometryTD.Definition
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public sealed class DefenseTowerStatsData
|
public sealed class DefenseTowerStatsData
|
||||||
{
|
{
|
||||||
public int AttackDamage { get; set; }
|
public int[] AttackDamage { get; set; }
|
||||||
public float DamageRandomRate { get; set; }
|
public float[] DamageRandomRate { get; set; }
|
||||||
public float RotateSpeed { get; set; }
|
public float[] RotateSpeed { get; set; }
|
||||||
public float AttackRange { get; set; }
|
public float[] AttackRange { get; set; }
|
||||||
public float AttackSpeed { get; set; }
|
public float[] AttackSpeed { get; set; }
|
||||||
public AttackMethodType AttackMethodType { get; set; }
|
public AttackMethodType AttackMethodType { get; set; }
|
||||||
public AttackPropertyType AttackPropertyType { get; set; }
|
public AttackPropertyType AttackPropertyType { get; set; }
|
||||||
public TagType[] Tags { get; set; }
|
public TagType[] Tags { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,16 @@ namespace GeometryTD.Entity.EntityData
|
||||||
public class DefenseTowerData : EntityDataBase
|
public class DefenseTowerData : EntityDataBase
|
||||||
{
|
{
|
||||||
[SerializeField] private DefenseTowerStatsData _stats = new DefenseTowerStatsData();
|
[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)
|
: base(entityId, typeId)
|
||||||
{
|
{
|
||||||
Position = position;
|
Position = position;
|
||||||
Rotation = rotation;
|
Rotation = rotation;
|
||||||
_stats = stats ?? new DefenseTowerStatsData();
|
_stats = stats ?? new DefenseTowerStatsData();
|
||||||
|
_towerLevel = Mathf.Max(0, towerLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefenseTowerStatsData Stats
|
public DefenseTowerStatsData Stats
|
||||||
|
|
@ -22,5 +25,11 @@ namespace GeometryTD.Entity.EntityData
|
||||||
get => _stats;
|
get => _stats;
|
||||||
set => _stats = value ?? new DefenseTowerStatsData();
|
set => _stats = value ?? new DefenseTowerStatsData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int TowerLevel
|
||||||
|
{
|
||||||
|
get => _towerLevel;
|
||||||
|
set => _towerLevel = Mathf.Max(0, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,15 @@ namespace GeometryTD.Entity
|
||||||
{
|
{
|
||||||
public sealed class CombatSelectInputService
|
public sealed class CombatSelectInputService
|
||||||
{
|
{
|
||||||
public bool TryBuildUserData(Tilemap tilemap, Transform mapTransform,
|
public bool TryBuildUserData(
|
||||||
IReadOnlyDictionary<Vector3Int, int> towerEntityIdByFoundationCell, Func<Vector3Int, bool> isFoundationCell,
|
Tilemap tilemap,
|
||||||
int upgradeCost, int destroyGain, out CombatSelectFormUserData userData)
|
Transform mapTransform,
|
||||||
|
IReadOnlyDictionary<Vector3Int, int> towerEntityIdByFoundationCell,
|
||||||
|
Func<Vector3Int, bool> isFoundationCell,
|
||||||
|
Func<int, bool> isTowerAtMaxLevel,
|
||||||
|
int upgradeCost,
|
||||||
|
int destroyGain,
|
||||||
|
out CombatSelectFormUserData userData)
|
||||||
{
|
{
|
||||||
userData = null;
|
userData = null;
|
||||||
if (tilemap == null || !TryGetPointerWorldPosition(tilemap, mapTransform, out Vector3 worldPosition,
|
if (tilemap == null || !TryGetPointerWorldPosition(tilemap, mapTransform, out Vector3 worldPosition,
|
||||||
|
|
@ -44,13 +50,15 @@ namespace GeometryTD.Entity
|
||||||
WorldPosition = worldPosition,
|
WorldPosition = worldPosition,
|
||||||
CellPosition = clickedCell,
|
CellPosition = clickedCell,
|
||||||
TowerEntityId = towerEntityId,
|
TowerEntityId = towerEntityId,
|
||||||
|
IsTowerAtMaxLevel = towerEntityId != 0 && isTowerAtMaxLevel != null && isTowerAtMaxLevel(towerEntityId),
|
||||||
UpgradeCost = Mathf.Max(0, upgradeCost),
|
UpgradeCost = Mathf.Max(0, upgradeCost),
|
||||||
DestroyGain = Mathf.Max(0, destroyGain)
|
DestroyGain = Mathf.Max(0, destroyGain)
|
||||||
};
|
};
|
||||||
return true;
|
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)
|
out Vector2 contentPosition)
|
||||||
{
|
{
|
||||||
worldPosition = Vector3.zero;
|
worldPosition = Vector3.zero;
|
||||||
|
|
@ -63,7 +71,9 @@ namespace GeometryTD.Entity
|
||||||
}
|
}
|
||||||
|
|
||||||
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
|
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
|
Vector3 planeNormal = mainCamera.transform.forward.sqrMagnitude > Mathf.Epsilon
|
||||||
? -mainCamera.transform.forward
|
? -mainCamera.transform.forward
|
||||||
: Vector3.forward;
|
: Vector3.forward;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Components;
|
using Components;
|
||||||
|
using GeometryTD.Definition;
|
||||||
using GeometryTD.Entity.EntityData;
|
using GeometryTD.Entity.EntityData;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
|
|
@ -13,6 +14,17 @@ namespace GeometryTD.Entity
|
||||||
_towerController?.SetAttackRangeVisible(visible);
|
_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)
|
protected override void OnInit(object userData)
|
||||||
{
|
{
|
||||||
base.OnInit(userData);
|
base.OnInit(userData);
|
||||||
|
|
@ -41,8 +53,7 @@ namespace GeometryTD.Entity
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_towerController.SetAutoUpdate(false);
|
_towerController.OnInit(towerData.Stats, towerData.TowerLevel);
|
||||||
_towerController.OnInit(towerData.Stats);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,8 @@ namespace GeometryTD.Entity
|
||||||
if (_combatSelectInputService == null ||
|
if (_combatSelectInputService == null ||
|
||||||
!_combatSelectInputService.TryBuildUserData(Tilemap, CachedTransform,
|
!_combatSelectInputService.TryBuildUserData(Tilemap, CachedTransform,
|
||||||
_towerPlacementService != null ? _towerPlacementService.TowerEntityIdByFoundationCell : null,
|
_towerPlacementService != null ? _towerPlacementService.TowerEntityIdByFoundationCell : null,
|
||||||
IsFoundationCell, _upgradeCost, _destroyGain, out CombatSelectFormUserData userData))
|
IsFoundationCell, IsTowerAtMaxLevel, _upgradeCost, _destroyGain,
|
||||||
|
out CombatSelectFormUserData userData))
|
||||||
{
|
{
|
||||||
userData = new CombatSelectFormUserData
|
userData = new CombatSelectFormUserData
|
||||||
{
|
{
|
||||||
|
|
@ -254,6 +255,11 @@ namespace GeometryTD.Entity
|
||||||
GameEntry.UIRouter.OpenUI(UIFormType.CombatSelectForm, userData);
|
GameEntry.UIRouter.OpenUI(UIFormType.CombatSelectForm, userData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsTowerAtMaxLevel(int towerEntityId)
|
||||||
|
{
|
||||||
|
return _towerPlacementService != null && _towerPlacementService.IsTowerAtMaxLevel(towerEntityId);
|
||||||
|
}
|
||||||
|
|
||||||
private void ApplySelectedObject(CombatSelectFormUserData userData)
|
private void ApplySelectedObject(CombatSelectFormUserData userData)
|
||||||
{
|
{
|
||||||
_towerSelectionPresenter?.ApplySelectedObject(userData);
|
_towerSelectionPresenter?.ApplySelectedObject(userData);
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ namespace GeometryTD.Procedure
|
||||||
GameEntry.EventNode.OnInit();
|
GameEntry.EventNode.OnInit();
|
||||||
GameEntry.CombatNode.OnInit(LevelThemeType.Plain);
|
GameEntry.CombatNode.OnInit(LevelThemeType.Plain);
|
||||||
GameEntry.ShopNode.OnInit();
|
GameEntry.ShopNode.OnInit();
|
||||||
|
GameEntry.PlayerInventory?.OnInit();
|
||||||
|
|
||||||
_repoFormUseCase = new RepoFormUseCase();
|
_repoFormUseCase = new RepoFormUseCase();
|
||||||
GameEntry.UIRouter.BindUIUseCase(UIFormType.RepoForm, _repoFormUseCase);
|
GameEntry.UIRouter.BindUIUseCase(UIFormType.RepoForm, _repoFormUseCase);
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,44 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
|
using GeometryTD.Entity;
|
||||||
using GeometryTD.Entity.EntityData;
|
using GeometryTD.Entity.EntityData;
|
||||||
using GeometryTD.Pathfinding;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Tilemaps;
|
using UnityEngine.Tilemaps;
|
||||||
using UnityGameFramework.Runtime;
|
|
||||||
|
|
||||||
namespace GeometryTD.Map
|
namespace GeometryTD.Map
|
||||||
{
|
{
|
||||||
public sealed class TowerPlacementService
|
public sealed class TowerPlacementService
|
||||||
{
|
{
|
||||||
private const int DefaultTowerTypeId = 401;
|
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<Vector3Int, int> _towerEntityIdByFoundationCell = new();
|
||||||
private readonly Dictionary<int, Vector3Int> _foundationCellByTowerEntityId = new();
|
private readonly Dictionary<int, Vector3Int> _foundationCellByTowerEntityId = new();
|
||||||
private readonly Dictionary<int, DefenseTowerStatsData> _towerStatsByEntityId = new();
|
private readonly Dictionary<int, DefenseTowerStatsData> _towerStatsByEntityId = new();
|
||||||
|
private readonly Dictionary<int, int> _towerLevelByEntityId = new();
|
||||||
private readonly List<int> _towerEntityIdBuffer = new();
|
private readonly List<int> _towerEntityIdBuffer = new();
|
||||||
|
|
||||||
public IReadOnlyDictionary<Vector3Int, int> TowerEntityIdByFoundationCell => _towerEntityIdByFoundationCell;
|
public IReadOnlyDictionary<Vector3Int, int> TowerEntityIdByFoundationCell => _towerEntityIdByFoundationCell;
|
||||||
public IReadOnlyDictionary<int, Vector3Int> FoundationCellByTowerEntityId => _foundationCellByTowerEntityId;
|
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,
|
public bool TryBuildTower(Vector3Int foundationCell, Func<Vector3Int, bool> isFoundationCell, int buildIndex,
|
||||||
int[] buildTowerCosts, int towerTypeId, Tilemap tilemap, Func<int, bool> tryConsumeCoin,
|
int[] buildTowerCosts, int towerTypeId, Tilemap tilemap, Func<int, bool> tryConsumeCoin,
|
||||||
Action<int> addCoin,
|
Action<int> addCoin,
|
||||||
|
|
@ -53,6 +71,7 @@ namespace GeometryTD.Map
|
||||||
_towerEntityIdByFoundationCell[foundationCell] = newTowerEntityId;
|
_towerEntityIdByFoundationCell[foundationCell] = newTowerEntityId;
|
||||||
_foundationCellByTowerEntityId[newTowerEntityId] = foundationCell;
|
_foundationCellByTowerEntityId[newTowerEntityId] = foundationCell;
|
||||||
_towerStatsByEntityId[newTowerEntityId] = CloneTowerStats(towerStats);
|
_towerStatsByEntityId[newTowerEntityId] = CloneTowerStats(towerStats);
|
||||||
|
_towerLevelByEntityId[newTowerEntityId] = MinTowerLevel;
|
||||||
towerEntityId = newTowerEntityId;
|
towerEntityId = newTowerEntityId;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -68,42 +87,36 @@ namespace GeometryTD.Map
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int requiredUpgradeCost = Mathf.Max(0, upgradeCost);
|
DefenseTowerStatsData towerStats =
|
||||||
if (tryConsumeCoin != null && !tryConsumeCoin(requiredUpgradeCost))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DefenseTowerStatsData oldStats =
|
|
||||||
_towerStatsByEntityId.TryGetValue(towerEntityId, out DefenseTowerStatsData cachedStats)
|
_towerStatsByEntityId.TryGetValue(towerEntityId, out DefenseTowerStatsData cachedStats)
|
||||||
? CloneTowerStats(cachedStats)
|
? CloneTowerStats(cachedStats)
|
||||||
: BuildTowerStats(0);
|
: BuildTowerStats(0);
|
||||||
DefenseTowerStatsData upgradedStats = CloneTowerStats(oldStats);
|
int currentTowerLevel = GetTowerLevel(towerEntityId);
|
||||||
ApplyUpgradeToStats(upgradedStats);
|
int maxTowerLevel = ResolveMaxTowerLevel(towerStats);
|
||||||
|
if (currentTowerLevel >= maxTowerLevel)
|
||||||
HideTowerEntity(towerEntityId);
|
|
||||||
_towerEntityIdByFoundationCell.Remove(foundationCell);
|
|
||||||
_foundationCellByTowerEntityId.Remove(towerEntityId);
|
|
||||||
_towerStatsByEntityId.Remove(towerEntityId);
|
|
||||||
|
|
||||||
if (!TryShowTowerEntity(foundationCell, upgradedStats, towerTypeId, tilemap, out int newTowerEntityId))
|
|
||||||
{
|
{
|
||||||
if (TryShowTowerEntity(foundationCell, oldStats, towerTypeId, tilemap, out int fallbackTowerEntityId))
|
resultTowerEntityId = towerEntityId;
|
||||||
{
|
|
||||||
_towerEntityIdByFoundationCell[foundationCell] = fallbackTowerEntityId;
|
|
||||||
_foundationCellByTowerEntityId[fallbackTowerEntityId] = foundationCell;
|
|
||||||
_towerStatsByEntityId[fallbackTowerEntityId] = CloneTowerStats(oldStats);
|
|
||||||
resultTowerEntityId = fallbackTowerEntityId;
|
|
||||||
}
|
|
||||||
|
|
||||||
addCoin?.Invoke(requiredUpgradeCost);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_towerEntityIdByFoundationCell[foundationCell] = newTowerEntityId;
|
int requiredUpgradeCost = Mathf.Max(0, upgradeCost);
|
||||||
_foundationCellByTowerEntityId[newTowerEntityId] = foundationCell;
|
if (tryConsumeCoin != null && !tryConsumeCoin(requiredUpgradeCost))
|
||||||
_towerStatsByEntityId[newTowerEntityId] = CloneTowerStats(upgradedStats);
|
{
|
||||||
resultTowerEntityId = newTowerEntityId;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,6 +133,7 @@ namespace GeometryTD.Map
|
||||||
_towerEntityIdByFoundationCell.Remove(foundationCell);
|
_towerEntityIdByFoundationCell.Remove(foundationCell);
|
||||||
_foundationCellByTowerEntityId.Remove(towerEntityId);
|
_foundationCellByTowerEntityId.Remove(towerEntityId);
|
||||||
_towerStatsByEntityId.Remove(towerEntityId);
|
_towerStatsByEntityId.Remove(towerEntityId);
|
||||||
|
_towerLevelByEntityId.Remove(towerEntityId);
|
||||||
addCoin?.Invoke(Mathf.Max(0, destroyGain));
|
addCoin?.Invoke(Mathf.Max(0, destroyGain));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -140,6 +154,7 @@ namespace GeometryTD.Map
|
||||||
_towerEntityIdByFoundationCell.Clear();
|
_towerEntityIdByFoundationCell.Clear();
|
||||||
_foundationCellByTowerEntityId.Clear();
|
_foundationCellByTowerEntityId.Clear();
|
||||||
_towerStatsByEntityId.Clear();
|
_towerStatsByEntityId.Clear();
|
||||||
|
_towerLevelByEntityId.Clear();
|
||||||
_towerEntityIdBuffer.Clear();
|
_towerEntityIdBuffer.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,6 +163,7 @@ namespace GeometryTD.Map
|
||||||
_towerEntityIdByFoundationCell.Clear();
|
_towerEntityIdByFoundationCell.Clear();
|
||||||
_foundationCellByTowerEntityId.Clear();
|
_foundationCellByTowerEntityId.Clear();
|
||||||
_towerStatsByEntityId.Clear();
|
_towerStatsByEntityId.Clear();
|
||||||
|
_towerLevelByEntityId.Clear();
|
||||||
_towerEntityIdBuffer.Clear();
|
_towerEntityIdBuffer.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,7 +191,8 @@ namespace GeometryTD.Map
|
||||||
int typeId = towerTypeId > 0 ? towerTypeId : DefaultTowerTypeId;
|
int typeId = towerTypeId > 0 ? towerTypeId : DefaultTowerTypeId;
|
||||||
Vector3 towerPosition = tilemap != null ? tilemap.GetCellCenterWorld(foundationCell) : foundationCell;
|
Vector3 towerPosition = tilemap != null ? tilemap.GetCellCenterWorld(foundationCell) : foundationCell;
|
||||||
towerPosition.z = 0f;
|
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);
|
GameEntry.Entity.ShowDefenseTower(towerData);
|
||||||
|
|
||||||
towerEntityId = entityId;
|
towerEntityId = entityId;
|
||||||
|
|
@ -203,11 +220,11 @@ namespace GeometryTD.Map
|
||||||
case 0:
|
case 0:
|
||||||
return new DefenseTowerStatsData
|
return new DefenseTowerStatsData
|
||||||
{
|
{
|
||||||
AttackDamage = 200,
|
AttackDamage = new[] { 200, 220, 240, 260, 300 },
|
||||||
DamageRandomRate = 0f,
|
DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f },
|
||||||
RotateSpeed = 200f,
|
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
|
||||||
AttackRange = 4.5f,
|
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
|
||||||
AttackSpeed = 1.5f,
|
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
|
||||||
AttackMethodType = AttackMethodType.NormalBullet,
|
AttackMethodType = AttackMethodType.NormalBullet,
|
||||||
AttackPropertyType = AttackPropertyType.Physics,
|
AttackPropertyType = AttackPropertyType.Physics,
|
||||||
Tags = Array.Empty<TagType>()
|
Tags = Array.Empty<TagType>()
|
||||||
|
|
@ -215,11 +232,11 @@ namespace GeometryTD.Map
|
||||||
case 1:
|
case 1:
|
||||||
return new DefenseTowerStatsData
|
return new DefenseTowerStatsData
|
||||||
{
|
{
|
||||||
AttackDamage = 260,
|
AttackDamage = new[] { 200, 220, 240, 260, 300 },
|
||||||
DamageRandomRate = 0f,
|
DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f },
|
||||||
RotateSpeed = 160f,
|
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
|
||||||
AttackRange = 5f,
|
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
|
||||||
AttackSpeed = 1.2f,
|
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
|
||||||
AttackMethodType = AttackMethodType.NormalBullet,
|
AttackMethodType = AttackMethodType.NormalBullet,
|
||||||
AttackPropertyType = AttackPropertyType.Fire,
|
AttackPropertyType = AttackPropertyType.Fire,
|
||||||
Tags = Array.Empty<TagType>()
|
Tags = Array.Empty<TagType>()
|
||||||
|
|
@ -227,11 +244,11 @@ namespace GeometryTD.Map
|
||||||
case 2:
|
case 2:
|
||||||
return new DefenseTowerStatsData
|
return new DefenseTowerStatsData
|
||||||
{
|
{
|
||||||
AttackDamage = 340,
|
AttackDamage = new[] { 200, 220, 240, 260, 300 },
|
||||||
DamageRandomRate = 0f,
|
DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f },
|
||||||
RotateSpeed = 140f,
|
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
|
||||||
AttackRange = 5.5f,
|
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
|
||||||
AttackSpeed = 0.95f,
|
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
|
||||||
AttackMethodType = AttackMethodType.NormalBullet,
|
AttackMethodType = AttackMethodType.NormalBullet,
|
||||||
AttackPropertyType = AttackPropertyType.Ice,
|
AttackPropertyType = AttackPropertyType.Ice,
|
||||||
Tags = Array.Empty<TagType>()
|
Tags = Array.Empty<TagType>()
|
||||||
|
|
@ -239,11 +256,11 @@ namespace GeometryTD.Map
|
||||||
case 3:
|
case 3:
|
||||||
return new DefenseTowerStatsData
|
return new DefenseTowerStatsData
|
||||||
{
|
{
|
||||||
AttackDamage = 440,
|
AttackDamage = new[] { 200, 220, 240, 260, 300 },
|
||||||
DamageRandomRate = 0f,
|
DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f },
|
||||||
RotateSpeed = 120f,
|
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
|
||||||
AttackRange = 6f,
|
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
|
||||||
AttackSpeed = 0.75f,
|
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
|
||||||
AttackMethodType = AttackMethodType.NormalBullet,
|
AttackMethodType = AttackMethodType.NormalBullet,
|
||||||
AttackPropertyType = AttackPropertyType.Poison,
|
AttackPropertyType = AttackPropertyType.Poison,
|
||||||
Tags = Array.Empty<TagType>()
|
Tags = Array.Empty<TagType>()
|
||||||
|
|
@ -251,11 +268,11 @@ namespace GeometryTD.Map
|
||||||
default:
|
default:
|
||||||
return new DefenseTowerStatsData
|
return new DefenseTowerStatsData
|
||||||
{
|
{
|
||||||
AttackDamage = 200,
|
AttackDamage = new[] { 200, 220, 240, 260, 300 },
|
||||||
DamageRandomRate = 0f,
|
DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f },
|
||||||
RotateSpeed = 180f,
|
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
|
||||||
AttackRange = 5f,
|
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
|
||||||
AttackSpeed = 1f,
|
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
|
||||||
AttackMethodType = AttackMethodType.NormalBullet,
|
AttackMethodType = AttackMethodType.NormalBullet,
|
||||||
AttackPropertyType = AttackPropertyType.Physics,
|
AttackPropertyType = AttackPropertyType.Physics,
|
||||||
Tags = Array.Empty<TagType>()
|
Tags = Array.Empty<TagType>()
|
||||||
|
|
@ -273,28 +290,63 @@ namespace GeometryTD.Map
|
||||||
TagType[] copiedTags = source.Tags != null ? (TagType[])source.Tags.Clone() : Array.Empty<TagType>();
|
TagType[] copiedTags = source.Tags != null ? (TagType[])source.Tags.Clone() : Array.Empty<TagType>();
|
||||||
return new DefenseTowerStatsData
|
return new DefenseTowerStatsData
|
||||||
{
|
{
|
||||||
AttackDamage = source.AttackDamage,
|
AttackDamage = source.AttackDamage != null ? (int[])source.AttackDamage.Clone() : Array.Empty<int>(),
|
||||||
DamageRandomRate = source.DamageRandomRate,
|
DamageRandomRate = source.DamageRandomRate != null
|
||||||
RotateSpeed = source.RotateSpeed,
|
? (float[])source.DamageRandomRate.Clone()
|
||||||
AttackRange = source.AttackRange,
|
: Array.Empty<float>(),
|
||||||
AttackSpeed = source.AttackSpeed,
|
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,
|
AttackMethodType = source.AttackMethodType,
|
||||||
AttackPropertyType = source.AttackPropertyType,
|
AttackPropertyType = source.AttackPropertyType,
|
||||||
Tags = copiedTags
|
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);
|
return Mathf.Clamp(towerLevel, MinTowerLevel, MaxTowerLevel);
|
||||||
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);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,10 +98,12 @@ namespace GeometryTD.UI
|
||||||
switch (userData.ClickObjectType)
|
switch (userData.ClickObjectType)
|
||||||
{
|
{
|
||||||
case CombatSelectClickObjectType.Foundation:
|
case CombatSelectClickObjectType.Foundation:
|
||||||
|
_useCase.SetUpgradeVisible(true);
|
||||||
_useCase.ShowForFoundation(userData.ContentPosition);
|
_useCase.ShowForFoundation(userData.ContentPosition);
|
||||||
break;
|
break;
|
||||||
case CombatSelectClickObjectType.Tower:
|
case CombatSelectClickObjectType.Tower:
|
||||||
_useCase.SetUpgradePrice(userData.UpgradeCost);
|
_useCase.SetUpgradePrice(userData.UpgradeCost);
|
||||||
|
_useCase.SetUpgradeVisible(!userData.IsTowerAtMaxLevel);
|
||||||
_useCase.SetDestroyGain(userData.DestroyGain);
|
_useCase.SetDestroyGain(userData.DestroyGain);
|
||||||
_useCase.ShowForTower(userData.ContentPosition);
|
_useCase.ShowForTower(userData.ContentPosition);
|
||||||
break;
|
break;
|
||||||
|
|
@ -133,6 +135,7 @@ namespace GeometryTD.UI
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_useCase.SetUpgradeVisible(true);
|
||||||
_useCase.ShowForFoundation(contentPosition);
|
_useCase.ShowForFoundation(contentPosition);
|
||||||
return OpenUI(_useCase.TryRefresh());
|
return OpenUI(_useCase.TryRefresh());
|
||||||
}
|
}
|
||||||
|
|
@ -146,6 +149,7 @@ namespace GeometryTD.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
_useCase.SetUpgradePrice(upgradeCost);
|
_useCase.SetUpgradePrice(upgradeCost);
|
||||||
|
_useCase.SetUpgradeVisible(true);
|
||||||
_useCase.SetDestroyGain(destroyGain);
|
_useCase.SetDestroyGain(destroyGain);
|
||||||
_useCase.ShowForTower(contentPosition);
|
_useCase.ShowForTower(contentPosition);
|
||||||
return OpenUI(_useCase.TryRefresh());
|
return OpenUI(_useCase.TryRefresh());
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ namespace GeometryTD.UI
|
||||||
public Vector3 WorldPosition;
|
public Vector3 WorldPosition;
|
||||||
public Vector3Int CellPosition;
|
public Vector3Int CellPosition;
|
||||||
public int TowerEntityId;
|
public int TowerEntityId;
|
||||||
|
public bool IsTowerAtMaxLevel;
|
||||||
public int UpgradeCost;
|
public int UpgradeCost;
|
||||||
public int DestroyGain;
|
public int DestroyGain;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ namespace GeometryTD.UI
|
||||||
{
|
{
|
||||||
public class CombatFinishFormUseCase : IUIUseCase
|
public class CombatFinishFormUseCase : IUIUseCase
|
||||||
{
|
{
|
||||||
private readonly RepoFormUseCase _repoFormUseCase = new RepoFormUseCase();
|
|
||||||
private CombatScheduler _combatScheduler;
|
private CombatScheduler _combatScheduler;
|
||||||
private int _defeatedEnemyCount;
|
private int _defeatedEnemyCount;
|
||||||
private int _gainedGold;
|
private int _gainedGold;
|
||||||
|
|
@ -57,8 +56,7 @@ namespace GeometryTD.UI
|
||||||
BackpackInventoryData rewardInventory = _rewardInventory;
|
BackpackInventoryData rewardInventory = _rewardInventory;
|
||||||
if (rewardInventory == null)
|
if (rewardInventory == null)
|
||||||
{
|
{
|
||||||
RepoFormRawData repoRawData = _repoFormUseCase.CreateInitialModel();
|
rewardInventory = RepoFormUseCase.SampleInventory();
|
||||||
rewardInventory = repoRawData != null ? repoRawData.Inventory : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CombatFinishFormRawData
|
return new CombatFinishFormRawData
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,11 @@ namespace GeometryTD.UI
|
||||||
_upgradeOption.Price = Mathf.Max(0, cost);
|
_upgradeOption.Price = Mathf.Max(0, cost);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetUpgradeVisible(bool visible)
|
||||||
|
{
|
||||||
|
_upgradeOption.IsVisible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
public void SetDestroyGain(int gain)
|
public void SetDestroyGain(int gain)
|
||||||
{
|
{
|
||||||
_destroyOption.Price = Mathf.Max(0, gain);
|
_destroyOption.Price = Mathf.Max(0, gain);
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,16 @@ namespace GeometryTD.UI
|
||||||
{
|
{
|
||||||
public RepoFormRawData CreateInitialModel()
|
public RepoFormRawData CreateInitialModel()
|
||||||
{
|
{
|
||||||
BackpackInventoryData sample = BuildSampleInventory();
|
BackpackInventoryData sample = GameEntry.PlayerInventory != null
|
||||||
|
? GameEntry.PlayerInventory.GetInventorySnapshot()
|
||||||
|
: SampleInventory();
|
||||||
return new RepoFormRawData
|
return new RepoFormRawData
|
||||||
{
|
{
|
||||||
Inventory = sample
|
Inventory = sample
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BackpackInventoryData BuildSampleInventory()
|
public static BackpackInventoryData SampleInventory()
|
||||||
{
|
{
|
||||||
BackpackInventoryData inventory = new BackpackInventoryData
|
BackpackInventoryData inventory = new BackpackInventoryData
|
||||||
{
|
{
|
||||||
|
|
@ -75,11 +77,11 @@ namespace GeometryTD.UI
|
||||||
BaseComponentInstanceId = baseComp.InstanceId,
|
BaseComponentInstanceId = baseComp.InstanceId,
|
||||||
Stats = new DefenseTowerStatsData
|
Stats = new DefenseTowerStatsData
|
||||||
{
|
{
|
||||||
AttackDamage = 30,
|
AttackDamage = new[] { 200, 220, 240, 260, 300 },
|
||||||
DamageRandomRate = 0.05f,
|
DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f },
|
||||||
RotateSpeed = 12f,
|
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
|
||||||
AttackRange = 2f,
|
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
|
||||||
AttackSpeed = 1.5f,
|
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
|
||||||
AttackMethodType = AttackMethodType.NormalBullet,
|
AttackMethodType = AttackMethodType.NormalBullet,
|
||||||
AttackPropertyType = AttackPropertyType.Fire,
|
AttackPropertyType = AttackPropertyType.Fire,
|
||||||
Tags = new[] { TagType.Fire, TagType.BurnSpread }
|
Tags = new[] { TagType.Fire, TagType.BurnSpread }
|
||||||
|
|
@ -98,11 +100,11 @@ namespace GeometryTD.UI
|
||||||
BaseComponentInstanceId = 0,
|
BaseComponentInstanceId = 0,
|
||||||
Stats = new DefenseTowerStatsData
|
Stats = new DefenseTowerStatsData
|
||||||
{
|
{
|
||||||
AttackDamage = 50,
|
AttackDamage = new[] { 200, 220, 240, 260, 300 },
|
||||||
DamageRandomRate = 0.03f,
|
DamageRandomRate = new[] { 0f, 0f, 0f, 0f, 0f },
|
||||||
RotateSpeed = 20f,
|
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
|
||||||
AttackRange = 4f,
|
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
|
||||||
AttackSpeed = 1f,
|
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
|
||||||
AttackMethodType = AttackMethodType.NormalBullet,
|
AttackMethodType = AttackMethodType.NormalBullet,
|
||||||
AttackPropertyType = AttackPropertyType.Physics,
|
AttackPropertyType = AttackPropertyType.Physics,
|
||||||
Tags = new[] { TagType.Pierce }
|
Tags = new[] { TagType.Pierce }
|
||||||
|
|
|
||||||
|
|
@ -153,11 +153,9 @@ Transform:
|
||||||
m_Children:
|
m_Children:
|
||||||
- {fileID: 513208573}
|
- {fileID: 513208573}
|
||||||
- {fileID: 1604812193}
|
- {fileID: 1604812193}
|
||||||
- {fileID: 159392563}
|
|
||||||
- {fileID: 1549230541}
|
|
||||||
- {fileID: 1758164286}
|
|
||||||
- {fileID: 2007255511}
|
- {fileID: 2007255511}
|
||||||
- {fileID: 428539048}
|
- {fileID: 428539048}
|
||||||
|
- {fileID: 1322505022}
|
||||||
m_Father: {fileID: 1852670053}
|
m_Father: {fileID: 1852670053}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!1 &120093239
|
--- !u!1 &120093239
|
||||||
|
|
@ -275,12 +273,12 @@ Transform:
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_GameObject: {fileID: 159392562}
|
m_GameObject: {fileID: 159392562}
|
||||||
serializedVersion: 2
|
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_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 119167776}
|
m_Father: {fileID: 1322505022}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!114 &159392564
|
--- !u!114 &159392564
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
|
|
@ -883,6 +881,85 @@ LightingSettings:
|
||||||
m_PVRTiledBaking: 0
|
m_PVRTiledBaking: 0
|
||||||
m_NumRaysToShootPerTexel: -1
|
m_NumRaysToShootPerTexel: -1
|
||||||
m_RespectSceneVisibilityWhenBakingGI: 0
|
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
|
--- !u!1 &1454214586
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|
@ -991,12 +1068,12 @@ Transform:
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_GameObject: {fileID: 1549230540}
|
m_GameObject: {fileID: 1549230540}
|
||||||
serializedVersion: 2
|
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_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 119167776}
|
m_Father: {fileID: 1322505022}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!114 &1549230542
|
--- !u!114 &1549230542
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
|
|
@ -1184,12 +1261,12 @@ Transform:
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_GameObject: {fileID: 1758164285}
|
m_GameObject: {fileID: 1758164285}
|
||||||
serializedVersion: 2
|
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_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 119167776}
|
m_Father: {fileID: 1322505022}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!114 &1758164287
|
--- !u!114 &1758164287
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,11 @@
|
||||||
| 状态 | ID | 任务 | 交付物路径 | 验收标准 |
|
| 状态 | ID | 任务 | 交付物路径 | 验收标准 |
|
||||||
|-----|-------|-------------------------------------|----------------------------------------------------------------------------------------|---------------------------|
|
|-----|-------|-------------------------------------|----------------------------------------------------------------------------------------|---------------------------|
|
||||||
| [x] | P0-01 | 冻结 MVP 范围(只保留:战斗节点/事件节点/商店节点/节点后组装) | `docs/MVP-Scope.md` | 明确“做/不做”清单,团队评审通过 |
|
| [x] | P0-01 | 冻结 MVP 范围(只保留:战斗节点/事件节点/商店节点/节点后组装) | `docs/MVP-Scope.md` | 明确“做/不做”清单,团队评审通过 |
|
||||||
| [ ] | P0-02 | 补齐数据表:组件、配件、敌人、波次、节点、事件、商店商品 | `Assets/GameMain/DataTables/*.txt` | 游戏启动可无报错加载全部新增表 |
|
| [x] | P0-02 | 补齐数据表:组件、配件、敌人、波次、节点、事件、商店商品 | `Assets/GameMain/DataTables/*.txt` | 游戏启动可无报错加载全部新增表 |
|
||||||
| [ ] | P0-03 | 新增/完善 DataRow 解析类 | `Assets/GameMain/Scripts/DataTable/*.cs` | 每个新增表都有对应 DR 类并成功解析 |
|
| [x] | P0-03 | 新增/完善 DataRow 解析类 | `Assets/GameMain/Scripts/DataTable/*.cs` | 每个新增表都有对应 DR 类并成功解析 |
|
||||||
| [ ] | P0-04 | 建立单局 Run 状态模型(金币、生命、库存、当前节点) | `Assets/GameMain/Scripts/Procedure/` | 进入/退出节点时状态可持续传递 |
|
| [x] | P0-04 | 建立单局 Run 状态模型(金币、生命、库存、当前节点) | `Assets/GameMain/Scripts/Procedure/` | 进入/退出节点时状态可持续传递 |
|
||||||
| [ ] | P0-05 | 实现 10 节点地图生成(最后节点固定 Boss) | `Assets/GameMain/Scripts/Scene/` | 每局都生成 10 节点且第 10 节点为 Boss |
|
| [ ] | 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/`<br>`Assets/GameMain/Scripts/Scene/` | 可完整打一场并得到胜利/失败结果 |
|
| [ ] | P0-07 | 战斗节点基础玩法:放置塔、出怪、基地扣血、胜负判定 | `Assets/GameMain/Scripts/Entity/`<br>`Assets/GameMain/Scripts/Scene/` | 可完整打一场并得到胜利/失败结果 |
|
||||||
| [ ] | P0-08 | 胜利波次与结算规则(100/80/50/<50) | `Assets/GameMain/Scripts/Procedure/` | 结算奖励与惩罚严格匹配设计文档 |
|
| [ ] | P0-08 | 胜利波次与结算规则(100/80/50/<50) | `Assets/GameMain/Scripts/Procedure/` | 结算奖励与惩罚严格匹配设计文档 |
|
||||||
| [ ] | P0-09 | 敌人掉落与关卡奖励(组件/配件/金币) | `Assets/GameMain/Scripts/Entity/` | 战斗结束能发放掉落并写入库存 |
|
| [ ] | P0-09 | 敌人掉落与关卡奖励(组件/配件/金币) | `Assets/GameMain/Scripts/Entity/` | 战斗结束能发放掉落并写入库存 |
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue