diff --git a/Assets/GameMain/Entities/TowerEntity.prefab b/Assets/GameMain/Entities/TowerEntity.prefab index 6186fbd..ca6c11c 100644 --- a/Assets/GameMain/Entities/TowerEntity.prefab +++ b/Assets/GameMain/Entities/TowerEntity.prefab @@ -98,10 +98,10 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 20a2c131403122146a41148cb72fcd43, type: 3} m_Name: m_EditorClassIdentifier: - _attackDamage: 10 + _attackDamage: 100 _attackMethodType: 1 _bulletTypeId: 501 - _muzzlePoint: {fileID: 0} + _muzzlePoint: {fileID: 7637292285124107611} _bulletSpeed: 12 --- !u!1 &1221576993898367501 GameObject: @@ -114,6 +114,7 @@ GameObject: - component: {fileID: 6791423131335728073} - component: {fileID: 8183383920109690380} - component: {fileID: 3255949411223456789} + - component: {fileID: 4014432302095443276} m_Layer: 0 m_Name: TowerEntity m_TagString: Untagged @@ -155,6 +156,11 @@ MonoBehaviour: _scanOrigin: {fileID: 6791423131335728073} _retargetInterval: 0.1 _autoUpdate: 0 + _attackRangeRenderer: {fileID: 4014432302095443276} + _attackRangeSegments: 64 + _attackRangeLineWidth: 0.08 + _attackRangeColor: {r: 0.1, g: 1, b: 0.4, a: 0.8} + _attackRangeZOffset: -0.01 --- !u!114 &3255949411223456789 MonoBehaviour: m_ObjectHideFlags: 0 @@ -167,6 +173,110 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d87f56efd9024709a3baf8ef8a6f4fd3, type: 3} m_Name: m_EditorClassIdentifier: +--- !u!120 &4014432302095443276 +LineRenderer: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1221576993898367501} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 0 + m_LightProbeUsage: 0 + m_ReflectionProbeUsage: 0 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_Positions: + - {x: 0, y: 0, z: 0} + - {x: 0, y: 0, z: 1} + m_Parameters: + serializedVersion: 3 + widthMultiplier: 1 + widthCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + colorGradient: + serializedVersion: 2 + key0: {r: 1, g: 1, b: 1, a: 1} + key1: {r: 1, g: 1, b: 1, a: 1} + key2: {r: 0, g: 0, b: 0, a: 0} + key3: {r: 0, g: 0, b: 0, a: 0} + key4: {r: 0, g: 0, b: 0, a: 0} + key5: {r: 0, g: 0, b: 0, a: 0} + key6: {r: 0, g: 0, b: 0, a: 0} + key7: {r: 0, g: 0, b: 0, a: 0} + ctime0: 0 + ctime1: 65535 + ctime2: 0 + ctime3: 0 + ctime4: 0 + ctime5: 0 + ctime6: 0 + ctime7: 0 + atime0: 0 + atime1: 65535 + atime2: 0 + atime3: 0 + atime4: 0 + atime5: 0 + atime6: 0 + atime7: 0 + m_Mode: 0 + m_ColorSpace: -1 + m_NumColorKeys: 2 + m_NumAlphaKeys: 2 + numCornerVertices: 0 + numCapVertices: 0 + alignment: 0 + textureMode: 0 + textureScale: {x: 1, y: 1} + shadowBias: 0.5 + generateLightingData: 0 + m_MaskInteraction: 0 + m_UseWorldSpace: 1 + m_Loop: 0 + m_ApplyActiveColorSpace: 1 --- !u!1 &2017874305906296486 GameObject: m_ObjectHideFlags: 0 @@ -269,8 +379,7 @@ MonoBehaviour: _rotateSpeed: 180 _attackRange: 5 _aimToleranceAngle: 2 - _rotateRoot: {fileID: 0} - _yawOnly: 1 + _rotateRoot: {fileID: 5517541809701307552} --- !u!1 &4867507345079921359 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/GameMain/Scripts/Components/BasicBearingComp.cs b/Assets/GameMain/Scripts/Components/BasicBearingComp.cs index d533e67..e75cf22 100644 --- a/Assets/GameMain/Scripts/Components/BasicBearingComp.cs +++ b/Assets/GameMain/Scripts/Components/BasicBearingComp.cs @@ -9,7 +9,6 @@ namespace Components [SerializeField] [Min(0.1f)] private float _attackRange = 5f; [SerializeField] [Min(0.1f)] private float _aimToleranceAngle = 2f; [SerializeField] private Transform _rotateRoot; - [SerializeField] private bool _yawOnly = true; public float RotateSpeed => _rotateSpeed; public float AttackRange => _attackRange; @@ -47,20 +46,19 @@ namespace Components } Transform rotateRoot = _rotateRoot != null ? _rotateRoot : transform; - Vector3 toTarget = target.position - rotateRoot.position; - if (_yawOnly) - { - toTarget.y = 0f; - } + Vector2 toTarget = (Vector2)(target.position - rotateRoot.position); if (toTarget.sqrMagnitude <= Mathf.Epsilon) { return true; } - Quaternion targetRotation = Quaternion.LookRotation(toTarget.normalized, Vector3.up); - float rotateAngle = _rotateSpeed * Mathf.Max(0f, deltaTime); - rotateRoot.rotation = Quaternion.RotateTowards(rotateRoot.rotation, targetRotation, rotateAngle); + 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); } @@ -72,18 +70,16 @@ namespace Components } Transform rotateRoot = _rotateRoot != null ? _rotateRoot : transform; - Vector3 toTarget = target.position - rotateRoot.position; - if (_yawOnly) - { - toTarget.y = 0f; - } + Vector2 toTarget = (Vector2)(target.position - rotateRoot.position); if (toTarget.sqrMagnitude <= Mathf.Epsilon) { return true; } - float angle = Vector3.Angle(rotateRoot.forward, toTarget.normalized); + float targetZAngle = Vector2.SignedAngle(Vector2.up, toTarget); + float currentZAngle = rotateRoot.eulerAngles.z; + float angle = Mathf.Abs(Mathf.DeltaAngle(currentZAngle, targetZAngle)); return angle <= _aimToleranceAngle; } diff --git a/Assets/GameMain/Scripts/Components/DefenseTowerController.cs b/Assets/GameMain/Scripts/Components/DefenseTowerController.cs index f50210c..123bbc7 100644 --- a/Assets/GameMain/Scripts/Components/DefenseTowerController.cs +++ b/Assets/GameMain/Scripts/Components/DefenseTowerController.cs @@ -7,15 +7,24 @@ 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; @@ -45,12 +54,15 @@ namespace Components _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(); @@ -58,6 +70,22 @@ namespace Components _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; @@ -153,6 +181,8 @@ namespace Components { _baseComp = GetComponent(); } + + EnsureAttackRangeRenderer(); } private bool HasCoreComponents() @@ -164,5 +194,72 @@ namespace Components { 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(); + 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 (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)); + } + } } } diff --git a/Assets/GameMain/Scripts/Components/ShooterBullet.cs b/Assets/GameMain/Scripts/Components/ShooterBullet.cs index a924fcc..122d44d 100644 --- a/Assets/GameMain/Scripts/Components/ShooterBullet.cs +++ b/Assets/GameMain/Scripts/Components/ShooterBullet.cs @@ -30,6 +30,12 @@ namespace Components _lifetime = 0f; _despawnRequested = false; _isRunning = _target != null; + if (_isRunning) + { + Vector2 initialDirection = (Vector2)(_target.position - transform.position); + RotateToDirection(initialDirection); + } + if (!_isRunning) { _despawnRequested = true; @@ -63,7 +69,8 @@ namespace Components return; } - Vector3 toTarget = _target.position - transform.position; + Vector3 currentPosition = transform.position; + Vector2 toTarget = (Vector2)(_target.position - currentPosition); float hitDistanceSquared = _hitDistance * _hitDistance; if (toTarget.sqrMagnitude <= hitDistanceSquared) { @@ -71,17 +78,18 @@ namespace Components return; } - Vector3 forward = toTarget.normalized; + Vector2 direction = toTarget.normalized; + RotateToDirection(direction); float moveDistance = _speed * Mathf.Max(0f, deltaTime); if (moveDistance * moveDistance >= toTarget.sqrMagnitude) { - transform.position = _target.position; + Vector3 targetPosition = _target.position; + transform.position = new Vector3(targetPosition.x, targetPosition.y, currentPosition.z); HitTarget(); return; } - transform.position += forward * moveDistance; - transform.forward = forward; + transform.position += (Vector3)(direction * moveDistance); } public bool TryConsumeDespawnRequest() @@ -105,9 +113,9 @@ namespace Components } MonoBehaviour[] components = _target.GetComponentsInParent(); - for (int i = 0; i < components.Length; i++) + foreach (var mono in components) { - if (components[i] is IDamageReceiver damageReceiver) + if (mono is IDamageReceiver damageReceiver) { damageReceiver.TakeDamage(_damage, _attackPropertyType); break; @@ -117,5 +125,16 @@ namespace Components _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); + } } } diff --git a/Assets/GameMain/Scripts/Components/ShooterMuzzleComp.cs b/Assets/GameMain/Scripts/Components/ShooterMuzzleComp.cs index 97c9845..a9361da 100644 --- a/Assets/GameMain/Scripts/Components/ShooterMuzzleComp.cs +++ b/Assets/GameMain/Scripts/Components/ShooterMuzzleComp.cs @@ -8,7 +8,7 @@ namespace Components [DisallowMultipleComponent] public class ShooterMuzzleComp : MonoBehaviour { - [SerializeField] [Min(1f)] private int _attackDamage = 10; + [SerializeField] [Min(1f)] private int _attackDamage = 100; [SerializeField] private AttackMethodType _attackMethodType = AttackMethodType.NormalBullet; [SerializeField] [Min(1)] private int _bulletTypeId = 501; [SerializeField] private Transform _muzzlePoint; @@ -54,7 +54,6 @@ namespace Components bulletEntityId, _bulletTypeId, spawnPoint.position, - spawnPoint.rotation, target, _attackDamage, _bulletSpeed, diff --git a/Assets/GameMain/Scripts/Entity/EntityData/BulletData.cs b/Assets/GameMain/Scripts/Entity/EntityData/BulletData.cs index afb7648..e5be59d 100644 --- a/Assets/GameMain/Scripts/Entity/EntityData/BulletData.cs +++ b/Assets/GameMain/Scripts/Entity/EntityData/BulletData.cs @@ -17,7 +17,6 @@ namespace GeometryTD.Entity.EntityData int entityId, int typeId, Vector3 position, - Quaternion rotation, Transform target, int damage, float speed, @@ -25,7 +24,7 @@ namespace GeometryTD.Entity.EntityData float maxLifetime = 3f) : base(entityId, typeId) { Position = position; - Rotation = rotation; + _target = target; _damage = damage; _speed = speed; diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/BulletEntity.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/BulletEntity.cs index d6f074c..ace1df6 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/BulletEntity.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/BulletEntity.cs @@ -1,5 +1,6 @@ using Components; using GeometryTD.Entity.EntityData; +using UnityEngine; using UnityGameFramework.Runtime; namespace GeometryTD.Entity @@ -36,6 +37,7 @@ namespace GeometryTD.Entity return; } + ConstrainToZRotation(); _shooterBullet.OnShow(bulletData); if (_shooterBullet.TryConsumeDespawnRequest()) { @@ -64,5 +66,11 @@ namespace GeometryTD.Entity _shooterBullet?.OnReset(); base.OnHide(isShutdown, userData); } + + private void ConstrainToZRotation() + { + Vector3 localEulerAngles = CachedTransform.localEulerAngles; + CachedTransform.localRotation = Quaternion.Euler(0f, 0f, localEulerAngles.z); + } } } diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/DefenseTowerEntity.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/DefenseTowerEntity.cs index 1369080..18505dd 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/DefenseTowerEntity.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/DefenseTowerEntity.cs @@ -8,6 +8,11 @@ namespace GeometryTD.Entity { private DefenseTowerController _towerController; + public void SetAttackRangeVisible(bool visible) + { + _towerController?.SetAttackRangeVisible(visible); + } + protected override void OnInit(object userData) { base.OnInit(userData); @@ -48,6 +53,7 @@ namespace GeometryTD.Entity protected override void OnHide(bool isShutdown, object userData) { + _towerController?.SetAttackRangeVisible(false); _towerController?.OnReset(); base.OnHide(isShutdown, userData); } diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/EnemyEntity.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/EnemyEntity.cs index a066896..bd0c9b2 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/EnemyEntity.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/EnemyEntity.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using GeometryTD.Definition; using GeometryTD.Entity.EntityData; using UnityEngine; +using UnityGameFramework.Runtime; namespace GeometryTD.Entity { @@ -116,11 +117,14 @@ namespace GeometryTD.Entity _currentHealth = Mathf.Max(0, _currentHealth - damage); if (_maxHealth > 0) { - GameEntry.HPBar?.ShowHPBar(this, (float)previousHealth / _maxHealth, (float)_currentHealth / _maxHealth); + GameEntry.HPBar?.ShowHPBar(this, (float)previousHealth / _maxHealth, + (float)_currentHealth / _maxHealth); + Log.Info($"ShowBar: {_currentHealth}/{_maxHealth}"); } if (_currentHealth <= 0) { + Log.Info("Enemy Dead"); _killedEnemyEntityIds.Add(Id); RequestDespawn(); } @@ -192,4 +196,4 @@ namespace GeometryTD.Entity GameEntry.Entity.HideEntity(Entity); } } -} +} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/EntityBase.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/EntityBase.cs index b2e9962..8ae9ef6 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/EntityBase.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/EntityBase.cs @@ -55,7 +55,6 @@ namespace GeometryTD.Entity Name = Utility.Text.Format("[Entity {0}]", Id); CachedTransform.localPosition = _entityData.Position; CachedTransform.localRotation = _entityData.Rotation; - CachedTransform.localScale = Vector3.one; } #if UNITY_2017_3_OR_NEWER diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/MapEntity.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/MapEntity.cs index 92b18d4..5388b43 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/MapEntity.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/MapEntity.cs @@ -46,6 +46,7 @@ namespace GeometryTD.Entity private bool _hasSelectedFoundationCell; private Vector3Int _selectedFoundationCell; private int _selectedTowerEntityId; + private int _attackRangeVisibleTowerEntityId; public IReadOnlyList PathCells => _pathCells; public IReadOnlyList FoundationCells => _foundationCells; @@ -486,6 +487,8 @@ namespace GeometryTD.Entity ClearSelectedObject(); break; } + + UpdateTowerAttackRangeDisplay(_selectedTowerEntityId); } private bool TryBuildTower(int buildIndex) @@ -517,6 +520,7 @@ namespace GeometryTD.Entity _foundationCellByTowerEntityId[towerEntityId] = _selectedFoundationCell; _towerStatsByEntityId[towerEntityId] = CloneTowerStats(towerStats); _selectedTowerEntityId = towerEntityId; + UpdateTowerAttackRangeDisplay(_selectedTowerEntityId); return true; } @@ -552,6 +556,11 @@ namespace GeometryTD.Entity _foundationCellByTowerEntityId[fallbackTowerEntityId] = foundationCell; _towerStatsByEntityId[fallbackTowerEntityId] = CloneTowerStats(oldStats); _selectedTowerEntityId = fallbackTowerEntityId; + UpdateTowerAttackRangeDisplay(_selectedTowerEntityId); + } + else + { + UpdateTowerAttackRangeDisplay(0); } GameEntry.CombatNode?.AddCoin(upgradeCost); @@ -564,6 +573,7 @@ namespace GeometryTD.Entity _hasSelectedFoundationCell = true; _selectedFoundationCell = foundationCell; _selectedTowerEntityId = newTowerEntityId; + UpdateTowerAttackRangeDisplay(_selectedTowerEntityId); return true; } @@ -664,11 +674,54 @@ namespace GeometryTD.Entity private void ClearSelectedObject() { + UpdateTowerAttackRangeDisplay(0); _hasSelectedFoundationCell = false; _selectedFoundationCell = default; _selectedTowerEntityId = 0; } + private void UpdateTowerAttackRangeDisplay(int towerEntityId) + { + if (_attackRangeVisibleTowerEntityId != 0 && _attackRangeVisibleTowerEntityId != towerEntityId) + { + SetTowerAttackRangeVisible(_attackRangeVisibleTowerEntityId, false); + _attackRangeVisibleTowerEntityId = 0; + } + + if (towerEntityId == 0) + { + if (_attackRangeVisibleTowerEntityId != 0) + { + SetTowerAttackRangeVisible(_attackRangeVisibleTowerEntityId, false); + _attackRangeVisibleTowerEntityId = 0; + } + + return; + } + + if (SetTowerAttackRangeVisible(towerEntityId, true)) + { + _attackRangeVisibleTowerEntityId = towerEntityId; + } + } + + private static bool SetTowerAttackRangeVisible(int towerEntityId, bool visible) + { + if (towerEntityId == 0 || GameEntry.Entity == null) + { + return false; + } + + EntityBase gameEntity = GameEntry.Entity.GetGameEntity(towerEntityId); + if (gameEntity is not DefenseTowerEntity towerEntity) + { + return false; + } + + towerEntity.SetAttackRangeVisible(visible); + return true; + } + private int GetBuildTowerCost(int buildIndex) { if (_buildTowerCosts == null || buildIndex < 0 || buildIndex >= _buildTowerCosts.Length) @@ -707,7 +760,7 @@ namespace GeometryTD.Entity case 0: return new DefenseTowerStatsData { - AttackDamage = 10, + AttackDamage = 100, DamageRandomRate = 0f, RotateSpeed = 200f, AttackRange = 4.5f, @@ -755,7 +808,7 @@ namespace GeometryTD.Entity default: return new DefenseTowerStatsData { - AttackDamage = 10, + AttackDamage = 100, DamageRandomRate = 0f, RotateSpeed = 180f, AttackRange = 5f,