init
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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