This commit is contained in:
2026-04-30 19:33:16 +08:00
commit fb2252f688
556 changed files with 69721 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
using System;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.Entity.EntityData
{
[Serializable]
public class BulletData : EntityDataBase
{
[SerializeField] private Transform _target = null;
[SerializeField] private float _speed = 0f;
[SerializeField] private float _maxLifetime = 3f;
[SerializeField] private AttackPayload _attackPayload = new AttackPayload();
public BulletData(
int entityId,
int typeId,
Vector3 position,
Transform target,
AttackPayload attackPayload,
float speed,
float maxLifetime = 3f) : base(entityId, typeId)
{
Position = position;
_target = target;
_attackPayload = attackPayload?.Clone() ?? new AttackPayload();
_speed = speed;
_maxLifetime = maxLifetime;
}
public Transform Target
{
get => _target;
set => _target = value;
}
public float Speed
{
get => _speed;
set => _speed = value;
}
public float MaxLifetime
{
get => _maxLifetime;
set => _maxLifetime = value;
}
public AttackPayload AttackPayload
{
get => _attackPayload;
set => _attackPayload = value?.Clone() ?? new AttackPayload();
}
}
}
+83
View File
@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.Entity.EntityData
{
[Serializable]
public class EnemyData : EntityDataBase
{
[SerializeField] private CampType _camp = CampType.Enemy;
[SerializeField] private int _maxHealth = 0;
[SerializeField] private float _speed = 0;
[SerializeField] private Transform _player = null;
[SerializeField] private List<Vector3> _pathPoints = new List<Vector3>();
public EnemyData(int entityId, int typeId, Transform player, Vector3 pos, int maxHp, float speed) : base(
entityId, typeId)
{
_maxHealth = maxHp;
_speed = speed;
Position = pos;
_player = player;
_pathPoints.Clear();
}
public EnemyData(int entityId, int typeId, Vector3 pos, int maxHp, float speed,
IReadOnlyList<Vector3> pathPoints) : base(entityId, typeId)
{
_maxHealth = maxHp;
_speed = speed;
Position = pos;
_player = null;
SetPathPoints(pathPoints);
}
public CampType Camp
{
get => _camp;
set => _camp = value;
}
public int MaxHealth
{
get => _maxHealth;
set => _maxHealth = value;
}
public float Speed
{
get => _speed;
set => _speed = value;
}
public Transform Player
{
get => _player;
set => _player = value;
}
public IReadOnlyList<Vector3> PathPoints => _pathPoints;
public bool HasPath => _pathPoints.Count > 0;
public void SetPathPoints(IReadOnlyList<Vector3> pathPoints)
{
_pathPoints.Clear();
if (pathPoints == null)
{
return;
}
for (int i = 0; i < pathPoints.Count; i++)
{
_pathPoints.Add(pathPoints[i]);
}
}
}
}
@@ -0,0 +1,58 @@
//------------------------------------------------------------
// Game Framework
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
// Homepage: https://gameframework.cn/
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------
using System;
using UnityEngine;
namespace GeometryTD.Entity.EntityData
{
[Serializable]
public abstract class EntityDataBase
{
[SerializeField] private int _id = 0;
[SerializeField] private int _typeId = 0;
[SerializeField] private Vector3 _position = Vector3.zero;
[SerializeField] private Quaternion _rotation = Quaternion.identity;
public EntityDataBase(int entityId, int typeId)
{
_id = entityId;
_typeId = typeId;
}
/// <summary>
/// 生成的实体编号。
/// </summary>
public int Id => _id;
/// <summary>
/// 实体类型编号(外键)。
/// </summary>
public int TypeId => _typeId;
/// <summary>
/// 实体位置。
/// </summary>
public Vector3 Position
{
get => _position;
set => _position = value;
}
/// <summary>
/// 实体朝向。
/// </summary>
public Quaternion Rotation
{
get => _rotation;
set => _rotation = value;
}
}
}
+127
View File
@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using GeometryTD.Definition;
using GeometryTD.CustomUtility;
using UnityEngine;
namespace GeometryTD.Entity.EntityData
{
[Serializable]
public class MapData : EntityDataBase
{
[SerializeField] private int _levelId = 0;
[SerializeField] private int _initialCoin = 0;
[SerializeField] private TowerStatsData[] _buildTowerStatsSnapshot = Array.Empty<TowerStatsData>();
[SerializeField] private BackpackInventoryData _inventorySnapshot;
[SerializeField] private TowerItemData[] _participantTowerSnapshot = Array.Empty<TowerItemData>();
public MapData(int entityId, int levelId, Vector3 position) : this(entityId, 0, levelId, position)
{
}
public MapData(int entityId, int typeId, int levelId, Vector3 position) : base(entityId, typeId)
{
_levelId = levelId;
Position = position;
}
public MapData(
int entityId,
int typeId,
int levelId,
Vector3 position,
int initialCoin,
IReadOnlyList<TowerStatsData> buildTowerStatsSnapshot,
BackpackInventoryData inventorySnapshot,
IReadOnlyList<TowerItemData> participantTowerSnapshot) : base(entityId, typeId)
{
_levelId = levelId;
Position = position;
_initialCoin = Mathf.Max(0, initialCoin);
SetBuildTowerStatsSnapshot(buildTowerStatsSnapshot);
_inventorySnapshot = inventorySnapshot != null
? InventoryCloneUtility.CloneInventory(inventorySnapshot)
: null;
SetParticipantTowerSnapshot(participantTowerSnapshot);
}
public int LevelId
{
get => _levelId;
set => _levelId = value;
}
public int InitialCoin
{
get => _initialCoin;
set => _initialCoin = Mathf.Max(0, value);
}
public int CurrentBuildTowerCount => _buildTowerStatsSnapshot != null ? _buildTowerStatsSnapshot.Length : 0;
public BackpackInventoryData InventorySnapshot => _inventorySnapshot != null
? InventoryCloneUtility.CloneInventory(_inventorySnapshot)
: null;
public IReadOnlyList<TowerItemData> ParticipantTowerSnapshot => _participantTowerSnapshot;
public bool TryGetBuildTowerStats(int buildIndex, out TowerStatsData stats)
{
stats = null;
if (_buildTowerStatsSnapshot == null || buildIndex < 0 || buildIndex >= _buildTowerStatsSnapshot.Length)
{
return false;
}
TowerStatsData sourceStats = _buildTowerStatsSnapshot[buildIndex];
if (sourceStats == null)
{
return false;
}
stats = InventoryCloneUtility.CloneTowerStats(sourceStats);
return stats != null;
}
public void SetBuildTowerStatsSnapshot(IReadOnlyList<TowerStatsData> buildTowerStatsSnapshot)
{
if (buildTowerStatsSnapshot == null || buildTowerStatsSnapshot.Count <= 0)
{
_buildTowerStatsSnapshot = Array.Empty<TowerStatsData>();
return;
}
_buildTowerStatsSnapshot = new TowerStatsData[buildTowerStatsSnapshot.Count];
for (int i = 0; i < buildTowerStatsSnapshot.Count; i++)
{
_buildTowerStatsSnapshot[i] = InventoryCloneUtility.CloneTowerStats(buildTowerStatsSnapshot[i]);
}
}
public MapData CloneForEntity(int entityId, Vector3 position)
{
return new MapData(
entityId,
TypeId,
_levelId,
position,
_initialCoin,
_buildTowerStatsSnapshot,
_inventorySnapshot,
_participantTowerSnapshot);
}
public void SetParticipantTowerSnapshot(IReadOnlyList<TowerItemData> participantTowerSnapshot)
{
if (participantTowerSnapshot == null || participantTowerSnapshot.Count <= 0)
{
_participantTowerSnapshot = Array.Empty<TowerItemData>();
return;
}
_participantTowerSnapshot = new TowerItemData[participantTowerSnapshot.Count];
for (int i = 0; i < participantTowerSnapshot.Count; i++)
{
_participantTowerSnapshot[i] = InventoryCloneUtility.CloneTower(participantTowerSnapshot[i]);
}
}
}
}
@@ -0,0 +1,18 @@
using System;
namespace GeometryTD.Entity.EntityData
{
public sealed class MapEntityLoadContext
{
public MapEntityLoadContext(MapData initialMapData, Func<int, bool> tryConsumeCoin, Action<int> addCoin)
{
InitialMapData = initialMapData;
TryConsumeCoin = tryConsumeCoin;
AddCoin = addCoin;
}
public MapData InitialMapData { get; }
public Func<int, bool> TryConsumeCoin { get; }
public Action<int> AddCoin { get; }
}
}
+40
View File
@@ -0,0 +1,40 @@
using System;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.Entity.EntityData
{
[Serializable]
public class PlayerData : EntityDataBase
{
[SerializeField] private CampType _camp = CampType.Player;
[SerializeField] private int _maxHealth = 0;
[SerializeField] private float _speed = 0;
public PlayerData(int entityId, int typeId, int maxHp, float speed) : base(entityId, typeId)
{
_maxHealth = maxHp;
_speed = speed;
}
public CampType Camp
{
get => _camp;
set => _camp = value;
}
public int MaxHealth
{
get => _maxHealth;
set => _maxHealth = value;
}
public float Speed
{
get => _speed;
set => _speed = value;
}
}
}
+60
View File
@@ -0,0 +1,60 @@
using System;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.Entity.EntityData
{
[Serializable]
public class TowerData : EntityDataBase
{
[SerializeField] private TowerStatsData _stats;
[SerializeField] private int _towerLevel = 0;
[SerializeField] private Color _muzzleColor;
[SerializeField] private Color _bearingColor;
[SerializeField] private Color _baseColor;
public TowerData(int entityId, int typeId, Vector3 position, Quaternion rotation,
TowerStatsData stats, int towerLevel = 0, Color? muzzleColor = null, Color? bearingColor = null,
Color? baseColor = null)
: base(entityId, typeId)
{
Position = position;
Rotation = rotation;
_stats = stats ?? new TowerStatsData();
_towerLevel = Mathf.Max(0, towerLevel);
_muzzleColor = muzzleColor ?? Color.white;
_bearingColor = bearingColor ?? Color.white;
_baseColor = baseColor ?? Color.white;
}
public TowerStatsData Stats
{
get => _stats;
set => _stats = value ?? new TowerStatsData();
}
public int TowerLevel
{
get => _towerLevel;
set => _towerLevel = Mathf.Max(0, value);
}
public Color MuzzleColor
{
get => _muzzleColor;
set => _muzzleColor = value;
}
public Color BearingColor
{
get => _bearingColor;
set => _bearingColor = value;
}
public Color BaseColor
{
get => _baseColor;
set => _baseColor = value;
}
}
}
+97
View File
@@ -0,0 +1,97 @@
using GameFramework.DataTable;
using System;
using GeometryTD.CustomUtility;
using GeometryTD.DataTable;
using GeometryTD.Entity;
using GeometryTD.Entity.EntityData;
using UnityGameFramework.Runtime;
namespace GeometryTD
{
public static class EntityExtension
{
private static int _serialId = 0;
public static EntityBase GetGameEntity(this EntityComponent entityComponent, int entityId)
{
UnityGameFramework.Runtime.Entity entity = entityComponent.GetEntity(entityId);
if (entity == null)
{
return null;
}
return (EntityBase)entity.Logic;
}
public static void HideEntity(this EntityComponent entityComponent, EntityBase entity)
{
entityComponent.HideEntity(entity.Entity);
}
public static void AttachEntity(this EntityComponent entityComponent, EntityBase entityBase, int ownerId,
string parentTransformPath = null, object userData = null)
{
entityComponent.AttachEntity(entityBase.Entity, ownerId, parentTransformPath, userData);
}
public static void ShowEnemy(this EntityComponent entityComponent, EnemyData data)
{
entityComponent.ShowEntity(typeof(EnemyEntity), "Enemy", Constant.AssetPriority.EnemyAsset, data);
}
public static void ShowDefenseTower(this EntityComponent entityComponent, TowerData data)
{
entityComponent.ShowEntity(typeof(TowerEntity), "Tower", Constant.AssetPriority.EnemyAsset, data);
}
public static void ShowBullet(this EntityComponent entityComponent, BulletData data)
{
entityComponent.ShowEntity(typeof(BulletEntity), "Bullet", Constant.AssetPriority.BulletAsset, data);
}
public static void ShowMap(this EntityComponent entityComponent, MapEntityLoadContext loadContext)
{
ShowMap(entityComponent, loadContext, null);
}
public static void ShowMap(this EntityComponent entityComponent, MapEntityLoadContext loadContext, string mapAssetName)
{
MapData data = loadContext?.InitialMapData;
if (data == null)
{
Log.Warning("Map data is invalid.");
return;
}
string resolvedMapAssetName = string.IsNullOrEmpty(mapAssetName) ? data.LevelId.ToString() : mapAssetName;
string mapAssetPath = AssetUtility.GetLevelMapAsset(resolvedMapAssetName);
entityComponent.ShowEntity(data.Id, typeof(MapEntity), mapAssetPath, "Map", Constant.AssetPriority.MapAsset, loadContext);
}
private static void ShowEntity(this EntityComponent entityComponent, Type logicType, string entityGroup,
int priority, EntityDataBase data)
{
if (data == null)
{
Log.Warning("Data is invalid.");
return;
}
IDataTable<DREntity> dtEntity = GameEntry.DataTable.GetDataTable<DREntity>();
DREntity drEntity = dtEntity.GetDataRow(data.TypeId);
if (drEntity == null)
{
Log.Warning("Can not load entity id '{0}' from data table.", data.TypeId.ToString());
return;
}
entityComponent.ShowEntity(data.Id, logicType, AssetUtility.GetEntityAsset(drEntity.AssetName), entityGroup,
priority, data);
}
public static int GenerateSerialId(this EntityComponent entityComponent)
{
return --_serialId;
}
}
}
@@ -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; }
}
}
+237
View File
@@ -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;
}
}
}
+114
View File
@@ -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);
}
}
}
+439
View File
@@ -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);
}
}
}
+55
View File
@@ -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);
}
}
}
+77
View File
@@ -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);
}
}
}