From 35ea90f71bbee8a28f95e69f23cdc2f5b500ff46 Mon Sep 17 00:00:00 2001 From: Ian Woods Date: Tue, 21 Jul 2026 20:56:55 -0700 Subject: [PATCH] fix slow area spell area of effect --- .../BuilderSpells/SlowAreaSpellDefinition.cs | 31 ++++---- .../Gameplay/BuilderSpells/SlowAreaZone.cs | 77 +++++++++++++++++++ .../BuilderSpells/SlowAreaZone.cs.meta | 2 + 3 files changed, 92 insertions(+), 18 deletions(-) create mode 100644 Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowAreaZone.cs create mode 100644 Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowAreaZone.cs.meta diff --git a/Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowAreaSpellDefinition.cs b/Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowAreaSpellDefinition.cs index bd4cd8b..4885848 100644 --- a/Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowAreaSpellDefinition.cs +++ b/Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowAreaSpellDefinition.cs @@ -14,6 +14,12 @@ namespace TD.Gameplay.BuilderSpells /// while aiming, and this spell resolves against that same radius (no separate field). /// Reuses the existing slow/DoT system () rather than /// introducing a new status mechanism. + /// + /// The cast spawns a that lingers for + /// and keeps re-applying the slow to anything standing in + /// the radius, so enemies walking through the area get slowed rather than only whatever + /// happened to be standing there at the exact instant of cast. + /// /// [CreateAssetMenu(fileName = "SlowAreaSpell", menuName = "TD/Builder Spells/Slow Area")] public class SlowAreaSpellDefinition : BuilderSpellDefinition @@ -35,7 +41,9 @@ namespace TD.Gameplay.BuilderSpells [Range(0f, 1f)] public float SlowFactor = 0.5f; - [Tooltip("Seconds the slow lasts. Re-casting on an already-slowed enemy refreshes it.")] + [Tooltip("Seconds the slow lasts on an affected enemy, and how long the area itself " + + "lingers, re-slowing anything that walks through it. Re-casting on an " + + "already-slowed enemy refreshes it.")] [Min(0f)] public float EffectDuration = 3f; @@ -43,25 +51,12 @@ namespace TD.Gameplay.BuilderSpells { PlayerSlot owner = PlayerMatchState.SlotForClient(clientId); - int count = Physics.OverlapSphereNonAlloc( - targetPoint, Mathf.Max(Radius, 0.01f), s_overlapBuffer, enemyLayerMask); + SlowAreaZone.Spawn(targetPoint, Mathf.Max(Radius, 0.01f), SlowFactor, + EffectDuration, enemyLayerMask, owner); - bool hitAny = false; - 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, EffectDuration, owner); - hitAny = true; - } - - return hitAny; + return true; } - + public override void ClientSpawnVfx(Vector3 targetPoint) { if (areaVfxPrefab != null) diff --git a/Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowAreaZone.cs b/Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowAreaZone.cs new file mode 100644 index 0000000..de1c76a --- /dev/null +++ b/Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowAreaZone.cs @@ -0,0 +1,77 @@ +// Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowAreaZone.cs +using UnityEngine; +using TD.Core; + +namespace TD.Gameplay.BuilderSpells +{ + /// + /// Server-only, non-networked lingering area spawned by + /// . Re-scans its radius on an + /// interval for its lifetime and keeps refreshing the slow on anything standing + /// inside, so enemies get slowed as they walk through rather than only at the + /// instant of cast. + /// + /// + /// Never spawned as a — it runs + /// server-side only, and the slow it applies already reaches clients through + /// 's own NetworkVariable, the same as an instant hit. + /// + public class SlowAreaZone : MonoBehaviour + { + private const float TickInterval = 0.2f; + + private float radius; + private float slowFactor; + private float effectDuration; + private LayerMask enemyLayerMask; + private PlayerSlot owner; + + private readonly Collider[] overlapBuffer = new Collider[64]; + private float tickTimer; + + public static SlowAreaZone Spawn(Vector3 position, float radius, float slowFactor, + float effectDuration, LayerMask enemyLayerMask, PlayerSlot owner) + { + var zoneObject = new GameObject("SlowAreaZone (Server)"); + zoneObject.transform.position = position; + + var zone = zoneObject.AddComponent(); + zone.radius = radius; + zone.slowFactor = slowFactor; + zone.effectDuration = effectDuration; + zone.enemyLayerMask = enemyLayerMask; + zone.owner = owner; + zone.ApplyToOverlapping(); + + Destroy(zoneObject, effectDuration); + return zone; + } + + private void Update() + { + tickTimer -= Time.deltaTime; + if (tickTimer > 0f) return; + + ApplyToOverlapping(); + } + + private void ApplyToOverlapping() + { + tickTimer = TickInterval; + + int count = Physics.OverlapSphereNonAlloc( + transform.position, radius, overlapBuffer, enemyLayerMask); + + for (int i = 0; i < count; i++) + { + var enemyHealth = overlapBuffer[i].GetComponent(); + if (enemyHealth == null || enemyHealth.IsDead) continue; + + var enemyStatus = overlapBuffer[i].GetComponent(); + if (enemyStatus == null) continue; + + enemyStatus.ApplyEffect(DamageType.Cold, slowFactor, effectDuration, owner); + } + } + } +} diff --git a/Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowAreaZone.cs.meta b/Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowAreaZone.cs.meta new file mode 100644 index 0000000..7c880a9 --- /dev/null +++ b/Assets/_Project/Scripts/Gameplay/BuilderSpells/SlowAreaZone.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7b00a1f57e8be13cd91c0ed5f515bfc8 \ No newline at end of file