// Assets/_Project/Scripts/VFX/FallFromSkyVisual.cs using System.Collections; using UnityEngine; namespace TD.VFX { /// /// Animates this object falling from the sky down to its own spawn position. Intended for /// a one-shot placeholder VFX prefab (e.g. FireballSpellDefinition.impactVfxPrefab) /// that gets instantiated already at the correct landing point — on Start, it snaps /// itself up by and animates back down over /// seconds. /// /// /// Mirrors 's fall coroutine, minus the /// TowerInstance coupling — this is a plain local one-shot visual, not tied to any /// networked gameplay state, so it can run from Start() instead of waiting on a /// spawn event. /// public class FallFromSkyVisual : MonoBehaviour { [Tooltip("How high above the landing point this object starts.")] [SerializeField] private float dropHeight = 20f; [Tooltip("Seconds for the fall animation to complete.")] [Min(0.01f)] [SerializeField] private float duration = 0.5f; [Tooltip("Eases the fall over Duration. X = normalized time, Y = normalized fraction " + "of DropHeight already descended (0 = still at drop height, 1 = landed).")] [SerializeField] private AnimationCurve fallCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f); [Header("Angle (optional — 0 = straight down)")] [Tooltip("Angle from vertical, in degrees. 0 = falls straight down. Higher values = " + "a shallower, more meteor-like diagonal approach. Capped below 90 — at 90 the " + "object would never actually descend.")] [Range(0f, 80f)] [SerializeField] private float fallAngleDegrees = 0f; [Tooltip("Horizontal direction the object travels as it falls (only used when " + "FallAngleDegrees > 0). Magnitude is ignored — normalized automatically.")] [SerializeField] private Vector3 fallDirection = Vector3.forward; private void Start() { StartCoroutine(AnimateDrop()); } private IEnumerator AnimateDrop() { Vector3 landedPosition = transform.position; // already correct — spawned at the target point // Split dropHeight into a vertical component (unchanged from the straight-down case) // plus a horizontal component derived from the angle via basic trig: at angle θ from // vertical, horizontal distance = dropHeight * tan(θ). At θ = 0 this is 0, so the // whole angle path collapses back to the original straight-down offset. float angleRad = fallAngleDegrees * Mathf.Deg2Rad; float horizontalDistance = dropHeight * Mathf.Tan(angleRad); // Only the horizontal (XZ) part of fallDirection matters — a Y component would just // be double-counting dropHeight, so it's dropped before normalizing. Vector3 flatDirection = new Vector3(fallDirection.x, 0f, fallDirection.z); Vector3 horizontalDir = flatDirection.sqrMagnitude > 0.0001f ? flatDirection.normalized : Vector3.zero; Vector3 startOffset = Vector3.up * dropHeight - horizontalDir * horizontalDistance; transform.position = landedPosition + startOffset; float t = 0f; while (t < duration) { t += Time.deltaTime; float u = fallCurve.Evaluate(Mathf.Clamp01(t / duration)); transform.position = landedPosition + startOffset * (1f - u); yield return null; } transform.position = landedPosition; // snap exactly, avoid float drift } } }