118 lines
4.5 KiB
C#
118 lines
4.5 KiB
C#
// Assets/_Project/Scripts/Combat/TeslaArcVisual.cs
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace TD.Combat
|
|
{
|
|
/// <summary>
|
|
/// Local-only electrical-arc visual for <see cref="TD.Core.TargetType.AllInRange"/>
|
|
/// towers (Tesla Coil). Subscribes to <see cref="TowerCombat.OnAreaFired"/> and draws a
|
|
/// jagged line from the tower's emitter point to every enemy hit, flashed for a moment.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Pure visualization — runs on every peer from the replicated fire broadcast; nothing
|
|
/// here is networked. Arcs are pooled <see cref="LineRenderer"/>s reused across ticks.
|
|
/// </remarks>
|
|
[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<LineRenderer> pool = new List<LineRenderer>();
|
|
private float hideAt = -1f;
|
|
|
|
private void Awake() => combat = GetComponent<TowerCombat>();
|
|
|
|
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<LineRenderer>();
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|