// 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);
}
/// 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;
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 (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
/// If TRUE includes the eventual tween delay, otherwise skips it
/// 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
/// 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.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 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) {
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.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.
/// 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.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"); 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"); 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"); return -1;
}
return pathTween.endValue.length;
}
#endregion
#endregion
}
}