using System.Collections.Generic; using UnityEngine; namespace Watermelon { /// /// Abstract base class for all tween animations. /// Encapsulates lifecycle (start, pause, resume, kill, complete), easing, delay, /// progress tracking, and callback registration. /// /// Lifecycle: /// /// CreateInstantiate a concrete subclass and configure it with the fluent builder methods. /// StartCall to register the tween with and begin updating. /// Update calls and each frame. /// EndEither (applies the final value and fires callbacks) or (no final value, no callbacks). /// /// /// /// Implementing a custom tween: /// Override to interpolate the value each frame, /// to snap to the final value on completion, and /// to return false when the target object is no longer valid. /// /// public abstract class TweenCase { // Managed by TweensHolder — internal set only /// Index of this tween inside its array. -1 when inactive. public int ActiveID { get; internal set; } /// Whether the tween is currently registered and updating in a . public bool IsActive { get; internal set; } // Private: only read/written inside TweenCase private float currentDelay; private float delay; private float duration; private int updateMethodIndex; private bool isPaused; private bool isUnscaled; private bool isCompleted; private bool isKilling; private Ease.IEasingFunction easeFunction; private SimpleCallback completedCallback; private SimpleCallback terminatedCallback; private List callbackData; // Protected: written by concrete case constructors /// Optional target object. Used by in subclasses to detect destroyed Unity objects. protected GameObject parentObject; // Protected: read by concrete Invoke() implementations via Interpolate(state) /// /// Normalized progress of the tween in the range [0, 1]. /// Read by concrete implementations via . /// protected float state; // Public read-only surface /// Elapsed delay time (seconds) since the tween started. Counts up until it reaches . public float CurrentDelay => currentDelay; /// Initial delay (seconds) before the tween begins animating. public float Delay => delay; /// Normalized progress [0, 1]. Advances each frame after the delay has elapsed. public float State => state; /// Total animation duration in seconds (excluding delay). public float Duration => duration; /// Whether the tween is currently paused. Paused tweens are skipped by but remain registered. public bool IsPaused => isPaused; /// When true, the tween uses Time.unscaledDeltaTime and is unaffected by Time.timeScale. public bool IsUnscaled => isUnscaled; /// Whether the tween has finished animating (state reached 1, or was called). public bool IsCompleted => isCompleted; /// /// Whether the tween has been marked for removal but not yet physically removed from the array. /// While true, is a no-op. Cleared by after removal. /// public bool IsKilling => isKilling; /// Array index of the owning inside . public int UpdateMethodIndex => updateMethodIndex; /// Unity update loop in which this tween runs. public UpdateMethod UpdateMethod => (UpdateMethod)updateMethodIndex; /// The GameObject this tween is attached to, or null for system (non-transform) tweens. public GameObject ParentObject => parentObject; /// Initialises the tween with a easing function. public TweenCase() { SetEasing(Ease.Type.Linear); } /// Registers the tween with the global system and begins updating. Returns this for chaining. public virtual TweenCase StartTween() { Tween.AddTween(this); return this; } /// /// Called each frame to check whether the tween's target object is still valid. /// Return false to automatically kill the tween (e.g. when a GameObject has been destroyed). /// public abstract bool Validate(); // ----------------------------------------------------------- // Lifecycle // ----------------------------------------------------------- /// /// Stops and removes the tween without applying the final value or firing . /// Fires the internal terminated callback used by . /// Idempotent — safe to call multiple times. /// public TweenCase Kill() { if (!isKilling) { IsActive = false; Tween.MarkForKilling(this); isKilling = true; completedCallback = null; terminatedCallback?.Invoke(); terminatedCallback = null; } return this; } /// /// Immediately completes the tween: snaps to the final value via , /// fires the callback, then calls . /// Idempotent — subsequent calls are no-ops. /// public TweenCase Complete() { if (isKilling || isCompleted) return this; if (isPaused) isPaused = false; state = 1; isCompleted = true; DefaultComplete(); InvokeCompleteEvent(); Kill(); return this; } /// /// Restarts the tween from the beginning (resets progress and delay to 0). /// If the tween has already ended, re-registers it with the system via . /// Has no effect while is mid-processing ( is true). /// public TweenCase Restart() { if (isKilling) return this; state = 0; currentDelay = 0; isCompleted = false; isPaused = false; if (!IsActive) StartTween(); return this; } /// Pauses the tween. The tween remains registered but is skipped by the update loop until is called. public TweenCase Pause() { isPaused = true; return this; } /// Resumes a paused tween so it continues updating from where it stopped. public TweenCase Resume() { isPaused = false; return this; } // ----------------------------------------------------------- // Builder (fluent API) // ----------------------------------------------------------- /// Sets a delay (seconds) before the tween begins animating. Must be called before . public TweenCase SetDelay(float delay) { this.delay = delay; currentDelay = 0; return this; } /// /// Sets which Unity update loop drives this tween (, /// , or ). /// Must be called before . /// public TweenCase SetUpdateMethod(UpdateMethod updateMethod) { updateMethodIndex = (int)updateMethod; return this; } /// /// When true, the tween uses Time.unscaledDeltaTime and ignores Time.timeScale. /// Useful for UI animations that must run during paused gameplay. /// public TweenCase SetUnscaledMode(bool isUnscaled) { this.isUnscaled = isUnscaled; return this; } /// Sets one of the built-in easing curves from . Default is . public TweenCase SetEasing(Ease.Type ease) { easeFunction = Ease.GetFunction(ease); return this; } /// Sets a custom easing curve via an . The curve is sampled over its total time range. public TweenCase SetCurveEasing(AnimationCurve easingCurve) { easeFunction = new AnimationCurveEasingFunction(easingCurve); return this; } /// Sets a fully custom easing function implementing . public TweenCase SetCustomEasing(Ease.IEasingFunction easeFunction) { this.easeFunction = easeFunction; return this; } /// Sets the total animation duration in seconds (excluding delay). public TweenCase SetDuration(float duration) { this.duration = duration; return this; } // ----------------------------------------------------------- // Callbacks // ----------------------------------------------------------- /// /// Registers a callback invoked when the tween completes naturally or via . /// Only one callback is stored — each call replaces the previous one (last call wins). /// Not fired when the tween is stopped via . /// public TweenCase OnComplete(SimpleCallback callback) { completedCallback = callback; return this; } /// /// Registers a callback fired when the tween ends for any reason — both and . /// Used internally by . Always has at most one subscriber. /// internal TweenCase OnTerminated(SimpleCallback callback) { terminatedCallback = callback; return this; } /// /// Registers a one-shot callback fired when the normalized progress reaches (range 0–1). /// Multiple thresholds can be registered; each fires exactly once and is then discarded. /// /// Normalized time threshold in [0, 1]. /// The callback to invoke when the threshold is crossed. public TweenCase OnTimeReached(float t, SimpleCallback callback) { callbackData ??= new List(); callbackData.Add(new CallbackData(t, callback)); return this; } // ----------------------------------------------------------- // Internal — called only by TweensHolder or TweenCase itself // ----------------------------------------------------------- /// /// Applies the easing function to the raw progress value . /// Call this inside to obtain the eased interpolation factor. /// /// Raw normalized progress in [0, 1]. /// Eased value, typically in [0, 1] (may overshoot for elastic/back easings). public float Interpolate(float p) => easeFunction.Interpolate(p); /// Advances the delay timer by . Called by while the delay has not yet elapsed. internal void UpdateDelay(float deltaTime) { currentDelay += deltaTime; } /// /// Advances by deltaTime / duration, /// fires any callbacks whose threshold was crossed, /// and sets when state reaches 1. /// Called by every frame after the delay has elapsed. /// internal void UpdateState(float deltaTime) { state += Mathf.Min(1.0f, deltaTime / duration); // Check OnTimeReached callbacks independently — even if state jumped past 1. if (!callbackData.IsNullOrEmpty()) { for (int i = 0; i < callbackData.Count; i++) { var data = callbackData[i]; if (state >= data.t) { data.callback?.Invoke(); callbackData.RemoveAt(i); i--; } } } if (state >= 1) isCompleted = true; } /// Invokes the registered callback. Called by after the tween completes naturally. internal void InvokeCompleteEvent() { completedCallback?.Invoke(); } /// /// Called by after the tween has been fully removed from its array. /// Resets so the tween can be safely ed. /// internal void ResetKillingState() { isKilling = false; } // ----------------------------------------------------------- // Abstract — implemented by concrete tween cases // ----------------------------------------------------------- /// /// Called every frame while the tween is active and not paused (after any delay has elapsed). /// Implement the actual value interpolation here using (). /// /// Elapsed time since the last frame (scaled or unscaled, matching ). public abstract void Invoke(float deltaTime); /// /// Called when the tween finishes naturally or via . /// Snap the animated value to its final state here to guarantee the animation reaches its target. /// Not called when the tween is stopped via . /// public abstract void DefaultComplete(); // ----------------------------------------------------------- // Helpers // ----------------------------------------------------------- private readonly struct CallbackData { public readonly float t; public readonly SimpleCallback callback; public CallbackData(float t, SimpleCallback callback) { this.t = t; this.callback = callback; } } } }