geometry-tower-defense/Assets/GameMain/Scripts/Components/DefenseTowerController.cs

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));
}
}
}
}