266 lines
8.6 KiB
C#
266 lines
8.6 KiB
C#
using GeometryTD.Definition;
|
|
using GeometryTD.Entity;
|
|
using UnityEngine;
|
|
|
|
namespace Components
|
|
{
|
|
[DisallowMultipleComponent]
|
|
public class DefenseTowerController : MonoBehaviour
|
|
{
|
|
private const string AttackRangeIndicatorObjectName = "AttackRangeIndicator";
|
|
private static Material s_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 bool _autoUpdate = true;
|
|
[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;
|
|
|
|
public Transform CurrentTarget => _currentTarget;
|
|
|
|
private void Awake()
|
|
{
|
|
ResolveComponents();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!_autoUpdate)
|
|
{
|
|
return;
|
|
}
|
|
|
|
OnUpdate(Time.deltaTime);
|
|
}
|
|
|
|
public void OnInit(DefenseTowerStatsData stats)
|
|
{
|
|
ResolveComponents();
|
|
if (stats == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_muzzleComp?.OnInit(stats.AttackDamage, stats.AttackMethodType);
|
|
_bearingComp?.OnInit(stats.RotateSpeed, stats.AttackRange);
|
|
_baseComp?.OnInit(stats.AttackSpeed, stats.AttackPropertyType);
|
|
SetAttackRange(stats.AttackRange);
|
|
SetAttackRangeVisible(false);
|
|
_currentTarget = null;
|
|
_retargetTimer = 0f;
|
|
}
|
|
|
|
public void OnReset()
|
|
{
|
|
SetAttackRangeVisible(false);
|
|
_currentTarget = null;
|
|
_retargetTimer = 0f;
|
|
_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 SetAutoUpdate(bool autoUpdate)
|
|
{
|
|
_autoUpdate = autoUpdate;
|
|
}
|
|
|
|
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;
|
|
for (int i = 0; i < EnemyEntity.ActiveEnemies.Count; i++)
|
|
{
|
|
EnemyEntity enemy = EnemyEntity.ActiveEnemies[i];
|
|
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 ResolveComponents()
|
|
{
|
|
if (_muzzleComp == null)
|
|
{
|
|
_muzzleComp = GetComponent<ShooterMuzzleComp>();
|
|
}
|
|
|
|
if (_bearingComp == null)
|
|
{
|
|
_bearingComp = GetComponent<BasicBearingComp>();
|
|
}
|
|
|
|
if (_baseComp == null)
|
|
{
|
|
_baseComp = GetComponent<BasicBaseComp>();
|
|
}
|
|
|
|
EnsureAttackRangeRenderer();
|
|
}
|
|
|
|
private bool HasCoreComponents()
|
|
{
|
|
return _muzzleComp != null && _bearingComp != null && _baseComp != null;
|
|
}
|
|
|
|
private static bool IsTargetValid(Transform target)
|
|
{
|
|
return target != null && target.gameObject.activeInHierarchy;
|
|
}
|
|
|
|
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 (s_AttackRangeSharedMaterial == null)
|
|
{
|
|
Shader lineShader = Shader.Find("Sprites/Default");
|
|
if (lineShader != null)
|
|
{
|
|
s_AttackRangeSharedMaterial = new Material(lineShader);
|
|
}
|
|
}
|
|
|
|
if (s_AttackRangeSharedMaterial != null)
|
|
{
|
|
_attackRangeRenderer.sharedMaterial = s_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));
|
|
}
|
|
}
|
|
}
|
|
}
|