360 lines
12 KiB
C#
360 lines
12 KiB
C#
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));
|
|
}
|
|
}
|
|
}
|
|
}
|