//------------------------------------------------- // NGUI: Next-Gen UI kit // Copyright © 2011-2019 Tasharen Entertainment Inc //------------------------------------------------- using UnityEngine; using System.Collections; using System.Collections.Generic; /// /// Base class for all tweening operations. /// public abstract class UITweener : MonoBehaviour { /// /// Current tween that triggered the callback function. /// static public UITweener current; [DoNotObfuscateNGUI] public enum Method { Linear, EaseIn, EaseOut, EaseInOut, BounceIn, BounceOut, } [DoNotObfuscateNGUI] public enum Style { Once, Loop, PingPong, } /// /// Tweening method used. /// [HideInInspector] public Method method = Method.Linear; /// /// Does it play once? Does it loop? /// [HideInInspector] public Style style = Style.Once; /// /// Optional curve to apply to the tween's time factor value. /// [HideInInspector] public AnimationCurve animationCurve = new AnimationCurve(new Keyframe(0f, 0f, 0f, 1f), new Keyframe(1f, 1f, 1f, 0f)); /// /// Whether the tween will ignore the timescale, making it work while the game is paused. /// [HideInInspector] public bool ignoreTimeScale = true; /// /// How long will the tweener wait before starting the tween? /// [HideInInspector] public float delay = 0f; /// /// How long is the duration of the tween? /// [HideInInspector] public float duration = 1f; /// /// Whether the tweener will use steeper curves for ease in / out style interpolation. /// [HideInInspector] public bool steeperCurves = false; /// /// Used by buttons and tween sequences. Group of '0' means not in a sequence. /// [HideInInspector] public int tweenGroup = 0; [Tooltip("By default, Update() will be used for tweening. Setting this to 'true' will make the tween happen in FixedUpdate() insted.")] public bool useFixedUpdate = false; /// /// Event delegates called when the animation finishes. /// [HideInInspector] public List onFinished = new List(); // Deprecated functionality, kept for backwards compatibility [HideInInspector] public GameObject eventReceiver; [HideInInspector] public string callWhenFinished; /// /// Custom time scale for this tween, if desired. Can be used to slow down or speed up the animation. /// [System.NonSerialized] public float timeScale = 1f; bool mStarted = false; float mStartTime = 0f; float mDuration = 0f; float mAmountPerDelta = 1000f; float mFactor = 0f; /// /// Amount advanced per delta time. /// public float amountPerDelta { get { if (duration == 0f) return 1000f; if (mDuration != duration) { mDuration = duration; mAmountPerDelta = Mathf.Abs(1f / duration) * Mathf.Sign(mAmountPerDelta); } return mAmountPerDelta; } } /// /// Tween factor, 0-1 range. /// public float tweenFactor { get { return mFactor; } set { mFactor = Mathf.Clamp01(value); } } /// /// Direction that the tween is currently playing in. /// public AnimationOrTween.Direction direction { get { return amountPerDelta < 0f ? AnimationOrTween.Direction.Reverse : AnimationOrTween.Direction.Forward; } } /// /// This function is called by Unity when you add a component. Automatically set the starting values for convenience. /// void Reset () { if (!mStarted) { SetStartToCurrentValue(); SetEndToCurrentValue(); } } /// /// Update as soon as it's started so that there is no delay. /// protected virtual void Start () { DoUpdate(); } protected void Update () { if (!useFixedUpdate) DoUpdate(); } protected void FixedUpdate () { if (useFixedUpdate) DoUpdate(); } /// /// Update the tweening factor and call the virtual update function. /// protected void DoUpdate () { float delta = ignoreTimeScale && !useFixedUpdate ? Time.unscaledDeltaTime : Time.deltaTime; float time = ignoreTimeScale && !useFixedUpdate ? Time.unscaledTime : Time.time; if (!mStarted) { delta = 0; mStarted = true; mStartTime = time + delay; } if (time < mStartTime) return; // Advance the sampling factor mFactor += (duration == 0f) ? 1f : amountPerDelta * delta * timeScale; // Loop style simply resets the play factor after it exceeds 1. if (style == Style.Loop) { if (mFactor > 1f) { mFactor -= Mathf.Floor(mFactor); } } else if (style == Style.PingPong) { // Ping-pong style reverses the direction if (mFactor > 1f) { mFactor = 1f - (mFactor - Mathf.Floor(mFactor)); mAmountPerDelta = -mAmountPerDelta; } else if (mFactor < 0f) { mFactor = -mFactor; mFactor -= Mathf.Floor(mFactor); mAmountPerDelta = -mAmountPerDelta; } } // If the factor goes out of range and this is a one-time tweening operation, disable the script if ((style == Style.Once) && (duration == 0f || mFactor > 1f || mFactor < 0f)) { mFactor = Mathf.Clamp01(mFactor); Sample(mFactor, true); enabled = false; if (current != this) { UITweener before = current; current = this; if (onFinished != null) { mTemp = onFinished; onFinished = new List(); // Notify the listener delegates EventDelegate.Execute(mTemp); // Re-add the previous persistent delegates for (int i = 0; i < mTemp.Count; ++i) { EventDelegate ed = mTemp[i]; if (ed != null && !ed.oneShot) EventDelegate.Add(onFinished, ed, ed.oneShot); } mTemp = null; } // Deprecated legacy functionality support if (eventReceiver != null && !string.IsNullOrEmpty(callWhenFinished)) eventReceiver.SendMessage(callWhenFinished, this, SendMessageOptions.DontRequireReceiver); current = before; } } else Sample(mFactor, false); } List mTemp = null; /// /// Convenience function -- set a new OnFinished event delegate (here for to be consistent with RemoveOnFinished). /// public void SetOnFinished (EventDelegate.Callback del) { EventDelegate.Set(onFinished, del); } /// /// Convenience function -- set a new OnFinished event delegate (here for to be consistent with RemoveOnFinished). /// public void SetOnFinished (EventDelegate del) { EventDelegate.Set(onFinished, del); } /// /// Convenience function -- add a new OnFinished event delegate (here for to be consistent with RemoveOnFinished). /// public void AddOnFinished (EventDelegate.Callback del) { EventDelegate.Add(onFinished, del); } /// /// Convenience function -- add a new OnFinished event delegate (here for to be consistent with RemoveOnFinished). /// public void AddOnFinished (EventDelegate del) { EventDelegate.Add(onFinished, del); } /// /// Remove an OnFinished delegate. Will work even while iterating through the list when the tweener has finished its operation. /// public void RemoveOnFinished (EventDelegate del) { if (onFinished != null) onFinished.Remove(del); if (mTemp != null) mTemp.Remove(del); } /// /// Mark as not started when finished to enable delay on next play. /// void OnDisable () { mStarted = false; } /// /// Immediately finish the tween animation, if it's active. /// public void Finish () { if (enabled) { Sample(mAmountPerDelta > 0f ? 1f : 0f, true); enabled = false; } } /// /// Sample the tween at the specified factor. /// public void Sample (float factor, bool isFinished) { // Calculate the sampling value float val = Mathf.Clamp01(factor); if (method == Method.EaseIn) { val = 1f - Mathf.Sin(0.5f * Mathf.PI * (1f - val)); if (steeperCurves) val *= val; } else if (method == Method.EaseOut) { val = Mathf.Sin(0.5f * Mathf.PI * val); if (steeperCurves) { val = 1f - val; val = 1f - val * val; } } else if (method == Method.EaseInOut) { const float pi2 = Mathf.PI * 2f; val = val - Mathf.Sin(val * pi2) / pi2; if (steeperCurves) { val = val * 2f - 1f; float sign = Mathf.Sign(val); val = 1f - Mathf.Abs(val); val = 1f - val * val; val = sign * val * 0.5f + 0.5f; } } else if (method == Method.BounceIn) { val = BounceLogic(val); } else if (method == Method.BounceOut) { val = 1f - BounceLogic(1f - val); } // Call the virtual update OnUpdate((animationCurve != null) ? animationCurve.Evaluate(val) : val, isFinished); } /// /// Main Bounce logic to simplify the Sample function /// float BounceLogic (float val) { if (val < 0.363636f) // 0.363636 = (1/ 2.75) { val = 7.5685f * val * val; } else if (val < 0.727272f) // 0.727272 = (2 / 2.75) { val = 7.5625f * (val -= 0.545454f) * val + 0.75f; // 0.545454f = (1.5 / 2.75) } else if (val < 0.909090f) // 0.909090 = (2.5 / 2.75) { val = 7.5625f * (val -= 0.818181f) * val + 0.9375f; // 0.818181 = (2.25 / 2.75) } else { val = 7.5625f * (val -= 0.9545454f) * val + 0.984375f; // 0.9545454 = (2.625 / 2.75) } return val; } /// /// Play the tween. /// [System.Obsolete("Use PlayForward() instead")] public void Play () { Play(true); } /// /// Play the tween forward. /// public void PlayForward () { Play(true); } /// /// Play the tween in reverse. /// public void PlayReverse () { Play(false); } /// /// Manually activate the tweening process, reversing it if necessary. /// public virtual void Play (bool forward) { mAmountPerDelta = Mathf.Abs(amountPerDelta); if (!forward) mAmountPerDelta = -mAmountPerDelta; if (!enabled) { enabled = true; mStarted = false; } DoUpdate(); } /// /// Manually reset the tweener's state to the beginning. /// If the tween is playing forward, this means the tween's start. /// If the tween is playing in reverse, this means the tween's end. /// public void ResetToBeginning () { mStarted = false; mFactor = (amountPerDelta < 0f) ? 1f : 0f; Sample(mFactor, false); } /// /// Manually start the tweening process, reversing its direction. /// public void Toggle () { if (mFactor > 0f) { mAmountPerDelta = -amountPerDelta; } else { mAmountPerDelta = Mathf.Abs(amountPerDelta); } enabled = true; } /// /// Actual tweening logic should go here. /// abstract protected void OnUpdate (float factor, bool isFinished); /// /// Starts the tweening operation. /// static public T Begin (GameObject go, float duration, float delay = 0f) where T : UITweener { T comp = go.GetComponent(); #if UNITY_FLASH if ((object)comp == null) comp = (T)go.AddComponent(); #else // Find the tween with an unset group ID (group ID of 0). if (comp != null && comp.tweenGroup != 0) { comp = null; T[] comps = go.GetComponents(); for (int i = 0, imax = comps.Length; i < imax; ++i) { comp = comps[i]; if (comp != null && comp.tweenGroup == 0) break; comp = null; } } if (comp == null) { comp = go.AddComponent(); if (comp == null) { Debug.LogError("Unable to add " + typeof(T) + " to " + NGUITools.GetHierarchy(go), go); return null; } } #endif comp.mStarted = false; comp.mFactor = 0f; comp.duration = duration; comp.mDuration = duration; comp.delay = delay; comp.mAmountPerDelta = duration > 0f ? Mathf.Abs(1f / duration) : 1000f; comp.style = Style.Once; comp.animationCurve = new AnimationCurve(new Keyframe(0f, 0f, 0f, 1f), new Keyframe(1f, 1f, 1f, 0f)); comp.eventReceiver = null; comp.callWhenFinished = null; comp.onFinished.Clear(); if (comp.mTemp != null) comp.mTemp.Clear(); comp.enabled = true; return comp; } /// /// Set the 'from' value to the current one. /// public virtual void SetStartToCurrentValue () { } /// /// Set the 'to' value to the current one. /// public virtual void SetEndToCurrentValue () { } }