// Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowZoneRuntime.cs using UnityEngine; using TD.Core; namespace TD.Gameplay.BuilderSpells { /// /// Server-only runtime object for a Slow Area cast. Re-scans its radius every frame for the /// rest of the zone's lifetime, continuously refreshing the Cold status on whatever enemies /// currently overlap it. /// /// /// Zone, not a snapshot. Replaces 's old /// single OverlapSphereNonAlloc call at cast time, which slowed whoever was standing /// there for a fixed duration regardless of whether they walked out. This ticks continuously /// instead, so only enemies currently inside the radius are ever affected. /// /// Refresh, not enter/exit events. Each tick re-applies /// with a short fixed window (), /// not the zone's remaining lifetime. An enemy that walks out of the zone simply stops being /// refreshed and the slow expires on its own seconds later — /// no explicit exit detection needed. /// /// Not a NetworkObject. It only ever runs where runs (the server), and it never needs to exist /// on clients — the zone's visual is spawned independently on every peer via /// . /// public class SlowZoneRuntime : MonoBehaviour { // Comfortably longer than one frame so a single missed tick doesn't let the slow // visibly drop while an enemy is still inside, but short enough that leaving the // zone reads as an almost-immediate loss of the slow. private const float RefreshWindow = 0.35f; private static readonly Collider[] s_overlapBuffer = new Collider[64]; private float radius; private float slowFactor; private float remainingLifetime; private LayerMask enemyLayerMask; private PlayerSlot owner; /// Positions and configures the zone. Call immediately after AddComponent. public void Initialize(Vector3 position, float radius, float slowFactor, float lifetime, LayerMask enemyLayerMask, PlayerSlot owner) { transform.position = position; this.radius = radius; this.slowFactor = slowFactor; this.remainingLifetime = lifetime; this.enemyLayerMask = enemyLayerMask; this.owner = owner; } private void Update() { remainingLifetime -= Time.deltaTime; if (remainingLifetime <= 0f) { Destroy(gameObject); return; } int count = Physics.OverlapSphereNonAlloc( transform.position, radius, s_overlapBuffer, enemyLayerMask); for (int i = 0; i < count; i++) { var enemyHealth = s_overlapBuffer[i].GetComponent(); if (enemyHealth == null || enemyHealth.IsDead) continue; var enemyStatus = s_overlapBuffer[i].GetComponent(); if (enemyStatus == null) continue; enemyStatus.ApplyEffect(DamageType.Cold, slowFactor, RefreshWindow, owner); } } } }