// Assets/_Project/Scripts/Combat/TeslaArcVisual.cs using System.Collections.Generic; using UnityEngine; namespace TD.Combat { /// /// Local-only electrical-arc visual for /// towers (Tesla Coil). Subscribes to and draws a /// jagged line from the tower's emitter point to every enemy hit, flashed for a moment. /// /// /// Pure visualization — runs on every peer from the replicated fire broadcast; nothing /// here is networked. Arcs are pooled s reused across ticks. /// [RequireComponent(typeof(TowerCombat))] public class TeslaArcVisual : MonoBehaviour { [Tooltip("Point the arcs originate from — the central top of the coil. " + "If unset, falls back to one unit above the tower root.")] [SerializeField] private Transform emitter; [Tooltip("Material for the arc lines. Assign an unlit/additive material for a glow. " + "If unset, a fallback Sprites/Default material is created at runtime.")] [SerializeField] private Material arcMaterial; [SerializeField] private Color arcColor = new Color(0.6f, 0.85f, 1f, 1f); [SerializeField] private float arcWidth = 0.06f; [Tooltip("How long each arc flash stays visible, in seconds.")] [SerializeField] private float arcDuration = 0.12f; [Tooltip("Number of points per arc. More = more jagged detail.")] [SerializeField] private int segmentsPerArc = 6; [Tooltip("Random offset applied to interior arc points for the lightning look.")] [SerializeField] private float jitter = 0.2f; private TowerCombat combat; private readonly List pool = new List(); private float hideAt = -1f; private void Awake() => combat = GetComponent(); private void OnEnable() { if (combat != null) combat.OnAreaFired += HandleAreaFired; } private void OnDisable() { if (combat != null) combat.OnAreaFired -= HandleAreaFired; } private void Update() { if (hideAt >= 0f && Time.time >= hideAt) { for (int i = 0; i < pool.Count; i++) pool[i].enabled = false; hideAt = -1f; } } private void HandleAreaFired(Vector3[] positions) { if (positions == null) return; Vector3 origin = emitter != null ? emitter.position : transform.position + Vector3.up; for (int i = 0; i < positions.Length; i++) { var lr = GetLine(i); DrawArc(lr, origin, positions[i]); lr.enabled = true; } // Disable any leftover lines from a previous, larger tick. for (int i = positions.Length; i < pool.Count; i++) pool[i].enabled = false; hideAt = Time.time + arcDuration; } private LineRenderer GetLine(int index) { while (pool.Count <= index) { var go = new GameObject($"Arc_{pool.Count}"); go.transform.SetParent(transform, worldPositionStays: false); var lr = go.AddComponent(); lr.useWorldSpace = true; lr.widthMultiplier = arcWidth; lr.numCapVertices = 2; lr.textureMode = LineTextureMode.Stretch; lr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; lr.material = arcMaterial != null ? arcMaterial : new Material(Shader.Find("Sprites/Default")); lr.startColor = lr.endColor = arcColor; lr.enabled = false; pool.Add(lr); } return pool[index]; } private void DrawArc(LineRenderer lr, Vector3 a, Vector3 b) { int n = Mathf.Max(2, segmentsPerArc); lr.positionCount = n; for (int s = 0; s < n; s++) { float t = s / (float)(n - 1); Vector3 p = Vector3.Lerp(a, b, t); // Jitter interior points only — endpoints stay anchored to the coil and enemy. if (s != 0 && s != n - 1) p += Random.insideUnitSphere * jitter; lr.SetPosition(s, p); } } } }