init
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
using Components;
|
||||
using GeometryTD.Entity.EntityData;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.Entity
|
||||
{
|
||||
public class BulletEntity : EntityBase
|
||||
{
|
||||
private ShooterBullet _shooterBullet;
|
||||
|
||||
protected override void OnInit(object userData)
|
||||
{
|
||||
base.OnInit(userData);
|
||||
|
||||
_shooterBullet = GetComponent<ShooterBullet>();
|
||||
if (_shooterBullet == null)
|
||||
{
|
||||
Log.Error("ShooterBullet component is missing on bullet entity '{0}'.", name);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
|
||||
if (_shooterBullet == null)
|
||||
{
|
||||
GameEntry.Entity.HideEntity(Entity);
|
||||
return;
|
||||
}
|
||||
|
||||
if (userData is not BulletData bulletData)
|
||||
{
|
||||
Log.Warning("BulletData is invalid for bullet entity '{0}'.", Id);
|
||||
GameEntry.Entity.HideEntity(Entity);
|
||||
return;
|
||||
}
|
||||
|
||||
ConstrainToZRotation();
|
||||
_shooterBullet.OnShow(bulletData);
|
||||
if (_shooterBullet.TryConsumeDespawnRequest())
|
||||
{
|
||||
GameEntry.Entity.HideEntity(Entity);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
if (_shooterBullet == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_shooterBullet.Tick(elapseSeconds);
|
||||
if (_shooterBullet.TryConsumeDespawnRequest())
|
||||
{
|
||||
GameEntry.Entity.HideEntity(Entity);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnHide(bool isShutdown, object userData)
|
||||
{
|
||||
_shooterBullet?.OnReset();
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
|
||||
private void ConstrainToZRotation()
|
||||
{
|
||||
Vector3 localEulerAngles = CachedTransform.localEulerAngles;
|
||||
CachedTransform.localRotation = Quaternion.Euler(0f, 0f, localEulerAngles.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.UI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.Tilemaps;
|
||||
|
||||
namespace GeometryTD.Entity
|
||||
{
|
||||
public sealed class CombatSelectInputService
|
||||
{
|
||||
public bool TryBuildUserData(
|
||||
Tilemap tilemap,
|
||||
Transform mapTransform,
|
||||
IReadOnlyDictionary<Vector3Int, int> towerEntityIdByFoundationCell,
|
||||
Func<Vector3Int, bool> isFoundationCell,
|
||||
Func<int, bool> isTowerAtMaxLevel,
|
||||
int upgradeCost,
|
||||
int destroyGain,
|
||||
out CombatSelectFormUserData userData)
|
||||
{
|
||||
userData = null;
|
||||
if (tilemap == null || !TryGetPointerWorldPosition(tilemap, mapTransform, out Vector3 worldPosition,
|
||||
out Vector2 screenPosition))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector3Int clickedCell = tilemap.WorldToCell(worldPosition);
|
||||
CombatSelectClickObjectType clickObjectType = CombatSelectClickObjectType.None;
|
||||
int towerEntityId = 0;
|
||||
Vector2 resolvedScreenPosition = screenPosition;
|
||||
|
||||
if (towerEntityIdByFoundationCell != null &&
|
||||
towerEntityIdByFoundationCell.TryGetValue(clickedCell, out int occupiedTowerEntityId))
|
||||
{
|
||||
clickObjectType = CombatSelectClickObjectType.Tower;
|
||||
towerEntityId = occupiedTowerEntityId;
|
||||
resolvedScreenPosition = BuildScreenPositionFromCell(tilemap, clickedCell, screenPosition);
|
||||
}
|
||||
else if (isFoundationCell != null && isFoundationCell(clickedCell))
|
||||
{
|
||||
clickObjectType = CombatSelectClickObjectType.Foundation;
|
||||
resolvedScreenPosition = BuildScreenPositionFromCell(tilemap, clickedCell, screenPosition);
|
||||
}
|
||||
|
||||
userData = new CombatSelectFormUserData
|
||||
{
|
||||
ClickObjectType = clickObjectType,
|
||||
ScreenPosition = resolvedScreenPosition,
|
||||
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,
|
||||
out Vector2 screenPosition)
|
||||
{
|
||||
worldPosition = Vector3.zero;
|
||||
screenPosition = Vector2.zero;
|
||||
|
||||
Camera mainCamera = GameEntry.Scene != null ? GameEntry.Scene.MainCamera : Camera.main;
|
||||
if (mainCamera == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Mouse mouse = Mouse.current;
|
||||
if (mouse == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector2 pointerPosition = mouse.position.ReadValue();
|
||||
Ray ray = mainCamera.ScreenPointToRay(pointerPosition);
|
||||
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;
|
||||
Plane mapPlane = new Plane(planeNormal, new Vector3(0f, 0f, mapPlaneZ));
|
||||
if (!mapPlane.Raycast(ray, out float enterDistance))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
worldPosition = ray.GetPoint(enterDistance);
|
||||
screenPosition = BuildScreenPosition(pointerPosition);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Vector2 BuildScreenPosition(Vector3 pointerScreenPosition)
|
||||
{
|
||||
return new Vector2(pointerScreenPosition.x, pointerScreenPosition.y);
|
||||
}
|
||||
|
||||
private static Vector2 BuildScreenPositionFromCell(Tilemap tilemap, Vector3Int cellPosition,
|
||||
Vector2 fallbackScreenPosition)
|
||||
{
|
||||
if (tilemap == null)
|
||||
{
|
||||
return fallbackScreenPosition;
|
||||
}
|
||||
|
||||
Camera mainCamera = GameEntry.Scene != null ? GameEntry.Scene.MainCamera : Camera.main;
|
||||
if (mainCamera == null)
|
||||
{
|
||||
return fallbackScreenPosition;
|
||||
}
|
||||
|
||||
Vector3 cellCenterWorld = tilemap.GetCellCenterWorld(cellPosition);
|
||||
Vector3 cellScreenPosition = mainCamera.WorldToScreenPoint(cellCenterWorld);
|
||||
if (cellScreenPosition.z < 0f)
|
||||
{
|
||||
return fallbackScreenPosition;
|
||||
}
|
||||
|
||||
return BuildScreenPosition(cellScreenPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.UI;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Entity
|
||||
{
|
||||
public sealed class CombatSelectUseCaseConfigurator
|
||||
{
|
||||
private const int BuildOptionCount = 4;
|
||||
|
||||
private readonly BuildTowerVisualInfo[] _buildTowerVisualInfos = new BuildTowerVisualInfo[BuildOptionCount];
|
||||
|
||||
public void Configure(
|
||||
CombatSelectFormUseCase useCase,
|
||||
Func<int> coinProvider,
|
||||
Func<int, Func<bool>> buildActionFactory,
|
||||
Func<bool> upgradeAction,
|
||||
int upgradeCost,
|
||||
Func<bool> destroyAction,
|
||||
int destroyGain,
|
||||
int[] buildTowerCosts,
|
||||
int currentBuildTowerCount,
|
||||
BackpackInventoryData inventorySnapshot,
|
||||
IReadOnlyList<TowerItemData> participantTowers)
|
||||
{
|
||||
if (useCase == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
useCase.SetCoinProvider(coinProvider);
|
||||
|
||||
Dictionary<long, MuzzleCompItemData> muzzleMap = BuildComponentMap(inventorySnapshot?.MuzzleComponents);
|
||||
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(inventorySnapshot?.BearingComponents);
|
||||
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(inventorySnapshot?.BaseComponents);
|
||||
|
||||
int availableBuildCount = Mathf.Clamp(currentBuildTowerCount, 0, BuildOptionCount);
|
||||
for (int i = 0; i < BuildOptionCount; i++)
|
||||
{
|
||||
bool isBuildAvailable = i < availableBuildCount;
|
||||
BuildTowerVisualInfo buildVisual = ResolveBuildTowerVisual(participantTowers, i, muzzleMap, bearingMap, baseMap);
|
||||
_buildTowerVisualInfos[i] = buildVisual;
|
||||
useCase.SetBuildAction(
|
||||
i,
|
||||
isBuildAvailable && buildActionFactory != null ? buildActionFactory.Invoke(i) : null,
|
||||
GetBuildTowerCost(buildTowerCosts, i),
|
||||
null,
|
||||
isBuildAvailable,
|
||||
buildVisual.BaseColor,
|
||||
buildVisual.BearingColor,
|
||||
buildVisual.MuzzleColor);
|
||||
useCase.SetBuildVisible(i, isBuildAvailable);
|
||||
}
|
||||
|
||||
useCase.SetUpgradeAction(upgradeAction, Mathf.Max(0, upgradeCost));
|
||||
useCase.SetDestroyAction(destroyAction, Mathf.Max(0, destroyGain));
|
||||
}
|
||||
|
||||
public BuildTowerVisualInfo GetBuildVisualInfo(int buildIndex)
|
||||
{
|
||||
if (buildIndex < 0 || buildIndex >= _buildTowerVisualInfos.Length)
|
||||
{
|
||||
return BuildTowerVisualInfo.Default;
|
||||
}
|
||||
|
||||
return _buildTowerVisualInfos[buildIndex];
|
||||
}
|
||||
|
||||
private static int GetBuildTowerCost(int[] buildTowerCosts, int buildIndex)
|
||||
{
|
||||
if (buildTowerCosts == null || buildIndex < 0 || buildIndex >= buildTowerCosts.Length)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Mathf.Max(0, buildTowerCosts[buildIndex]);
|
||||
}
|
||||
|
||||
private static BuildTowerVisualInfo ResolveBuildTowerVisual(
|
||||
IReadOnlyList<TowerItemData> participantTowers,
|
||||
int buildIndex,
|
||||
IReadOnlyDictionary<long, MuzzleCompItemData> muzzleMap,
|
||||
IReadOnlyDictionary<long, BearingCompItemData> bearingMap,
|
||||
IReadOnlyDictionary<long, BaseCompItemData> baseMap)
|
||||
{
|
||||
if (participantTowers == null || buildIndex < 0 || buildIndex >= participantTowers.Count)
|
||||
{
|
||||
return BuildTowerVisualInfo.Default;
|
||||
}
|
||||
|
||||
TowerItemData tower = participantTowers[buildIndex];
|
||||
if (tower == null)
|
||||
{
|
||||
return BuildTowerVisualInfo.Default;
|
||||
}
|
||||
|
||||
Color muzzleColor = ResolveComponentColor(muzzleMap, tower.MuzzleComponentInstanceId);
|
||||
Color bearingColor = ResolveComponentColor(bearingMap, tower.BearingComponentInstanceId);
|
||||
Color baseColor = ResolveComponentColor(baseMap, tower.BaseComponentInstanceId);
|
||||
return new BuildTowerVisualInfo(muzzleColor, bearingColor, baseColor);
|
||||
}
|
||||
|
||||
private static Color ResolveComponentColor<TComp>(IReadOnlyDictionary<long, TComp> componentMap, long componentId)
|
||||
where TComp : TowerCompItemData
|
||||
{
|
||||
if (componentMap == null || componentId <= 0)
|
||||
{
|
||||
return Color.white;
|
||||
}
|
||||
|
||||
return componentMap.TryGetValue(componentId, out TComp component) && component != null
|
||||
? IconColorGenerator.GenerateForComponent(component)
|
||||
: Color.white;
|
||||
}
|
||||
|
||||
private static Dictionary<long, TComp> BuildComponentMap<TComp>(IReadOnlyList<TComp> items)
|
||||
where TComp : TowerCompItemData
|
||||
{
|
||||
Dictionary<long, TComp> map = new Dictionary<long, TComp>();
|
||||
if (items == null)
|
||||
{
|
||||
return map;
|
||||
}
|
||||
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
TComp item = items[i];
|
||||
if (item == null || item.InstanceId <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
map[item.InstanceId] = item;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct BuildTowerVisualInfo
|
||||
{
|
||||
public static BuildTowerVisualInfo Default => new BuildTowerVisualInfo(Color.white, Color.white, Color.white);
|
||||
|
||||
public BuildTowerVisualInfo(Color muzzleColor, Color bearingColor, Color baseColor)
|
||||
{
|
||||
MuzzleColor = muzzleColor;
|
||||
BearingColor = bearingColor;
|
||||
BaseColor = baseColor;
|
||||
}
|
||||
|
||||
public Color MuzzleColor { get; }
|
||||
public Color BearingColor { get; }
|
||||
public Color BaseColor { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
using Components;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Entity.EntityData;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.Entity
|
||||
{
|
||||
public class EnemyEntity : EntityBase, IDamageReceiver
|
||||
{
|
||||
private const float WaypointReachDistance = 0.05f;
|
||||
private static readonly List<EnemyEntity> _activeEnemies = new();
|
||||
private static readonly HashSet<int> _killedEnemyEntityIds = new();
|
||||
|
||||
private Transform _target;
|
||||
private float _speed;
|
||||
private int _maxHealth;
|
||||
private int _currentHealth;
|
||||
private MovementComponent _movementComponent;
|
||||
private readonly List<Vector3> _pathPoints = new();
|
||||
private int _pathPointIndex;
|
||||
private bool _isDespawnRequested;
|
||||
private readonly EnemyTagStatusRuntime _tagStatusRuntime = new();
|
||||
|
||||
public static IReadOnlyList<EnemyEntity> ActiveEnemies => _activeEnemies;
|
||||
public int CurrentHealth => _currentHealth;
|
||||
public int MaxHealth => _maxHealth;
|
||||
public bool HasSlowStatus => _tagStatusRuntime.GetMoveSpeedMultiplier() < 0.999f;
|
||||
public float MoveSpeedMultiplier => _tagStatusRuntime.GetMoveSpeedMultiplier();
|
||||
|
||||
public static bool TryConsumeKilledFlag(int entityId)
|
||||
{
|
||||
return _killedEnemyEntityIds.Remove(entityId);
|
||||
}
|
||||
|
||||
protected override void OnInit(object userData)
|
||||
{
|
||||
base.OnInit(userData);
|
||||
|
||||
_movementComponent = GetComponent<MovementComponent>();
|
||||
}
|
||||
|
||||
protected override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
|
||||
_target = null;
|
||||
_pathPoints.Clear();
|
||||
_pathPointIndex = 0;
|
||||
_isDespawnRequested = false;
|
||||
_maxHealth = 1;
|
||||
_currentHealth = 1;
|
||||
_tagStatusRuntime.Reset();
|
||||
if (userData is EnemyData enemyData)
|
||||
{
|
||||
_speed = enemyData.Speed;
|
||||
_target = enemyData.Player;
|
||||
_maxHealth = Mathf.Max(1, enemyData.MaxHealth);
|
||||
_currentHealth = _maxHealth;
|
||||
if (enemyData.HasPath)
|
||||
{
|
||||
IReadOnlyList<Vector3> pathPoints = enemyData.PathPoints;
|
||||
for (int i = 0; i < pathPoints.Count; i++)
|
||||
{
|
||||
_pathPoints.Add(pathPoints[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_movementComponent.OnInit(_speed, CachedTransform);
|
||||
_movementComponent.SetMove(true);
|
||||
_killedEnemyEntityIds.Remove(Id);
|
||||
if (!_activeEnemies.Contains(this))
|
||||
{
|
||||
_activeEnemies.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
_tagStatusRuntime.Tick(elapseSeconds, ApplyDamageFromStatus);
|
||||
|
||||
if (_pathPoints.Count > 0)
|
||||
{
|
||||
UpdatePathMovement(elapseSeconds, realElapseSeconds);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnHide(bool isShutdown, object userData)
|
||||
{
|
||||
_movementComponent.SetMove(false);
|
||||
_pathPoints.Clear();
|
||||
_pathPointIndex = 0;
|
||||
_isDespawnRequested = false;
|
||||
_maxHealth = 0;
|
||||
_currentHealth = 0;
|
||||
_tagStatusRuntime.Reset();
|
||||
_activeEnemies.Remove(this);
|
||||
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_activeEnemies.Remove(this);
|
||||
}
|
||||
|
||||
public void TakeDamage(AttackPayload attackPayload)
|
||||
{
|
||||
if (_isDespawnRequested || _currentHealth <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HitContext hitContext = TagEffectResolver.ResolveBeforeHit(new HitContext
|
||||
{
|
||||
AttackPayload = attackPayload,
|
||||
TargetEntityId = Id,
|
||||
TargetPosition = CachedTransform.position,
|
||||
TargetCurrentHealthBeforeHit = _currentHealth,
|
||||
TargetCurrentHealthAfterHit = _currentHealth,
|
||||
TargetMaxHealth = _maxHealth,
|
||||
TargetMoveSpeedMultiplierBeforeHit = _tagStatusRuntime.GetMoveSpeedMultiplier(),
|
||||
TargetStatusTagsBeforeHit = _tagStatusRuntime.GetActiveTagSnapshot(),
|
||||
TargetStatusRuntime = _tagStatusRuntime
|
||||
});
|
||||
ApplyDirectDamage(hitContext.FinalDamage);
|
||||
hitContext.TargetCurrentHealthAfterHit = _currentHealth;
|
||||
hitContext.IsKilled = _currentHealth <= 0;
|
||||
TagEffectResolver.ApplyOnHit(hitContext);
|
||||
TagEffectResolver.ApplyAfterHit(hitContext);
|
||||
if (hitContext.IsKilled)
|
||||
{
|
||||
TagEffectResolver.ApplyOnKill(hitContext);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePathMovement(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
if (_isDespawnRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pathPointIndex >= _pathPoints.Count)
|
||||
{
|
||||
DespawnOnReachHouse();
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 direction = GetDirectionToPathPoint(_pathPoints[_pathPointIndex], out float distanceSquared);
|
||||
if (distanceSquared <= WaypointReachDistance * WaypointReachDistance)
|
||||
{
|
||||
_pathPointIndex++;
|
||||
if (_pathPointIndex >= _pathPoints.Count)
|
||||
{
|
||||
DespawnOnReachHouse();
|
||||
return;
|
||||
}
|
||||
|
||||
direction = GetDirectionToPathPoint(_pathPoints[_pathPointIndex], out _);
|
||||
}
|
||||
|
||||
if (direction.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
_movementComponent.SetMove(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_movementComponent.SetSpeedMultiplier(_tagStatusRuntime.GetMoveSpeedMultiplier());
|
||||
_movementComponent.SetMove(true);
|
||||
_movementComponent.SetDirection(direction);
|
||||
_movementComponent.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
}
|
||||
|
||||
private Vector3 GetDirectionToPathPoint(Vector3 worldPoint, out float distanceSquared)
|
||||
{
|
||||
Vector3 delta = worldPoint - CachedTransform.position;
|
||||
distanceSquared = delta.sqrMagnitude;
|
||||
if (distanceSquared <= Mathf.Epsilon)
|
||||
{
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
return delta.normalized;
|
||||
}
|
||||
|
||||
private void DespawnOnReachHouse()
|
||||
{
|
||||
RequestDespawn();
|
||||
}
|
||||
|
||||
private void RequestDespawn()
|
||||
{
|
||||
if (_isDespawnRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDespawnRequested = true;
|
||||
_movementComponent.SetMove(false);
|
||||
GameEntry.Entity.HideEntity(Entity);
|
||||
}
|
||||
|
||||
private void ApplyDamageFromStatus(int damage)
|
||||
{
|
||||
ApplyDirectDamage(damage);
|
||||
}
|
||||
|
||||
private void ApplyDirectDamage(int damage)
|
||||
{
|
||||
if (_isDespawnRequested || damage <= 0 || _currentHealth <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int previousHealth = _currentHealth;
|
||||
_currentHealth = Mathf.Max(0, _currentHealth - damage);
|
||||
if (_maxHealth > 0)
|
||||
{
|
||||
GameEntry.HPBar?.ShowHPBar(this, (float)previousHealth / _maxHealth,
|
||||
(float)_currentHealth / _maxHealth);
|
||||
}
|
||||
|
||||
if (_currentHealth > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_killedEnemyEntityIds.Add(Id);
|
||||
RequestDespawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Entity
|
||||
{
|
||||
public sealed class EnemyTagStatusRuntime
|
||||
{
|
||||
private readonly Dictionary<TagType, EnemyStatusTagStateBase> _statesByTag = new();
|
||||
private readonly List<TagType> _activeTags = new();
|
||||
|
||||
public bool HasStatus(TagType tagType)
|
||||
{
|
||||
return _statesByTag.ContainsKey(tagType);
|
||||
}
|
||||
|
||||
public TagType[] GetActiveTagSnapshot()
|
||||
{
|
||||
if (_activeTags.Count <= 0)
|
||||
{
|
||||
return Array.Empty<TagType>();
|
||||
}
|
||||
|
||||
return _activeTags.ToArray();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_statesByTag.Clear();
|
||||
_activeTags.Clear();
|
||||
}
|
||||
|
||||
public void Activate(TagType tagType)
|
||||
{
|
||||
if (_activeTags.Contains(tagType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_activeTags.Add(tagType);
|
||||
}
|
||||
|
||||
public TState GetState<TState>(TagType tagType) where TState : EnemyStatusTagStateBase
|
||||
{
|
||||
if (!_statesByTag.TryGetValue(tagType, out EnemyStatusTagStateBase state))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Debug.Assert(state is TState);
|
||||
return state as TState;
|
||||
}
|
||||
|
||||
public TState GetOrCreateState<TState>(TagType tagType) where TState : EnemyStatusTagStateBase, new()
|
||||
{
|
||||
if (_statesByTag.TryGetValue(tagType, out EnemyStatusTagStateBase existingState))
|
||||
{
|
||||
Debug.Assert(existingState is TState);
|
||||
return existingState as TState;
|
||||
}
|
||||
|
||||
TState state = new TState();
|
||||
_statesByTag[tagType] = state;
|
||||
return state;
|
||||
}
|
||||
|
||||
public void Tick(float deltaTime, Action<int> applyDamage)
|
||||
{
|
||||
float resolvedDeltaTime = Mathf.Max(0f, deltaTime);
|
||||
if (resolvedDeltaTime <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = _activeTags.Count - 1; i >= 0; i--)
|
||||
{
|
||||
TagType tagType = _activeTags[i];
|
||||
Debug.Assert(EnemyStatusTagRegistry.TryGetEffect(tagType, out IEnemyStatusTagEffect effect));
|
||||
|
||||
if (effect.Tick(this, resolvedDeltaTime, applyDamage))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_statesByTag.Remove(tagType);
|
||||
_activeTags.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
public float GetMoveSpeedMultiplier()
|
||||
{
|
||||
float multiplier = 1f;
|
||||
for (int i = 0; i < _activeTags.Count; i++)
|
||||
{
|
||||
TagType tagType = _activeTags[i];
|
||||
Debug.Assert(EnemyStatusTagRegistry.TryGetEffect(tagType, out IEnemyStatusTagEffect effect));
|
||||
multiplier = Mathf.Min(multiplier, effect.GetMoveSpeedMultiplier(this));
|
||||
}
|
||||
|
||||
return multiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using GeometryTD.Entity.EntityData;
|
||||
using GameFramework;
|
||||
using GeometryTD;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.Entity
|
||||
{
|
||||
public abstract class EntityBase : EntityLogic
|
||||
{
|
||||
[SerializeField] private EntityDataBase _entityData = null;
|
||||
|
||||
public int Id => Entity.Id;
|
||||
|
||||
public Animation CachedAnimation
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnInit(object userData)
|
||||
#else
|
||||
protected internal override void OnInit(object userData)
|
||||
#endif
|
||||
{
|
||||
base.OnInit(userData);
|
||||
CachedAnimation = GetComponent<Animation>();
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnRecycle()
|
||||
#else
|
||||
protected internal override void OnRecycle()
|
||||
#endif
|
||||
{
|
||||
base.OnRecycle();
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnShow(object userData)
|
||||
#else
|
||||
protected internal override void OnShow(object userData)
|
||||
#endif
|
||||
{
|
||||
base.OnShow(userData);
|
||||
|
||||
_entityData = userData as EntityDataBase;
|
||||
if (_entityData == null)
|
||||
{
|
||||
Log.Error("Entity data is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
Name = Utility.Text.Format("[Entity {0}]", Id);
|
||||
CachedTransform.localPosition = _entityData.Position;
|
||||
CachedTransform.localRotation = _entityData.Rotation;
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnHide(bool isShutdown, object userData)
|
||||
#else
|
||||
protected internal override void OnHide(bool isShutdown, object userData)
|
||||
#endif
|
||||
{
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnAttached(EntityLogic childEntity, Transform parentTransform, object userData)
|
||||
#else
|
||||
protected internal override void OnAttached(EntityLogic childEntity, Transform parentTransform, object userData)
|
||||
#endif
|
||||
{
|
||||
base.OnAttached(childEntity, parentTransform, userData);
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnDetached(EntityLogic childEntity, object userData)
|
||||
#else
|
||||
protected internal override void OnDetached(EntityLogic childEntity, object userData)
|
||||
#endif
|
||||
{
|
||||
base.OnDetached(childEntity, userData);
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnAttachTo(EntityLogic parentEntity, Transform parentTransform, object userData)
|
||||
#else
|
||||
protected internal override void OnAttachTo(EntityLogic parentEntity, Transform parentTransform, object userData)
|
||||
#endif
|
||||
{
|
||||
base.OnAttachTo(parentEntity, parentTransform, userData);
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnDetachFrom(EntityLogic parentEntity, object userData)
|
||||
#else
|
||||
protected internal override void OnDetachFrom(EntityLogic parentEntity, object userData)
|
||||
#endif
|
||||
{
|
||||
base.OnDetachFrom(parentEntity, userData);
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
#else
|
||||
protected internal override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
#endif
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.CustomComponent;
|
||||
using GeometryTD.Map;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Entity.EntityData;
|
||||
using GeometryTD.UI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.Tilemaps;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.Entity
|
||||
{
|
||||
public class MapEntity : EntityBase
|
||||
{
|
||||
private const int DefaultTowerTypeId = 401;
|
||||
|
||||
private static readonly Spawner[] EmptySpawners = Array.Empty<Spawner>();
|
||||
|
||||
[SerializeField] private bool _enableCombatSelectInput = true;
|
||||
|
||||
[SerializeField] private int _towerTypeId = DefaultTowerTypeId;
|
||||
|
||||
[SerializeField] private int[] _buildTowerCosts = { 40, 60, 60, 80 };
|
||||
|
||||
[SerializeField] private int _upgradeCost = 50;
|
||||
|
||||
[SerializeField] private int _destroyGain = 40;
|
||||
|
||||
private MapDataRefs _mapDataRefs;
|
||||
private MapData _mapData;
|
||||
private MapTopologyService _mapTopologyService;
|
||||
private CombatSelectFormUseCase _combatSelectFormUseCase;
|
||||
private CombatSelectInputService _combatSelectInputService;
|
||||
private TowerPlacementService _towerPlacementService;
|
||||
private TowerSelectionPresenter _towerSelectionPresenter;
|
||||
private CombatSelectUseCaseConfigurator _combatSelectUseCaseConfigurator;
|
||||
private MapCombatRuntimeBridge _combatRuntimeBridge;
|
||||
|
||||
public IReadOnlyList<Vector3Int> PathCells => _mapTopologyService != null
|
||||
? _mapTopologyService.PathCells
|
||||
: Array.Empty<Vector3Int>();
|
||||
public IReadOnlyList<Vector3Int> FoundationCells => _mapTopologyService != null
|
||||
? _mapTopologyService.FoundationCells
|
||||
: Array.Empty<Vector3Int>();
|
||||
public Tilemap Tilemap => _mapDataRefs != null ? _mapDataRefs.Tilemap : null;
|
||||
public Spawner[] Spawners => _mapDataRefs?.Spawners ?? EmptySpawners;
|
||||
public House House => _mapDataRefs?.House;
|
||||
|
||||
public bool IsPathCell(Vector3Int cellPosition)
|
||||
{
|
||||
return _mapTopologyService != null && _mapTopologyService.IsPathCell(cellPosition);
|
||||
}
|
||||
|
||||
public bool IsFoundationCell(Vector3Int cellPosition)
|
||||
{
|
||||
return _mapTopologyService != null && _mapTopologyService.IsFoundationCell(cellPosition);
|
||||
}
|
||||
|
||||
public bool TryGetNearestPathCell(Vector3 worldPosition, out Vector3Int pathCell)
|
||||
{
|
||||
pathCell = default;
|
||||
return _mapTopologyService != null && _mapTopologyService.TryGetNearestPathCell(Tilemap, worldPosition, out pathCell);
|
||||
}
|
||||
|
||||
public Vector3 GetPathCellCenterWorld(Vector3Int pathCell)
|
||||
{
|
||||
return _mapTopologyService != null
|
||||
? _mapTopologyService.GetPathCellCenterWorld(Tilemap, pathCell)
|
||||
: Vector3.zero;
|
||||
}
|
||||
|
||||
public bool TryGetDefaultPathCells(Spawner spawner, out IReadOnlyList<Vector3Int> pathCells)
|
||||
{
|
||||
pathCells = null;
|
||||
return _mapTopologyService != null && _mapTopologyService.TryGetDefaultPathCells(spawner, out pathCells);
|
||||
}
|
||||
|
||||
public bool TryFindPathCells(Spawner spawner, IReadOnlyCollection<Vector3Int> blockedCells,
|
||||
List<Vector3Int> pathResult)
|
||||
{
|
||||
return _mapTopologyService != null && _mapTopologyService.TryFindPathCells(spawner, blockedCells, pathResult);
|
||||
}
|
||||
|
||||
public bool TryFindPathWorldPoints(Spawner spawner, IReadOnlyCollection<Vector3Int> blockedCells,
|
||||
List<Vector3> worldPathResult)
|
||||
{
|
||||
return _mapTopologyService != null &&
|
||||
_mapTopologyService.TryFindPathWorldPoints(Tilemap, spawner, blockedCells, worldPathResult);
|
||||
}
|
||||
|
||||
protected override void OnInit(object userData)
|
||||
{
|
||||
base.OnInit(userData);
|
||||
|
||||
_mapDataRefs = GetComponent<MapDataRefs>();
|
||||
if (_mapDataRefs == null)
|
||||
{
|
||||
Log.Error("MapDataRefs is missing on map entity '{0}'.", name);
|
||||
}
|
||||
|
||||
InitializeCombatSelectUseCase();
|
||||
InitializeCombatSelectUseCaseConfigurator();
|
||||
InitializeCombatSelectInputService();
|
||||
InitializeMapTopologyService();
|
||||
InitializeTowerPlacementService();
|
||||
InitializeTowerSelectionPresenter();
|
||||
InitializeCombatRuntimeBridge();
|
||||
}
|
||||
|
||||
protected override void OnShow(object userData)
|
||||
{
|
||||
MapEntityLoadContext loadContext = ResolveLoadContext(userData);
|
||||
_mapData = loadContext?.InitialMapData;
|
||||
if (_mapData == null)
|
||||
{
|
||||
Log.Warning("MapData is invalid for map entity '{0}'.", Id);
|
||||
}
|
||||
|
||||
base.OnShow(_mapData);
|
||||
|
||||
_combatRuntimeBridge?.Initialize(loadContext, name);
|
||||
|
||||
RefreshTiles();
|
||||
ConfigureCombatSelectUseCase();
|
||||
HideCombatSelectForm();
|
||||
}
|
||||
|
||||
protected override void OnHide(bool isShutdown, object userData)
|
||||
{
|
||||
_combatRuntimeBridge?.Reset();
|
||||
HideCombatSelectForm();
|
||||
_towerPlacementService?.HideAndClearAllPlacedTowers();
|
||||
ClearSelectionState();
|
||||
ClearTowerTracking();
|
||||
ClearMapTopology();
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
HandleCombatSelectInput();
|
||||
}
|
||||
|
||||
private void RefreshTiles()
|
||||
{
|
||||
ClearMapTopology();
|
||||
ClearTowerTracking();
|
||||
ClearSelectionState();
|
||||
|
||||
if (_mapDataRefs == null)
|
||||
{
|
||||
_mapDataRefs = GetComponent<MapDataRefs>();
|
||||
if (_mapDataRefs == null)
|
||||
{
|
||||
Log.Error("MapDataRefs is missing on map entity '{0}'.", name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Tilemap tilemap = _mapDataRefs.Tilemap;
|
||||
if (tilemap == null)
|
||||
{
|
||||
Log.Error("Tilemap reference is missing in MapDataRefs on '{0}'.", name);
|
||||
return;
|
||||
}
|
||||
|
||||
_mapTopologyService?.Refresh(tilemap, Spawners, House, name, _mapData != null ? _mapData.LevelId : 0);
|
||||
}
|
||||
|
||||
private void ClearMapTopology()
|
||||
{
|
||||
_mapTopologyService?.Clear();
|
||||
}
|
||||
|
||||
private void ClearTowerTracking()
|
||||
{
|
||||
_towerPlacementService?.ClearTracking();
|
||||
}
|
||||
|
||||
private void ClearSelectionState()
|
||||
{
|
||||
_towerSelectionPresenter?.ClearSelectedObject();
|
||||
}
|
||||
|
||||
private void InitializeCombatSelectUseCase()
|
||||
{
|
||||
if (_combatSelectFormUseCase == null)
|
||||
{
|
||||
_combatSelectFormUseCase = new CombatSelectFormUseCase();
|
||||
}
|
||||
|
||||
GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatSelectForm, _combatSelectFormUseCase);
|
||||
}
|
||||
|
||||
private void InitializeCombatSelectUseCaseConfigurator()
|
||||
{
|
||||
if (_combatSelectUseCaseConfigurator == null)
|
||||
{
|
||||
_combatSelectUseCaseConfigurator = new CombatSelectUseCaseConfigurator();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeTowerSelectionPresenter()
|
||||
{
|
||||
if (_towerSelectionPresenter == null)
|
||||
{
|
||||
_towerSelectionPresenter = new TowerSelectionPresenter();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeTowerPlacementService()
|
||||
{
|
||||
if (_towerPlacementService == null)
|
||||
{
|
||||
_towerPlacementService = new TowerPlacementService();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeCombatSelectInputService()
|
||||
{
|
||||
if (_combatSelectInputService == null)
|
||||
{
|
||||
_combatSelectInputService = new CombatSelectInputService();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeMapTopologyService()
|
||||
{
|
||||
if (_mapTopologyService == null)
|
||||
{
|
||||
_mapTopologyService = new MapTopologyService();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeCombatRuntimeBridge()
|
||||
{
|
||||
if (_combatRuntimeBridge == null)
|
||||
{
|
||||
_combatRuntimeBridge = new MapCombatRuntimeBridge();
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureCombatSelectUseCase()
|
||||
{
|
||||
_combatSelectFormUseCase.SetCoinProvider(GetCurrentCoin);
|
||||
_combatSelectUseCaseConfigurator?.Configure(
|
||||
_combatSelectFormUseCase,
|
||||
GetCurrentCoin,
|
||||
buildIndex => () => TryBuildTower(buildIndex),
|
||||
TryUpgradeTower,
|
||||
_upgradeCost,
|
||||
TryDestroyTower,
|
||||
_destroyGain,
|
||||
_buildTowerCosts,
|
||||
GetCurrentBuildTowerCount(),
|
||||
_mapData != null ? _mapData.InventorySnapshot : null,
|
||||
_mapData != null ? _mapData.ParticipantTowerSnapshot : null);
|
||||
}
|
||||
|
||||
private MapEntityLoadContext ResolveLoadContext(object userData)
|
||||
{
|
||||
if (userData is MapEntityLoadContext loadContext)
|
||||
{
|
||||
return loadContext;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void HandleCombatSelectInput()
|
||||
{
|
||||
Mouse mouse = Mouse.current;
|
||||
if (!_enableCombatSelectInput || mouse == null || !mouse.leftButton.wasPressedThisFrame)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (EventSystem.current != null && EventSystem.current.IsPointerOverGameObject())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_combatSelectInputService == null ||
|
||||
!_combatSelectInputService.TryBuildUserData(Tilemap, CachedTransform,
|
||||
_towerPlacementService != null ? _towerPlacementService.TowerEntityIdByFoundationCell : null,
|
||||
IsFoundationCell, IsTowerAtMaxLevel, _upgradeCost, _destroyGain,
|
||||
out CombatSelectFormUserData userData))
|
||||
{
|
||||
userData = new CombatSelectFormUserData
|
||||
{
|
||||
ClickObjectType = CombatSelectClickObjectType.None
|
||||
};
|
||||
}
|
||||
|
||||
ApplySelectedObject(userData);
|
||||
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);
|
||||
}
|
||||
|
||||
private bool TryBuildTower(int buildIndex)
|
||||
{
|
||||
if (_towerSelectionPresenter == null ||
|
||||
_towerPlacementService == null ||
|
||||
!_towerSelectionPresenter.TryGetSelectedFoundationCell(out Vector3Int selectedFoundationCell) ||
|
||||
!IsFoundationCell(selectedFoundationCell))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_mapData == null || !_mapData.TryGetBuildTowerStats(buildIndex, out TowerStatsData buildTowerStats))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BuildTowerVisualInfo buildVisual = _combatSelectUseCaseConfigurator != null
|
||||
? _combatSelectUseCaseConfigurator.GetBuildVisualInfo(buildIndex)
|
||||
: BuildTowerVisualInfo.Default;
|
||||
|
||||
if (!_towerPlacementService.TryBuildTower(selectedFoundationCell, IsFoundationCell, buildIndex, _buildTowerCosts,
|
||||
buildTowerStats, buildVisual.MuzzleColor, buildVisual.BearingColor, buildVisual.BaseColor, _towerTypeId, Tilemap, TryConsumeCoin, AddCoin, out int towerEntityId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_towerSelectionPresenter.SelectTower(selectedFoundationCell, towerEntityId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryUpgradeTower()
|
||||
{
|
||||
if (_towerSelectionPresenter == null ||
|
||||
_towerPlacementService == null ||
|
||||
!_towerSelectionPresenter.TryGetSelectedTower(_towerPlacementService.FoundationCellByTowerEntityId,
|
||||
out int towerEntityId,
|
||||
out Vector3Int foundationCell))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_towerPlacementService.TryUpgradeTower(towerEntityId, _upgradeCost, _towerTypeId, Tilemap,
|
||||
TryConsumeCoin, AddCoin, out int resultTowerEntityId, out foundationCell))
|
||||
{
|
||||
if (resultTowerEntityId != 0)
|
||||
{
|
||||
_towerSelectionPresenter.SelectTower(foundationCell, resultTowerEntityId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_towerSelectionPresenter.ClearSelectedObject();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_towerSelectionPresenter.SelectTower(foundationCell, resultTowerEntityId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryDestroyTower()
|
||||
{
|
||||
if (_towerSelectionPresenter == null ||
|
||||
_towerPlacementService == null ||
|
||||
!_towerSelectionPresenter.TryGetSelectedTower(_towerPlacementService.FoundationCellByTowerEntityId,
|
||||
out int towerEntityId,
|
||||
out Vector3Int foundationCell))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_towerPlacementService.TryDestroyTower(towerEntityId, _destroyGain, AddCoin, out foundationCell))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_towerSelectionPresenter.ClearSelectedObject();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HideCombatSelectForm()
|
||||
{
|
||||
_combatSelectFormUseCase?.Hide();
|
||||
GameEntry.UIRouter.CloseUI(UIFormType.CombatSelectForm);
|
||||
}
|
||||
|
||||
private int GetCurrentBuildTowerCount()
|
||||
{
|
||||
if (_mapData == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Mathf.Clamp(_mapData.CurrentBuildTowerCount, 0, 4);
|
||||
}
|
||||
|
||||
private bool TryConsumeCoin(int cost)
|
||||
{
|
||||
int requiredCoin = Mathf.Max(0, cost);
|
||||
if (requiredCoin <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_mapData == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _combatRuntimeBridge != null && _combatRuntimeBridge.TryConsumeCoin(requiredCoin);
|
||||
}
|
||||
|
||||
private int GetCurrentCoin()
|
||||
{
|
||||
return Mathf.Max(0, _combatRuntimeBridge != null ? _combatRuntimeBridge.CurrentCoin : 0);
|
||||
}
|
||||
|
||||
private void AddCoin(int coin)
|
||||
{
|
||||
int amount = Mathf.Max(0, coin);
|
||||
if (amount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_combatRuntimeBridge?.AddCoin(amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using Components;
|
||||
using GeometryTD.Entity;
|
||||
using GeometryTD.Entity.EntityData;
|
||||
|
||||
namespace GeometryTD.Entity
|
||||
{
|
||||
public class Player : EntityBase
|
||||
{
|
||||
private float _speed;
|
||||
private InputComponent _inputComponent;
|
||||
private MovementComponent _movementComponent;
|
||||
|
||||
protected override void OnInit(object userData)
|
||||
{
|
||||
base.OnInit(userData);
|
||||
|
||||
_inputComponent = GetComponent<InputComponent>();
|
||||
_movementComponent = GetComponent<MovementComponent>();
|
||||
}
|
||||
|
||||
protected override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
|
||||
if (userData is PlayerData playerData)
|
||||
{
|
||||
_speed = playerData.Speed;
|
||||
}
|
||||
|
||||
_inputComponent.OnInit();
|
||||
_inputComponent.SetListening(true);
|
||||
|
||||
_movementComponent.OnInit(_speed, this.CachedTransform);
|
||||
_movementComponent.SetMove(true);
|
||||
}
|
||||
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
_inputComponent.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
_movementComponent.SetDirection(_inputComponent.Direction);
|
||||
_movementComponent.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
}
|
||||
|
||||
protected override void OnHide(bool isShutdown, object userData)
|
||||
{
|
||||
_inputComponent.SetListening(false);
|
||||
_movementComponent.SetMove(false);
|
||||
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using Components;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Entity.EntityData;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.Entity
|
||||
{
|
||||
public class TowerEntity : EntityBase
|
||||
{
|
||||
private TowerController _towerController;
|
||||
|
||||
public void SetAttackRangeVisible(bool visible)
|
||||
{
|
||||
_towerController?.SetAttackRangeVisible(visible);
|
||||
}
|
||||
|
||||
public bool TryApplyStats(TowerStatsData 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);
|
||||
|
||||
_towerController = GetComponent<TowerController>();
|
||||
if (_towerController == null)
|
||||
{
|
||||
Log.Error("DefenseTowerController is missing on tower entity '{0}'.", name);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
|
||||
if (_towerController == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (userData is not TowerData towerData)
|
||||
{
|
||||
Log.Warning("DefenseTowerData is invalid for tower entity '{0}'.", Id);
|
||||
_towerController.OnReset();
|
||||
GameEntry.Entity.HideEntity(Entity);
|
||||
return;
|
||||
}
|
||||
|
||||
_towerController.OnInit(
|
||||
towerData.Stats,
|
||||
towerData.TowerLevel,
|
||||
towerData.MuzzleColor,
|
||||
towerData.BearingColor,
|
||||
towerData.BaseColor);
|
||||
}
|
||||
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
_towerController?.OnUpdate(elapseSeconds);
|
||||
}
|
||||
|
||||
protected override void OnHide(bool isShutdown, object userData)
|
||||
{
|
||||
_towerController?.SetAttackRangeVisible(false);
|
||||
_towerController?.OnReset();
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user