init
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Components
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class BasicBaseComp : MonoBehaviour
|
||||
{
|
||||
[SerializeField] [Min(0.01f)] private float _attackSpeed = 1f;
|
||||
|
||||
[SerializeField] private AttackPropertyType _attackPropertyType = AttackPropertyType.Physics;
|
||||
|
||||
[SerializeField] private SpriteRenderer _renderer;
|
||||
|
||||
private float _cooldownRemaining;
|
||||
|
||||
public float AttackSpeed => _attackSpeed;
|
||||
public AttackPropertyType AttackPropertyType => _attackPropertyType;
|
||||
public bool CanAttack => _cooldownRemaining <= 0f;
|
||||
|
||||
public void OnInit(float attackSpeed, AttackPropertyType attackPropertyType)
|
||||
{
|
||||
_attackSpeed = Mathf.Max(0.01f, attackSpeed);
|
||||
_attackPropertyType = attackPropertyType;
|
||||
_cooldownRemaining = 0f;
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
_attackSpeed = 1f;
|
||||
_attackPropertyType = AttackPropertyType.None;
|
||||
_cooldownRemaining = 0f;
|
||||
}
|
||||
|
||||
public void SetColor(Color color)
|
||||
{
|
||||
if (_renderer != null)
|
||||
{
|
||||
_renderer.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick(float deltaTime)
|
||||
{
|
||||
if (_cooldownRemaining <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_cooldownRemaining = Mathf.Max(0f, _cooldownRemaining - Mathf.Max(0f, deltaTime));
|
||||
}
|
||||
|
||||
public bool TryAttack(BasicBearingComp bearingComp, ShooterMuzzleComp shooterMuzzleComp, Transform target, float deltaTime)
|
||||
{
|
||||
if (bearingComp == null || shooterMuzzleComp == null || target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Tick(deltaTime);
|
||||
bool isAligned = bearingComp.TrackTarget(target, deltaTime);
|
||||
if (!isAligned || !CanAttack)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool fired = shooterMuzzleComp.Attack(target, _attackPropertyType);
|
||||
if (!fired)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_cooldownRemaining = 1f / _attackSpeed;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Components
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class BasicBearingComp : MonoBehaviour
|
||||
{
|
||||
[SerializeField] [Min(1f)] private float _rotateSpeed = 180f;
|
||||
|
||||
[SerializeField] [Min(0.1f)] private float _attackRange = 5f;
|
||||
|
||||
[SerializeField] [Min(0.1f)] private float _aimToleranceAngle = 2f;
|
||||
|
||||
[SerializeField] private Transform _rotateRoot;
|
||||
|
||||
[SerializeField] private SpriteRenderer _renderer;
|
||||
|
||||
public float RotateSpeed => _rotateSpeed;
|
||||
public float AttackRange => _attackRange;
|
||||
public float AimToleranceAngle => _aimToleranceAngle;
|
||||
|
||||
public void OnInit(float rotateSpeed, float attackRange = 5f)
|
||||
{
|
||||
_rotateSpeed = Mathf.Max(1f, rotateSpeed);
|
||||
_attackRange = Mathf.Max(0.1f, attackRange);
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
_rotateSpeed = 1f;
|
||||
_attackRange = 5f;
|
||||
}
|
||||
|
||||
public void SetColor(Color color)
|
||||
{
|
||||
if (_renderer != null)
|
||||
{
|
||||
_renderer.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTargetInRange(Transform target, Transform origin = null)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Transform originTransform = origin != null ? origin : transform;
|
||||
Vector3 delta = target.position - originTransform.position;
|
||||
return delta.sqrMagnitude <= _attackRange * _attackRange;
|
||||
}
|
||||
|
||||
public bool TrackTarget(Transform target, float deltaTime)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Transform rotateRoot = _rotateRoot != null ? _rotateRoot : transform;
|
||||
Vector2 toTarget = (Vector2)(target.position - rotateRoot.position);
|
||||
|
||||
if (toTarget.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
float targetZAngle = Vector2.SignedAngle(Vector2.up, toTarget);
|
||||
float currentZAngle = rotateRoot.eulerAngles.z;
|
||||
float maxStep = _rotateSpeed * Mathf.Max(0f, deltaTime);
|
||||
float angleDelta = Mathf.DeltaAngle(currentZAngle, targetZAngle);
|
||||
float nextZAngle = currentZAngle + Mathf.Clamp(angleDelta, -maxStep, maxStep);
|
||||
rotateRoot.rotation = Quaternion.Euler(0f, 0f, nextZAngle);
|
||||
return IsTargetAligned(target);
|
||||
}
|
||||
|
||||
public bool IsTargetAligned(Transform target)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Transform rotateRoot = _rotateRoot != null ? _rotateRoot : transform;
|
||||
Vector2 toTarget = (Vector2)(target.position - rotateRoot.position);
|
||||
|
||||
if (toTarget.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
float targetZAngle = Vector2.SignedAngle(Vector2.up, toTarget);
|
||||
float currentZAngle = rotateRoot.eulerAngles.z;
|
||||
float angle = Mathf.Abs(Mathf.DeltaAngle(currentZAngle, targetZAngle));
|
||||
return angle <= _aimToleranceAngle;
|
||||
}
|
||||
|
||||
public bool TryAttack(ShooterMuzzleComp shooterMuzzleComp, Transform target, float deltaTime)
|
||||
{
|
||||
if (shooterMuzzleComp == null || target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsTargetInRange(target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isAligned = TrackTarget(target, deltaTime);
|
||||
if (!isAligned)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return shooterMuzzleComp.Attack(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using GeometryTD.Definition;
|
||||
|
||||
namespace Components
|
||||
{
|
||||
public interface IDamageReceiver
|
||||
{
|
||||
void TakeDamage(AttackPayload attackPayload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Components
|
||||
{
|
||||
public class InputComponent : MonoBehaviour
|
||||
{
|
||||
private PlayerInputActions _inputActions;
|
||||
private bool _isListening = false;
|
||||
|
||||
private Vector3 _direction = Vector3.zero;
|
||||
public Vector3 Direction => _direction;
|
||||
|
||||
public void OnInit()
|
||||
{
|
||||
_inputActions = new();
|
||||
}
|
||||
|
||||
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
if (_isListening && _inputActions != null)
|
||||
{
|
||||
ReadInput();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
_inputActions = null;
|
||||
_isListening = false;
|
||||
}
|
||||
|
||||
private void ReadInput()
|
||||
{
|
||||
Vector2 rawDir = _inputActions.Game.Move.ReadValue<Vector2>();
|
||||
_direction = new Vector3(rawDir.x, 0, rawDir.y);
|
||||
}
|
||||
|
||||
public void SetListening(bool isListening)
|
||||
{
|
||||
_isListening = isListening;
|
||||
if (_isListening)
|
||||
{
|
||||
_inputActions.Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
_inputActions.Disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Components
|
||||
{
|
||||
public class MovementComponent : MonoBehaviour
|
||||
{
|
||||
private bool _isMoving = false;
|
||||
private Vector3 _direction = Vector3.forward;
|
||||
private float _speed = 0;
|
||||
private float _speedMultiplier = 1f;
|
||||
private Transform _cachedTransform;
|
||||
|
||||
public void OnInit(float speed, Transform transform)
|
||||
{
|
||||
_speed = speed;
|
||||
_speedMultiplier = 1f;
|
||||
_cachedTransform = transform;
|
||||
}
|
||||
|
||||
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
if (_isMoving && _cachedTransform != null)
|
||||
{
|
||||
Move(elapseSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
_speed = 0;
|
||||
_speedMultiplier = 1f;
|
||||
_cachedTransform = null;
|
||||
_isMoving = false;
|
||||
}
|
||||
|
||||
private void Move(float deltaTime = 0)
|
||||
{
|
||||
_cachedTransform.Translate(_direction * (_speed * _speedMultiplier) * deltaTime);
|
||||
}
|
||||
|
||||
public void SetMove(bool isMoving) => _isMoving = isMoving;
|
||||
public void SetDirection(Vector3 direction) => _direction = direction;
|
||||
public void SetSpeedMultiplier(float speedMultiplier) => _speedMultiplier = Mathf.Max(0f, speedMultiplier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using UnityEngine;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Entity.EntityData;
|
||||
|
||||
namespace Components
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ShooterBullet : MonoBehaviour
|
||||
{
|
||||
[SerializeField] [Min(0.1f)] private float _defaultSpeed = 12f;
|
||||
[SerializeField] [Min(0.02f)] private float _hitDistance = 0.15f;
|
||||
[SerializeField] [Min(0.1f)] private float _maxLifetime = 3f;
|
||||
|
||||
private Transform _target;
|
||||
private float _speed;
|
||||
private AttackPayload _attackPayload;
|
||||
private float _lifetime;
|
||||
private float _runtimeMaxLifetime;
|
||||
private bool _isRunning;
|
||||
private bool _despawnRequested;
|
||||
|
||||
public void OnShow(BulletData bulletData)
|
||||
{
|
||||
_target = bulletData != null ? bulletData.Target : null;
|
||||
_attackPayload = bulletData?.AttackPayload?.Clone() ?? new AttackPayload();
|
||||
_speed = bulletData != null && bulletData.Speed > 0f ? bulletData.Speed : _defaultSpeed;
|
||||
_runtimeMaxLifetime = bulletData != null && bulletData.MaxLifetime > 0f ? bulletData.MaxLifetime : _maxLifetime;
|
||||
_lifetime = 0f;
|
||||
_despawnRequested = false;
|
||||
_isRunning = _target != null;
|
||||
if (_isRunning)
|
||||
{
|
||||
Vector2 initialDirection = (Vector2)(_target.position - transform.position);
|
||||
RotateToDirection(initialDirection);
|
||||
}
|
||||
|
||||
if (!_isRunning)
|
||||
{
|
||||
_despawnRequested = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
_target = null;
|
||||
_speed = 0f;
|
||||
_attackPayload = new AttackPayload();
|
||||
_lifetime = 0f;
|
||||
_runtimeMaxLifetime = _maxLifetime;
|
||||
_isRunning = false;
|
||||
_despawnRequested = false;
|
||||
}
|
||||
|
||||
public void Tick(float deltaTime)
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lifetime += Mathf.Max(0f, deltaTime);
|
||||
if (_lifetime >= _runtimeMaxLifetime || _target == null)
|
||||
{
|
||||
_isRunning = false;
|
||||
_despawnRequested = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 currentPosition = transform.position;
|
||||
Vector2 toTarget = (Vector2)(_target.position - currentPosition);
|
||||
float hitDistanceSquared = _hitDistance * _hitDistance;
|
||||
if (toTarget.sqrMagnitude <= hitDistanceSquared)
|
||||
{
|
||||
HitTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 direction = toTarget.normalized;
|
||||
RotateToDirection(direction);
|
||||
float moveDistance = _speed * Mathf.Max(0f, deltaTime);
|
||||
if (moveDistance * moveDistance >= toTarget.sqrMagnitude)
|
||||
{
|
||||
Vector3 targetPosition = _target.position;
|
||||
transform.position = new Vector3(targetPosition.x, targetPosition.y, currentPosition.z);
|
||||
HitTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
transform.position += (Vector3)(direction * moveDistance);
|
||||
}
|
||||
|
||||
public bool TryConsumeDespawnRequest()
|
||||
{
|
||||
if (!_despawnRequested)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_despawnRequested = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HitTarget()
|
||||
{
|
||||
if (_target == null)
|
||||
{
|
||||
_isRunning = false;
|
||||
_despawnRequested = true;
|
||||
return;
|
||||
}
|
||||
|
||||
MonoBehaviour[] components = _target.GetComponentsInParent<MonoBehaviour>();
|
||||
foreach (var mono in components)
|
||||
{
|
||||
if (mono is IDamageReceiver damageReceiver)
|
||||
{
|
||||
damageReceiver.TakeDamage(_attackPayload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_isRunning = false;
|
||||
_despawnRequested = true;
|
||||
}
|
||||
|
||||
private void RotateToDirection(Vector2 direction)
|
||||
{
|
||||
if (direction.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float targetZAngle = Vector2.SignedAngle(Vector2.up, direction);
|
||||
transform.rotation = Quaternion.Euler(0f, 0f, targetZAngle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Entity.EntityData;
|
||||
using GeometryTD;
|
||||
using GeometryTD.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Components
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ShooterMuzzleComp : MonoBehaviour
|
||||
{
|
||||
[SerializeField] [Min(1f)] private int _attackDamage = 100;
|
||||
|
||||
[SerializeField] private AttackMethodType _attackMethodType = AttackMethodType.NormalBullet;
|
||||
|
||||
[SerializeField] [Min(1)] private int _bulletTypeId = 501;
|
||||
|
||||
[SerializeField] private Transform _muzzlePoint;
|
||||
|
||||
[SerializeField] [Min(0.1f)] private float _bulletSpeed = 12f;
|
||||
|
||||
[SerializeField] private SpriteRenderer _renderer;
|
||||
|
||||
private TagRuntimeData[] _tagRuntimes = System.Array.Empty<TagRuntimeData>();
|
||||
|
||||
public int AttackDamage => _attackDamage;
|
||||
public AttackMethodType AttackMethodType => _attackMethodType;
|
||||
|
||||
public void OnInit(
|
||||
int attackDamage,
|
||||
AttackMethodType attackMethodType = AttackMethodType.NormalBullet,
|
||||
int bulletTypeId = 501,
|
||||
TagRuntimeData[] tagRuntimes = null)
|
||||
{
|
||||
_attackDamage = Mathf.Max(1, attackDamage);
|
||||
_attackMethodType = attackMethodType;
|
||||
_bulletTypeId = Mathf.Max(1, bulletTypeId);
|
||||
_tagRuntimes = CloneTagRuntimes(tagRuntimes);
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
_attackDamage = 1;
|
||||
_attackMethodType = AttackMethodType.None;
|
||||
_bulletTypeId = 501;
|
||||
_tagRuntimes = System.Array.Empty<TagRuntimeData>();
|
||||
}
|
||||
|
||||
public void SetColor(Color color)
|
||||
{
|
||||
if (_renderer != null)
|
||||
{
|
||||
_renderer.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Attack(Transform target)
|
||||
{
|
||||
return Attack(target, AttackPropertyType.None);
|
||||
}
|
||||
|
||||
public bool Attack(Transform target, AttackPropertyType attackPropertyType)
|
||||
{
|
||||
if (_attackMethodType != AttackMethodType.NormalBullet)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Transform spawnPoint = _muzzlePoint != null ? _muzzlePoint : transform;
|
||||
int bulletEntityId = GameEntry.Entity.GenerateSerialId();
|
||||
AttackPayload attackPayload = new AttackPayload
|
||||
{
|
||||
BaseDamage = _attackDamage,
|
||||
AttackPropertyType = attackPropertyType,
|
||||
SourceEntityId = ResolveSourceEntityId(),
|
||||
ProjectileEntityId = bulletEntityId,
|
||||
OriginPosition = spawnPoint.position,
|
||||
TagRuntimes = CloneTagRuntimes(_tagRuntimes)
|
||||
};
|
||||
BulletData bulletData = new BulletData(
|
||||
bulletEntityId,
|
||||
_bulletTypeId,
|
||||
spawnPoint.position,
|
||||
target,
|
||||
attackPayload,
|
||||
_bulletSpeed);
|
||||
GameEntry.Entity.ShowBullet(bulletData);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static TagRuntimeData[] CloneTagRuntimes(TagRuntimeData[] tagRuntimes)
|
||||
{
|
||||
if (tagRuntimes == null || tagRuntimes.Length <= 0)
|
||||
{
|
||||
return System.Array.Empty<TagRuntimeData>();
|
||||
}
|
||||
|
||||
TagRuntimeData[] cloned = new TagRuntimeData[tagRuntimes.Length];
|
||||
for (int i = 0; i < tagRuntimes.Length; i++)
|
||||
{
|
||||
TagRuntimeData runtime = tagRuntimes[i];
|
||||
if (runtime == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
cloned[i] = new TagRuntimeData
|
||||
{
|
||||
TagType = runtime.TagType,
|
||||
TotalStack = runtime.TotalStack
|
||||
};
|
||||
}
|
||||
|
||||
return cloned;
|
||||
}
|
||||
|
||||
private int ResolveSourceEntityId()
|
||||
{
|
||||
TowerEntity towerEntity = GetComponentInParent<TowerEntity>();
|
||||
return towerEntity != null ? towerEntity.Id : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Components
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class TowerController : MonoBehaviour
|
||||
{
|
||||
private const string AttackRangeIndicatorObjectName = "AttackRangeIndicator";
|
||||
private const int MinTowerLevel = 0;
|
||||
private const int MaxTowerLevel = 4;
|
||||
|
||||
private static Material _attackRangeSharedMaterial;
|
||||
|
||||
[SerializeField] private ShooterMuzzleComp _muzzleComp;
|
||||
[SerializeField] private BasicBearingComp _bearingComp;
|
||||
[SerializeField] private BasicBaseComp _baseComp;
|
||||
[SerializeField] private Transform _scanOrigin;
|
||||
[SerializeField] [Min(0.02f)] private float _retargetInterval = 0.1f;
|
||||
[SerializeField] private LineRenderer _attackRangeRenderer;
|
||||
[SerializeField] [Min(12)] private int _attackRangeSegments = 64;
|
||||
[SerializeField] [Min(0.005f)] private float _attackRangeLineWidth = 0.08f;
|
||||
[SerializeField] private Color _attackRangeColor = new Color(0.1f, 1f, 0.4f, 0.8f);
|
||||
[SerializeField] private float _attackRangeZOffset = -0.01f;
|
||||
|
||||
private Transform _currentTarget;
|
||||
private float _retargetTimer;
|
||||
private float _attackRange;
|
||||
private int _towerLevel;
|
||||
private Color _muzzleColor = Color.white;
|
||||
private Color _bearingColor = Color.white;
|
||||
private Color _baseColor = Color.white;
|
||||
private TagRuntimeData[] _tagRuntimes = System.Array.Empty<TagRuntimeData>();
|
||||
|
||||
public Transform CurrentTarget => _currentTarget;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
ResolveComponents();
|
||||
}
|
||||
|
||||
public void OnInit(TowerStatsData stats)
|
||||
{
|
||||
OnInit(stats, MinTowerLevel);
|
||||
}
|
||||
|
||||
public void OnInit(TowerStatsData stats, int towerLevel, Color muzzleColor, Color bearingColor, Color baseColor)
|
||||
{
|
||||
SetComponentColors(muzzleColor, bearingColor, baseColor);
|
||||
OnInit(stats, towerLevel);
|
||||
}
|
||||
|
||||
public void OnInit(TowerStatsData stats, int towerLevel)
|
||||
{
|
||||
ResolveComponents();
|
||||
if (stats == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_towerLevel = Mathf.Clamp(towerLevel, MinTowerLevel, MaxTowerLevel);
|
||||
int attackDamage = ResolveIntValue(stats.AttackDamage, _towerLevel, 1, 1);
|
||||
float rotateSpeed = ResolveFloatValue(stats.RotateSpeed, _towerLevel, 180f, 1f);
|
||||
float attackRange = ResolveFloatValue(stats.AttackRange, _towerLevel, 5f, 0.1f);
|
||||
float attackSpeed = ResolveFloatValue(stats.AttackSpeed, _towerLevel, 1f, 0.01f);
|
||||
_tagRuntimes = ResolveRuntimeTags(stats);
|
||||
|
||||
_muzzleComp?.OnInit(attackDamage, stats.AttackMethodType, tagRuntimes: _tagRuntimes);
|
||||
_bearingComp?.OnInit(rotateSpeed, attackRange);
|
||||
_baseComp?.OnInit(attackSpeed, stats.AttackPropertyType);
|
||||
ApplyComponentColors();
|
||||
SetAttackRange(attackRange);
|
||||
SetAttackRangeVisible(false);
|
||||
_currentTarget = null;
|
||||
_retargetTimer = 0f;
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
SetAttackRangeVisible(false);
|
||||
_currentTarget = null;
|
||||
_retargetTimer = 0f;
|
||||
_towerLevel = MinTowerLevel;
|
||||
_tagRuntimes = System.Array.Empty<TagRuntimeData>();
|
||||
_muzzleComp?.OnReset();
|
||||
_bearingComp?.OnReset();
|
||||
_baseComp?.OnReset();
|
||||
}
|
||||
|
||||
public void SetAttackRangeVisible(bool visible)
|
||||
{
|
||||
EnsureAttackRangeRenderer();
|
||||
if (_attackRangeRenderer != null)
|
||||
{
|
||||
_attackRangeRenderer.enabled = visible;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAttackRange(float range)
|
||||
{
|
||||
_attackRange = Mathf.Max(0.05f, range);
|
||||
EnsureAttackRangeRenderer();
|
||||
RebuildAttackRangeGeometry();
|
||||
}
|
||||
|
||||
public void SetComponentColors(Color muzzleColor, Color bearingColor, Color baseColor)
|
||||
{
|
||||
_muzzleColor = muzzleColor;
|
||||
_bearingColor = bearingColor;
|
||||
_baseColor = baseColor;
|
||||
ApplyComponentColors();
|
||||
}
|
||||
|
||||
public void SetTarget(Transform target)
|
||||
{
|
||||
_currentTarget = target;
|
||||
}
|
||||
|
||||
public void ClearTarget()
|
||||
{
|
||||
_currentTarget = null;
|
||||
}
|
||||
|
||||
public void OnUpdate(float deltaTime)
|
||||
{
|
||||
if (!HasCoreComponents())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsTargetValid(_currentTarget) || !_bearingComp.IsTargetInRange(_currentTarget, _scanOrigin))
|
||||
{
|
||||
TryRetarget(deltaTime);
|
||||
}
|
||||
|
||||
if (_currentTarget == null)
|
||||
{
|
||||
_baseComp.Tick(deltaTime);
|
||||
return;
|
||||
}
|
||||
|
||||
_baseComp.TryAttack(_bearingComp, _muzzleComp, _currentTarget, deltaTime);
|
||||
}
|
||||
|
||||
private void TryRetarget(float deltaTime)
|
||||
{
|
||||
_retargetTimer -= Mathf.Max(0f, deltaTime);
|
||||
if (_retargetTimer > 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_retargetTimer = _retargetInterval;
|
||||
_currentTarget = FindNearestEnemyTarget();
|
||||
}
|
||||
|
||||
private Transform FindNearestEnemyTarget()
|
||||
{
|
||||
Vector3 origin = _scanOrigin != null ? _scanOrigin.position : transform.position;
|
||||
float maxRange = _bearingComp.AttackRange;
|
||||
float maxRangeSquared = maxRange * maxRange;
|
||||
|
||||
EnemyEntity bestEnemy = null;
|
||||
float bestDistanceSquared = float.MaxValue;
|
||||
|
||||
foreach (var enemy in EnemyEntity.ActiveEnemies)
|
||||
{
|
||||
if (enemy == null || !enemy.isActiveAndEnabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Transform enemyTransform = enemy.transform;
|
||||
Vector3 delta = enemyTransform.position - origin;
|
||||
float distanceSquared = delta.sqrMagnitude;
|
||||
if (distanceSquared > maxRangeSquared || distanceSquared >= bestDistanceSquared)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bestDistanceSquared = distanceSquared;
|
||||
bestEnemy = enemy;
|
||||
}
|
||||
|
||||
return bestEnemy != null ? bestEnemy.transform : null;
|
||||
}
|
||||
|
||||
private void ApplyComponentColors()
|
||||
{
|
||||
_muzzleComp?.SetColor(_muzzleColor);
|
||||
_bearingComp?.SetColor(_bearingColor);
|
||||
_baseComp?.SetColor(_baseColor);
|
||||
}
|
||||
|
||||
private void ResolveComponents()
|
||||
{
|
||||
if (_muzzleComp == null)
|
||||
{
|
||||
_muzzleComp = GetComponent<ShooterMuzzleComp>();
|
||||
}
|
||||
|
||||
if (_bearingComp == null)
|
||||
{
|
||||
_bearingComp = GetComponent<BasicBearingComp>();
|
||||
}
|
||||
|
||||
if (_baseComp == null)
|
||||
{
|
||||
_baseComp = GetComponent<BasicBaseComp>();
|
||||
}
|
||||
|
||||
ApplyComponentColors();
|
||||
EnsureAttackRangeRenderer();
|
||||
}
|
||||
|
||||
private bool HasCoreComponents()
|
||||
{
|
||||
return _muzzleComp != null && _bearingComp != null && _baseComp != null;
|
||||
}
|
||||
|
||||
private static bool IsTargetValid(Transform target)
|
||||
{
|
||||
return target != null && target.gameObject.activeInHierarchy;
|
||||
}
|
||||
|
||||
private static TagRuntimeData[] CloneTagRuntimes(TagRuntimeData[] source)
|
||||
{
|
||||
if (source == null || source.Length <= 0)
|
||||
{
|
||||
return System.Array.Empty<TagRuntimeData>();
|
||||
}
|
||||
|
||||
TagRuntimeData[] cloned = new TagRuntimeData[source.Length];
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
{
|
||||
TagRuntimeData runtime = source[i];
|
||||
if (runtime == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
cloned[i] = new TagRuntimeData
|
||||
{
|
||||
TagType = runtime.TagType,
|
||||
TotalStack = runtime.TotalStack
|
||||
};
|
||||
}
|
||||
|
||||
return cloned;
|
||||
}
|
||||
|
||||
private static TagRuntimeData[] ResolveRuntimeTags(TowerStatsData stats)
|
||||
{
|
||||
if (stats == null)
|
||||
{
|
||||
return System.Array.Empty<TagRuntimeData>();
|
||||
}
|
||||
|
||||
if (stats.TagRuntimes != null && stats.TagRuntimes.Length > 0)
|
||||
{
|
||||
return CloneTagRuntimes(stats.TagRuntimes);
|
||||
}
|
||||
|
||||
return TowerTagAggregationService.BuildRuntimeTagsFromUniqueTags(stats.Tags);
|
||||
}
|
||||
|
||||
private static int ResolveIntValue(int[] values, int level, int fallback, int minValue)
|
||||
{
|
||||
int resolved = Mathf.Max(minValue, fallback);
|
||||
if (values == null || values.Length <= 0)
|
||||
{
|
||||
return resolved;
|
||||
}
|
||||
|
||||
int index = Mathf.Clamp(level, 0, values.Length - 1);
|
||||
return Mathf.Max(minValue, values[index]);
|
||||
}
|
||||
|
||||
private static float ResolveFloatValue(float[] values, int level, float fallback, float minValue)
|
||||
{
|
||||
float resolved = Mathf.Max(minValue, fallback);
|
||||
if (values == null || values.Length <= 0)
|
||||
{
|
||||
return resolved;
|
||||
}
|
||||
|
||||
int index = Mathf.Clamp(level, 0, values.Length - 1);
|
||||
return Mathf.Max(minValue, values[index]);
|
||||
}
|
||||
|
||||
private void EnsureAttackRangeRenderer()
|
||||
{
|
||||
if (_attackRangeRenderer == null)
|
||||
{
|
||||
Transform indicatorTransform = transform.Find(AttackRangeIndicatorObjectName);
|
||||
if (indicatorTransform == null)
|
||||
{
|
||||
GameObject indicatorObject = new GameObject(AttackRangeIndicatorObjectName);
|
||||
indicatorTransform = indicatorObject.transform;
|
||||
indicatorTransform.SetParent(transform, false);
|
||||
}
|
||||
|
||||
_attackRangeRenderer = indicatorTransform.GetComponent<LineRenderer>();
|
||||
if (_attackRangeRenderer == null)
|
||||
{
|
||||
_attackRangeRenderer = indicatorTransform.gameObject.AddComponent<LineRenderer>();
|
||||
}
|
||||
}
|
||||
|
||||
_attackRangeRenderer.useWorldSpace = false;
|
||||
_attackRangeRenderer.loop = true;
|
||||
_attackRangeRenderer.positionCount = Mathf.Max(12, _attackRangeSegments);
|
||||
_attackRangeRenderer.widthMultiplier = _attackRangeLineWidth;
|
||||
_attackRangeRenderer.startColor = _attackRangeColor;
|
||||
_attackRangeRenderer.endColor = _attackRangeColor;
|
||||
_attackRangeRenderer.numCapVertices = 4;
|
||||
_attackRangeRenderer.numCornerVertices = 4;
|
||||
_attackRangeRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
||||
_attackRangeRenderer.receiveShadows = false;
|
||||
_attackRangeRenderer.allowOcclusionWhenDynamic = false;
|
||||
_attackRangeRenderer.enabled = false;
|
||||
|
||||
if (_attackRangeSharedMaterial == null)
|
||||
{
|
||||
Shader lineShader = Shader.Find("Sprites/Default");
|
||||
if (lineShader != null)
|
||||
{
|
||||
_attackRangeSharedMaterial = new Material(lineShader);
|
||||
}
|
||||
}
|
||||
|
||||
if (_attackRangeSharedMaterial != null)
|
||||
{
|
||||
_attackRangeRenderer.sharedMaterial = _attackRangeSharedMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
private void RebuildAttackRangeGeometry()
|
||||
{
|
||||
if (_attackRangeRenderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int segmentCount = Mathf.Max(12, _attackRangeSegments);
|
||||
_attackRangeRenderer.positionCount = segmentCount;
|
||||
|
||||
float stepAngle = Mathf.PI * 2f / segmentCount;
|
||||
for (int i = 0; i < segmentCount; i++)
|
||||
{
|
||||
float angle = stepAngle * i;
|
||||
float x = Mathf.Cos(angle) * _attackRange;
|
||||
float y = Mathf.Sin(angle) * _attackRange;
|
||||
_attackRangeRenderer.SetPosition(i, new Vector3(x, y, _attackRangeZOffset));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user