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(); 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(); _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(); } if (_bearingComp == null) { _bearingComp = GetComponent(); } if (_baseComp == null) { _baseComp = GetComponent(); } 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[] 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(); } 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(); if (_attackRangeRenderer == null) { _attackRangeRenderer = indicatorTransform.gameObject.AddComponent(); } } _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)); } } } }