// Author: Daniele Giardini - http://www.demigiant.com // Created: 2014/07/05 18:31 // // License Copyright (c) Daniele Giardini. // This work is subject to the terms at http://dotween.demigiant.com/license.php using DG.Tweening.Core; using DG.Tweening.Core.Easing; using DG.Tweening.Core.Enums; using DG.Tweening.Plugins.Core.PathCore; using DG.Tweening.Plugins.Options; using UnityEngine; #pragma warning disable 1573 namespace DG.Tweening { /// /// Methods that extend Tween objects and allow to control or get data from them /// public static class TweenExtensions { // =================================================================================== // TWEENERS + SEQUENCES -------------------------------------------------------------- #region Runtime Operations // /// Completes the tween // /// For Sequences only: if TRUE also internal Sequence callbacks will be fired, // /// otherwise they will be ignored // public static void Complete(this Tween t, bool withCallbacks = false) // { // if (t == null) { // if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return; // } else if (!t.active) { // if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return; // } else if (t.isSequenced) { // if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return; // } // // TweenManager.Complete(t, true, withCallbacks ? UpdateMode.Update : UpdateMode.Goto); // } /// Completes the tween public static void Complete(this Tween t) { Complete(t, false); } /// Completes the tween /// For Sequences only: if TRUE also internal Sequence callbacks will be fired, /// otherwise they will be ignored public static void Complete(this Tween t, bool withCallbacks) { if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return; } // TweenManager.Complete(t, true, withCallbacks ? UpdateMode.Update : UpdateMode.Goto); UpdateMode updateMode = TweenManager.isUpdateLoop ? UpdateMode.IgnoreOnComplete : withCallbacks ? UpdateMode.Update : UpdateMode.Goto; TweenManager.Complete(t, true, updateMode); } /// Flips the direction of this tween (backwards if it was going forward or viceversa) public static void Flip(this Tween t) { if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return; } TweenManager.Flip(t); } /// Forces the tween to initialize its settings immediately public static void ForceInit(this Tween t) { if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return; } TweenManager.ForceInit(t); } /// Send the tween to the given position in time /// Time position to reach /// (if higher than the whole tween duration the tween will simply reach its end) /// If TRUE will play the tween after reaching the given position, otherwise it will pause it public static void Goto(this Tween t, float to, bool andPlay = false) { if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return; } if (to < 0) to = 0; if (!t.startupDone) TweenManager.ForceInit(t); // Initialize the tween if it's not initialized already (required) TweenManager.Goto(t, to, andPlay); } /// Kills the tween /// If TRUE completes the tween before killing it public static void Kill(this Tween t, bool complete = false) { if (!DOTween.initialized) return; if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return; } if (complete) { TweenManager.Complete(t); if (t.autoKill && t.loops >= 0) return; // Already killed by Complete, so no need to go on } if (TweenManager.isUpdateLoop) { // Just mark it for killing, so the update loop will take care of it t.active = false; } else TweenManager.Despawn(t); } /// Pauses the tween public static T Pause(this T t) where T : Tween { if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return t; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return t; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return t; } TweenManager.Pause(t); return t; } /// Plays the tween public static T Play(this T t) where T : Tween { if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return t; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return t; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return t; } TweenManager.Play(t); return t; } /// Sets the tween in a backwards direction and plays it public static void PlayBackwards(this Tween t) { if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return; } TweenManager.PlayBackwards(t); } /// Sets the tween in a forward direction and plays it public static void PlayForward(this Tween t) { if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return; } TweenManager.PlayForward(t); } /// Restarts the tween from the beginning /// Ignored in case of Sequences. If TRUE includes the eventual tween delay, otherwise skips it /// Ignored in case of Sequences. If >= 0 changes the startup delay to this value, otherwise doesn't touch it public static void Restart(this Tween t, bool includeDelay = true, float changeDelayTo = -1) { if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return; } TweenManager.Restart(t, includeDelay, changeDelayTo); } /// Rewinds and pauses the tween /// Ignored in case of Sequences. If TRUE includes the eventual tween delay, otherwise skips it public static void Rewind(this Tween t, bool includeDelay = true) { if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return; } TweenManager.Rewind(t, includeDelay); } /// Smoothly rewinds the tween (delays excluded). /// A "smooth rewind" animates the tween to its start position, /// skipping all elapsed loops (except in case of LoopType.Incremental) while keeping the animation fluent. /// If called on a tween who is still waiting for its delay to happen, it will simply set the delay to 0 and pause the tween. /// Note that a tween that was smoothly rewinded will have its play direction flipped public static void SmoothRewind(this Tween t) { if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return; } TweenManager.SmoothRewind(t); } /// Plays the tween if it was paused, pauses it if it was playing public static void TogglePause(this Tween t) { if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return; } TweenManager.TogglePause(t); } #region Path Tweens /// Send a path tween to the given waypoint. /// Has no effect if this is not a path tween. /// BEWARE, this is a special utility method: /// it works only with Linear eases. Also, the lookAt direction might be wrong after calling this and might need to be set manually /// (because it relies on a smooth path movement and doesn't work well with jumps that encompass dramatic direction changes) /// Waypoint index to reach /// (if higher than the max waypoint index the tween will simply go to the last one) /// If TRUE will play the tween after reaching the given waypoint, otherwise it will pause it public static void GotoWaypoint(this Tween t, int waypointIndex, bool andPlay = false) { if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return; } TweenerCore pathTween = t as TweenerCore; if (pathTween == null) { if (Debugger.logPriority > 1) Debugger.LogNonPathTween(t); return; } if (!t.startupDone) TweenManager.ForceInit(t); // Initialize the tween if it's not initialized already (required) if (waypointIndex < 0) waypointIndex = 0; else if (waypointIndex > pathTween.changeValue.wps.Length - 1) waypointIndex = pathTween.changeValue.wps.Length - 1; // Find path percentage relative to given waypoint float wpLength = 0; // Total length from start to the given waypoint for (int i = 0; i < waypointIndex + 1; i++) wpLength += pathTween.changeValue.wpLengths[i]; float wpPerc = wpLength / pathTween.changeValue.length; // Convert to time taking eventual inverse direction into account bool useInversePosition = t.hasLoops && t.loopType == LoopType.Yoyo && (t.position < t.duration ? t.completedLoops % 2 != 0 : t.completedLoops % 2 == 0); if (useInversePosition) wpPerc = 1 - wpPerc; float to = (t.isComplete ? t.completedLoops - 1 : t.completedLoops) * t.duration + wpPerc * t.duration; TweenManager.Goto(t, to, andPlay); } #endregion #endregion #region Yield Coroutines /// /// Creates a yield instruction that waits until the tween is killed or complete. /// It can be used inside a coroutine as a yield. /// Example usage:yield return myTween.WaitForCompletion(); /// public static YieldInstruction WaitForCompletion(this Tween t) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return null; } return DOTween.instance.StartCoroutine(DOTween.instance.WaitForCompletion(t)); } /// /// Creates a yield instruction that waits until the tween is killed or rewinded. /// It can be used inside a coroutine as a yield. /// Example usage:yield return myTween.WaitForRewind(); /// public static YieldInstruction WaitForRewind(this Tween t) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return null; } return DOTween.instance.StartCoroutine(DOTween.instance.WaitForRewind(t)); } /// /// Creates a yield instruction that waits until the tween is killed. /// It can be used inside a coroutine as a yield. /// Example usage:yield return myTween.WaitForKill(); /// public static YieldInstruction WaitForKill(this Tween t) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return null; } return DOTween.instance.StartCoroutine(DOTween.instance.WaitForKill(t)); } /// /// Creates a yield instruction that waits until the tween is killed or has gone through the given amount of loops. /// It can be used inside a coroutine as a yield. /// Example usage:yield return myTween.WaitForElapsedLoops(2); /// /// Elapsed loops to wait for public static YieldInstruction WaitForElapsedLoops(this Tween t, int elapsedLoops) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return null; } return DOTween.instance.StartCoroutine(DOTween.instance.WaitForElapsedLoops(t, elapsedLoops)); } /// /// Creates a yield instruction that waits until the tween is killed or has reached the given position (loops included, delays excluded). /// It can be used inside a coroutine as a yield. /// Example usage:yield return myTween.WaitForPosition(2.5f); /// /// Position (loops included, delays excluded) to wait for public static YieldInstruction WaitForPosition(this Tween t, float position) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return null; } return DOTween.instance.StartCoroutine(DOTween.instance.WaitForPosition(t, position)); } /// /// Creates a yield instruction that waits until the tween is killed or started /// (meaning when the tween is set in a playing state the first time, after any eventual delay). /// It can be used inside a coroutine as a yield. /// Example usage:yield return myTween.WaitForStart(); /// public static Coroutine WaitForStart(this Tween t) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return null; } return DOTween.instance.StartCoroutine(DOTween.instance.WaitForStart(t)); } #endregion #region Info Getters /// Returns the total number of loops completed by this tween public static int CompletedLoops(this Tween t) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return 0; } return t.completedLoops; } /// Returns the eventual delay set for this tween public static float Delay(this Tween t) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return 0; } return t.delay; } /// Returns the eventual elapsed delay set for this tween public static float ElapsedDelay(this Tween t) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return 0; } return t.elapsedDelay; } /// Returns the duration of this tween (delays excluded). /// NOTE: when using settings like SpeedBased, the duration will be recalculated when the tween starts /// If TRUE returns the full duration loops included, /// otherwise the duration of a single loop cycle public static float Duration(this Tween t, bool includeLoops = true) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return 0; } // Calculate duration here instead than getting fullDuration because fullDuration might // not be set yet, since it's set inside DoStartup if (includeLoops) return t.loops == -1 ? Mathf.Infinity : t.duration * t.loops; return t.duration; } /// Returns the elapsed time for this tween (delays exluded) /// If TRUE returns the elapsed time since startup loops included, /// otherwise the elapsed time within the current loop cycle public static float Elapsed(this Tween t, bool includeLoops = true) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return 0; } if (includeLoops) { int loopsToCount = t.position >= t.duration ? t.completedLoops - 1 : t.completedLoops; return (loopsToCount * t.duration) + t.position; } return t.position; } /// Returns the elapsed percentage (0 to 1) of this tween (delays exluded) /// If TRUE returns the elapsed percentage since startup loops included, /// otherwise the elapsed percentage within the current loop cycle public static float ElapsedPercentage(this Tween t, bool includeLoops = true) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return 0; } if (includeLoops) { if (t.fullDuration <= 0) return 0; int loopsToCount = t.position >= t.duration ? t.completedLoops - 1 : t.completedLoops; return ((loopsToCount * t.duration) + t.position) / t.fullDuration; } return t.position / t.duration; } /// Returns the elapsed percentage (0 to 1) of this tween (delays exluded), /// based on a single loop, and calculating eventual backwards Yoyo loops as 1 to 0 instead of 0 to 1 public static float ElapsedDirectionalPercentage(this Tween t) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return 0; } float perc = t.position / t.duration; bool isInverse = t.completedLoops > 0 && t.hasLoops && t.loopType == LoopType.Yoyo && (!t.isComplete && t.completedLoops % 2 != 0 || t.isComplete && t.completedLoops % 2 == 0); return isInverse ? 1 - perc : perc; } /// Returns FALSE if this tween has been killed or is NULL, TRUE otherwise. /// BEWARE: if this tween is recyclable it might have been spawned again for another use and thus return TRUE anyway. /// When working with recyclable tweens you should take care to know when a tween has been killed and manually set your references to NULL. /// If you want to be sure your references are set to NULL when a tween is killed you can use the OnKill callback like this: /// .OnKill(()=> myTweenReference = null) public static bool IsActive(this Tween t) { return t != null && t.active; } /// Returns TRUE if this tween was reversed and is set to go backwards public static bool IsBackwards(this Tween t) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return false; } return t.isBackwards; } /// Returns TRUE if the tween is complete /// (silently fails and returns FALSE if the tween has been killed) public static bool IsComplete(this Tween t) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return false; } return t.isComplete; } /// Returns TRUE if this tween has been initialized public static bool IsInitialized(this Tween t) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return false; } return t.startupDone; } /// Returns TRUE if this tween is playing public static bool IsPlaying(this Tween t) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return false; } return t.isPlaying; } /// Returns the total number of loops set for this tween /// (returns -1 if the loops are infinite) public static int Loops(this Tween t) { if (!t.active) { if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t); return 0; } return t.loops; } #region Path Tweens /// /// Returns a point on a path based on the given path percentage. /// Returns Vector3.zero if this is not a path tween, if the tween is invalid, or if the path is not yet initialized. /// A path is initialized after its tween starts, or immediately if the tween was created with the Path Editor (DOTween Pro feature). /// You can force a path to be initialized by calling myTween.ForceInit(). /// /// Percentage of the path (0 to 1) on which to get the point public static Vector3 PathGetPoint(this Tween t, float pathPercentage) { if (pathPercentage > 1) pathPercentage = 1; else if (pathPercentage < 0) pathPercentage = 0; if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return Vector3.zero; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return Vector3.zero; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return Vector3.zero; } TweenerCore pathTween = t as TweenerCore; if (pathTween == null) { if (Debugger.logPriority > 1) Debugger.LogNonPathTween(t); return Vector3.zero; } else if (!pathTween.endValue.isFinalized) { if (Debugger.logPriority > 1) Debugger.LogWarning("The path is not finalized yet", t); return Vector3.zero; } return pathTween.endValue.GetPoint(pathPercentage, true); } /// /// Returns an array of points that can be used to draw the path. /// Note that this method generates allocations, because it creates a new array. /// Returns NULL if this is not a path tween, if the tween is invalid, or if the path is not yet initialized. /// A path is initialized after its tween starts, or immediately if the tween was created with the Path Editor (DOTween Pro feature). /// You can force a path to be initialized by calling myTween.ForceInit(). /// /// How many points to create for each path segment (waypoint to waypoint). /// Only used in case of non-Linear paths public static Vector3[] PathGetDrawPoints(this Tween t, int subdivisionsXSegment = 10) { if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return null; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return null; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return null; } TweenerCore pathTween = t as TweenerCore; if (pathTween == null) { if (Debugger.logPriority > 1) Debugger.LogNonPathTween(t); return null; } else if (!pathTween.endValue.isFinalized) { if (Debugger.logPriority > 1) Debugger.LogWarning("The path is not finalized yet", t); return null; } return Path.GetDrawPoints(pathTween.endValue, subdivisionsXSegment); } /// /// Returns the length of a path. /// Returns -1 if this is not a path tween, if the tween is invalid, or if the path is not yet initialized. /// A path is initialized after its tween starts, or immediately if the tween was created with the Path Editor (DOTween Pro feature). /// You can force a path to be initialized by calling myTween.ForceInit(). /// public static float PathLength(this Tween t) { if (t == null) { if (Debugger.logPriority > 1) Debugger.LogNullTween(t); return -1; } else if (!t.active) { if (Debugger.logPriority > 1) Debugger.LogInvalidTween(t); return -1; } else if (t.isSequenced) { if (Debugger.logPriority > 1) Debugger.LogNestedTween(t); return -1; } TweenerCore pathTween = t as TweenerCore; if (pathTween == null) { if (Debugger.logPriority > 1) Debugger.LogNonPathTween(t); return -1; } else if (!pathTween.endValue.isFinalized) { if (Debugger.logPriority > 1) Debugger.LogWarning("The path is not finalized yet", t); return -1; } return pathTween.endValue.length; } #endregion #endregion } }