Skip to content

Instantly share code, notes, and snippets.

@FronkonGames
Created October 30, 2022 23:20
Show Gist options
  • Select an option

  • Save FronkonGames/ae3d0d613ac4ea6738e288c0a490c020 to your computer and use it in GitHub Desktop.

Select an option

Save FronkonGames/ae3d0d613ac4ea6738e288c0a490c020 to your computer and use it in GitHub Desktop.

Revisions

  1. FronkonGames created this gist Oct 30, 2022.
    784 changes: 784 additions & 0 deletions TinyTween.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,784 @@
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // _____ _ _____
    // |_ _|_|___ _ _ |_ _|_ _ _ ___ ___ ___
    // | | | | | | | | | | | | | -_| -_| |
    // |_| |_|_|_|_ | |_| |_____|___|___|_|_|
    // |___|
    // A Complete and Easy to use Tweens library in One File
    //
    // Basic use:
    // using FronkonGames.TinyTween;
    // GameObject gameObject = new();
    // gameObject.TweenMove(new Vector3(10.0f, 0.0f, 0.0f), 1.0f, Ease.Bounce);
    //
    // Advanced use:
    // using FronkonGames.TinyTween;
    // GameObject clockHand = new();
    // TweenQuaternion.Create()
    // .Origin(Quaternion.Euler(-30.0f, 0.0f, 0.0f))
    // .Destination(Quaternion.Euler(30.0f, 0.0f, 0.0f))
    // .Duration(1.0f)
    // .Loop(TweenLoop.YoYo)
    // .EasingIn(Ease.Back)
    // .EasingOut(Ease.Elastic)
    // .Owner(clockHand)
    // .Condition(tween => tween.ExecutionCount < 10)
    // .OnUpdate(tween => clockHand.transform.rotation = tween.Value)
    // .OnEnd(() => Debug.Log("It's show time!"))
    // .Start();
    //
    // Copyright (c) 2022 Martin Bustos @FronkonGames <[email protected]>
    //
    // MIT License
    // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
    // documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
    // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    // permit persons to whom the Software is furnished to do so, subject to the following conditions:
    //
    // The above copyright notice and this permission notice shall be included in all copies or substantial portions of
    // the Software.
    //
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
    // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
    // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
    // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    using System;
    using System.Collections.Generic;
    using UnityEngine;

    namespace FronkonGames.TinyTween
    {
    /// <summary> State of a Tween operation. </summary>
    public enum TweenState
    {
    Running, Paused, Finished
    }

    /// <summary> Execution modes of a Tween operation. </summary>
    public enum TweenLoop
    {
    // Just once. Start over from the beginning. Back and forth loop.
    Once, Loop, YoYo,
    }

    /// <summary> Interface of a tween. </summary>
    public interface ITween
    {
    /// <summary> Tween status. </summary>
    TweenState State { get; }

    /// <summary> Update the Tween operation. </summary>
    void Update();
    }

    /// <summary> Generic interface of a tween. </summary>
    public interface ITween<T> : ITween where T : struct
    {
    /// <summary> Current value. </summary>
    T Value { get; }

    /// <summary> Tween operation progress (0, 1). </summary>
    float Progress { get; }

    /// <summary> Executions counter. </summary>
    int ExecutionCount { get; }

    /// <summary> Tween status. </summary>
    TweenState State { get; }

    /// <summary> Execute a tween operation. </summary>
    Tween<T> Start();

    /// <summary> Pause the tween. </summary>
    void Pause();

    /// <summary> Continue the tween. </summary>
    void Resume();

    /// <summary> Finish the Tween operation. </summary>
    /// <param name="moveToEnd">Move the value at the end or leave it as this.</param>
    void Stop(bool moveToEnd = true);

    /// <summary> Sets tween value at origin and time to 0. </summary>
    void Reset();

    /// <summary> Update the Tween operation. </summary>
    void Update();
    }

    /// <summary> Tween operation. If it is created manually, Update() must be called. </summary>
    public abstract class Tween<T> : ITween<T> where T : struct
    {
    /// <inheritdoc/>
    public TweenState State { get; private set; } = TweenState.Paused;

    /// <inheritdoc/>
    public T Value { get; private set; }

    /// <inheritdoc/>
    public float Progress { get; private set; }

    /// <inheritdoc/>
    public int ExecutionCount { get; private set; }

    /// <summary> Time that the operation takes. </summary>
    public float Time { get; private set; }

    /// <summary> Does the Tween depend on another object? </summary>
    private bool IsOwned { get; set; }

    private object owner = null;

    private T origin, destination;
    private Ease easeIn = Ease.None, easeOut = Ease.None;
    private TweenLoop loop = TweenLoop.Once;

    private float duration = 1.0f;
    private float currentTime;
    private bool clamp;
    private int residueCount = -1;

    private Action<Tween<T>> updateFunction;
    private Action<Tween<T>> endFunction;
    private Func<Tween<T>, bool> condition;

    private readonly Func<Tween<T>, T, T, float, bool, T> interpolationFunction; // Tween, start, end, progress, clamp.

    /// <summary> Constructor. </summary>
    /// <param name="lerpFunc">Interpolation function.</param>
    protected Tween(Func<Tween<T>, T, T, float, bool, T> interpolationFunction) => this.interpolationFunction = interpolationFunction;

    /// <summary> Initial value. Use it only to create a new Tween. </summary>
    /// <returns>This.</returns>
    public Tween<T> Origin(T start) { origin = start; return this; }

    /// <summary> Final value. Use it only to create a new Tween. </summary>
    /// <returns>This.</returns>
    public Tween<T> Destination(T end) { destination = end; return this; }

    /// <summary> Time to execute the operation, must be greater than 0. Use it only to create a new Tween. </summary>
    /// <returns>This.</returns>
    public Tween<T> Duration(float duration) { this.duration = Mathf.Max(duration, 0.0f); return this; }

    /// <summary> Execution mode. Use it only to create a new Tween. </summary>
    /// <returns>This.</returns>
    public Tween<T> Loop(TweenLoop loop) { this.loop = loop; return this; }

    /// <summary> Easing function. Overwrite the In and Out functions. Use it only to create a new Tween. </summary>
    /// <param name="ease">Easing function.</param>
    /// <returns>This.</returns>
    public Tween<T> Easing(Ease ease) { easeIn = easeOut = ease; return this; }

    /// <summary> Easing In function. Use it only to create a new Tween. </summary>
    /// <param name="ease">Easing function.</param>
    /// <returns>This.</returns>
    public Tween<T> EasingIn(Ease ease) { easeIn = ease; return this; }

    /// <summary> Easing Out function. Use it only to create a new Tween. </summary>
    /// <param name="ease">Easing function.</param>
    /// <returns>This.</returns>
    public Tween<T> EasingOut(Ease ease) { easeOut = ease; return this; }

    /// <summary> Update callback. Use it to apply the Tween values. </summary>
    /// <param name="updateCallback">Callback.</param>
    /// <returns>This.</returns>
    public Tween<T> OnUpdate(Action<Tween<T>> updateCallback) { updateFunction = updateCallback; return this; }

    /// <summary> Executed at the end of the operation (optional). </summary>
    /// <param name="endCallback">Callback.</param>
    /// <returns>This.</returns>
    public Tween<T> OnEnd(Action<Tween<T>> endCallback) { endFunction = endCallback; return this; }

    /// <summary> Condition of progress, stops if the operation is not true (optional). </summary>
    /// <param name="condition">Condition function.</param>
    /// <returns>This.</returns>
    public Tween<T> Condition(Func<Tween<T>, bool> condition) { this.condition = condition; return this; }

    /// <summary> Limits the values of the interpolation to the range [0, 1]. </summary>
    /// <param name="clamp">Clamp.</param>
    /// <returns>This.</returns>
    public Tween<T> Clamp(bool clamp) { this.clamp = clamp; return this; }

    /// <summary>
    /// I set an object as the 'owner' of the Tween. If the object is destroyed, the Tween ends and is destroyed.
    /// </summary>
    /// <param name="owner">Owner</param>
    /// <returns>This.</returns>
    public Tween<T> Owner(object owner) { IsOwned = owner != null; this.owner = owner; return this; }

    /// <inheritdoc/>
    public Tween<T> Start()
    {
    Debug.Assert(duration > 0.0f, "[FronkonGames.TinyTween] The duration of the tween should be greater than zero.");
    Debug.Assert(easeIn != Ease.None && easeOut != Ease.None, "[FronkonGames.TinyTween] You must set some kind of Ease.");

    State = TweenState.Running;
    UpdateValue();

    return this;
    }

    /// <inheritdoc/>
    public void Pause() => State = TweenState.Paused;

    /// <inheritdoc/>
    public void Resume() => State = TweenState.Running;

    /// <inheritdoc/>
    public void Stop(bool moveToEnd = true)
    {
    if (State != TweenState.Finished)
    {
    State = TweenState.Finished;
    if (moveToEnd == true)
    {
    currentTime = duration;

    UpdateValue();
    }

    endFunction?.Invoke(this);
    }
    }

    /// <inheritdoc/>
    public void Reset()
    {
    currentTime = 0.0f;
    Value = origin;
    }

    /// <inheritdoc/>
    public void Update()
    {
    if (IsOwned == true && owner.Equals(null) || condition != null && condition(this) == false)
    Stop(false);
    else
    {
    currentTime += UnityEngine.Time.deltaTime;
    if (currentTime >= duration)
    {
    residueCount--;
    ExecutionCount++;

    switch (loop)
    {
    case TweenLoop.Once: Stop(); break;

    case TweenLoop.Loop:
    if (residueCount == 0)
    Stop();

    Value = origin;
    currentTime = Progress = 0.0f;
    break;

    case TweenLoop.YoYo:
    if (residueCount == 0)
    Stop();

    (destination, origin) = (origin, destination);
    currentTime = Progress = 0.0f;
    break;

    default: throw new ArgumentOutOfRangeException();
    }
    }
    else
    UpdateValue();
    }
    }

    private void UpdateValue()
    {
    float t = currentTime / duration;
    Progress = EasingFunctions.Calculate(t > 0.5f ? easeOut : easeIn, easeIn != Ease.None, easeOut != Ease.None, t);

    Value = interpolationFunction(this, origin, destination, Progress, clamp);

    updateFunction?.Invoke(this);
    }
    }

    /// <summary>
    /// Tweens manager, update tweens and delete those that have already ended.
    /// It is not necessary if you are in charge of maintaining your Tweens ;)
    /// </summary>
    public sealed class TinyTween : MonoBehaviour
    {
    public static TinyTween Instance => LazyInstance.Value;

    private static readonly Lazy<TinyTween> LazyInstance = new(CreateSingleton);

    private readonly List<ITween> tweens = new();

    /// <summary> Add an existing tween. </summary>
    /// <param name="tween">New tween</param>
    /// <returns>Tween.</returns>
    public ITween Add(ITween tween) { tweens.Add(tween); return tween; }

    private static TinyTween CreateSingleton()
    {
    GameObject ownerObject = new("TinyTween");
    TinyTween instance = ownerObject.AddComponent<TinyTween>();
    DontDestroyOnLoad(ownerObject);

    return instance;
    }

    private void Update()
    {
    int count = tweens.Count;
    for (int i = count - 1; i >= 0; --i)
    {
    ITween tween = tweens[i];

    if (tween.State == TweenState.Running)
    tween.Update();

    if (tween.State == TweenState.Finished && i < count)
    tweens.RemoveAt(i);
    }
    }

    private void OnDisable() => tweens.Clear();
    }

    /// <summary> Tween float. </summary>
    public sealed class TweenFloat : Tween<float>
    {
    /// <summary> Create a Tween float and add it to the TinyTween manager. </summary>
    /// <returns>Tween.</returns>
    public static Tween<float> Create() => TinyTween.Instance.Add(new TweenFloat()) as Tween<float>;

    private static float Lerp(ITween<float> t, float start, float end, float progress, bool clamp) =>
    clamp == true ? Mathf.Lerp(start, end, progress) : Mathf.LerpUnclamped(start, end, progress);

    private TweenFloat() : base(Lerp) { }
    }

    /// <summary> Tween Vector2. </summary>
    public sealed class TweenVector2 : Tween<Vector2>
    {
    /// <summary> Create a Tween Vector2 and add it to the TinyTween manager. </summary>
    /// <returns>Tween.</returns>
    public static Tween<Vector2> Create() => TinyTween.Instance.Add(new TweenVector2()) as Tween<Vector2>;

    private static Vector2 Lerp(ITween<Vector2> t, Vector2 start, Vector2 end, float progress, bool clamp) =>
    clamp == true ? Vector2.Lerp(start, end, progress) : Vector2.LerpUnclamped(start, end, progress);

    private TweenVector2() : base(Lerp) { }
    }

    /// <summary> Tween Vector3. </summary>
    public sealed class TweenVector3 : Tween<Vector3>
    {
    /// <summary> Create a Tween Vector3 and add it to the TinyTween manager. </summary>
    /// <returns>Tween.</returns>
    public static Tween<Vector3> Create() => TinyTween.Instance.Add(new TweenVector3()) as Tween<Vector3>;

    private static Vector3 Lerp(ITween<Vector3> t, Vector3 start, Vector3 end, float progress, bool clamp) =>
    clamp == true ? Vector3.Lerp(start, end, progress) : Vector3.LerpUnclamped(start, end, progress);

    private TweenVector3() : base(Lerp) { }
    }

    /// <summary> Tween Vector4. </summary>
    public sealed class TweenVector4 : Tween<Vector4>
    {
    /// <summary> Create a Tween Vector4 and add it to the TinyTween manager. </summary>
    /// <returns>Tween.</returns>
    public static Tween<Vector4> Create() => TinyTween.Instance.Add(new TweenVector4()) as Tween<Vector4>;

    private static Vector4 Lerp(ITween<Vector4> t, Vector4 start, Vector4 end, float progress, bool clamp) =>
    clamp == true ? Vector4.Lerp(start, end, progress) : Vector4.LerpUnclamped(start, end, progress);

    private TweenVector4() : base(Lerp) { }
    }

    /// <summary> Tween Quaternion. </summary>
    public sealed class TweenQuaternion : Tween<Quaternion>
    {
    /// <summary> Create a Tween Quaternion and add it to the TinyTween manager. </summary>
    /// <returns>Tween.</returns>
    public static Tween<Quaternion> Create() => TinyTween.Instance.Add(new TweenQuaternion()) as Tween<Quaternion>;

    private static Quaternion Lerp(ITween<Quaternion> t, Quaternion start, Quaternion end, float progress, bool clamp) =>
    clamp == true ? Quaternion.Lerp(start, end, progress) : Quaternion.LerpUnclamped(start, end, progress);

    private TweenQuaternion() : base(Lerp) { }
    }

    /// <summary> Tween Color. </summary>
    public sealed class TweenColor : Tween<Color>
    {
    /// <summary> Create a Tween Color and add it to the TinyTween manager. </summary>
    /// <returns>Tween.</returns>
    public static Tween<Color> Create() => TinyTween.Instance.Add(new TweenColor()) as Tween<Color>;

    private static Color Lerp(ITween<Color> t, Color start, Color end, float progress, bool clamp) =>
    clamp == true ? Color.Lerp(start, end, progress) : Color.LerpUnclamped(start, end, progress);

    private TweenColor() : base(Lerp) { }
    }

    /// <summary> Extensions to make it easy to use TinyTween. </summary>
    public static class TweenExtensions
    {
    /// <summary> Create and execute a tween. </summary>
    /// <param name="self">Self.</param>
    /// <param name="origin">Start value.</param>
    /// <param name="destination">End value.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<float> Tween(this float self, float origin, float destination, float duration, Ease ease) =>
    TweenFloat.Create()
    .Origin(origin)
    .Destination(destination)
    .Duration(duration)
    .OnUpdate(tween => self = tween.Value)
    .Owner(self)
    .Easing(ease)
    .Start();

    /// <summary> Create and execute a tween, using as initial value the current value. </summary>
    /// <param name="self">Self.</param>
    /// <param name="destination">End value.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<float> Tween(this float self, float destination, float duration, Ease ease) => Tween(self, self, destination, duration, ease);

    /// <summary> Create and execute a tween. </summary>
    /// <param name="self">Self.</param>
    /// <param name="origin">Start value.</param>
    /// <param name="destination">End value.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Vector3> Tween(this Vector3 self, Vector3 origin, Vector3 destination, float duration, Ease ease) =>
    TweenVector3.Create()
    .Origin(origin)
    .Destination(destination)
    .Duration(duration)
    .OnUpdate(tween => self = tween.Value)
    .Owner(self)
    .Easing(ease)
    .Start();

    /// <summary> Create and execute a tween, using as initial value the current value. </summary>
    /// <param name="self">Self.</param>
    /// <param name="destination">End value.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Vector3> Tween(this Vector3 self, Vector3 destination, float duration, Ease ease) => Tween(self, self, destination, duration, ease);

    /// <summary> Create and execute a tween. </summary>
    /// <param name="self">Self.</param>
    /// <param name="origin">Start value.</param>
    /// <param name="destination">End value.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Quaternion> Tween(this Quaternion self, Quaternion origin, Quaternion destination, float duration, Ease ease) =>
    TweenQuaternion.Create()
    .Origin(origin)
    .Destination(destination)
    .Duration(duration)
    .OnUpdate(tween => self = tween.Value)
    .Owner(self)
    .Easing(ease)
    .Start();

    /// <summary> Create and execute a tween, using as initial value the current value. </summary>
    /// <param name="self">Self.</param>
    /// <param name="destination">End value.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Quaternion> Tween(this Quaternion self, Quaternion destination, float duration, Ease ease) => Tween(self, self, destination, duration, ease);

    /// <summary> Create and execute a tween. </summary>
    /// <param name="self">Self.</param>
    /// <param name="origin">Start value.</param>
    /// <param name="destination">End value.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Color> Tween(this Color self, Color origin, Color destination, float duration, Ease ease) =>
    FronkonGames.TinyTween.TweenColor.Create()
    .Origin(origin)
    .Destination(destination)
    .Duration(duration)
    .OnUpdate(tween => self = tween.Value)
    .Owner(self)
    .Easing(ease)
    .Start();

    /// <summary> Create and execute a tween, using as initial value the current value. </summary>
    /// <param name="self">Self.</param>
    /// <param name="destination">End value.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Color> Tween(this Color self, Color destination, float duration, Ease ease) => Tween(self, self, destination, duration, ease);

    /// <summary> Moves a Transform. </summary>
    /// <param name="self">Self.</param>
    /// <param name="origin">Start position.</param>
    /// <param name="destination">End position.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Vector3> TweenMove(this Transform self, Vector3 origin, Vector3 destination, float duration, Ease ease) =>
    TweenVector3.Create()
    .Origin(origin)
    .Destination(destination)
    .Duration(duration)
    .OnUpdate(tween => self.position = tween.Value)
    .Owner(self)
    .Easing(ease)
    .Start();

    /// <summary> Moves a Transform, using its current position as origin. </summary>
    /// <param name="self">Self.</param>
    /// <param name="destination">End position.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Vector3> TweenMove(this Transform self, Vector3 destination, float duration, Ease ease) => TweenMove(self, self.position, destination, duration, ease);

    /// <summary> Scale a Transform. </summary>
    /// <param name="self">Self.</param>
    /// <param name="origin">Start scale.</param>
    /// <param name="destination">End scale.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Vector3> TweenScale(this Transform self, Vector3 origin, Vector3 destination, float duration, Ease ease) =>
    TweenVector3.Create()
    .Origin(origin)
    .Destination(destination)
    .Duration(duration)
    .OnUpdate(tween => self.localScale = tween.Value)
    .Owner(self)
    .Easing(ease)
    .Start();

    /// <summary> Scale a Transform, using its current scale as origin. </summary>
    /// <param name="self">Self.</param>
    /// <param name="destination">End scale.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Vector3> TweenScale(this Transform self, Vector3 destination, float duration, Ease ease) => TweenScale(self, self.localScale, destination, duration, ease);

    /// <summary> Rotate a Transform. </summary>
    /// <param name="self">Self.</param>
    /// <param name="origin">Start rotation.</param>
    /// <param name="destination">End rotation.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Quaternion> TweenRotation(this Transform self, Quaternion origin, Quaternion destination, float duration, Ease ease) =>
    TweenQuaternion.Create()
    .Origin(origin)
    .Destination(destination)
    .Duration(duration)
    .OnUpdate(tween => self.rotation = tween.Value)
    .Owner(self)
    .Easing(ease)
    .Start();

    /// <summary> Rotate a Transform, using its current rotation as origin. </summary>
    /// <param name="self">Self.</param>
    /// <param name="destination">End rotation.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Quaternion> TweenRotation(this Transform self, Quaternion destination, float duration, Ease ease) => TweenRotation(self, self.rotation, destination, duration, ease);

    /// <summary> Moves a GameObject. </summary>
    /// <param name="self">Self.</param>
    /// <param name="origin">Start position.</param>
    /// <param name="destination">End position.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Vector3> TweenMove(this GameObject self, Vector3 origin, Vector3 destination, float duration, Ease ease) => TweenMove(self.transform, origin, destination, duration, ease);

    /// <summary> Moves a GameObject, using its current position as origin. </summary>
    /// <param name="self">Self.</param>
    /// <param name="destination">End position.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Vector3> TweenMove(this GameObject self, Vector3 destination, float duration, Ease ease) => TweenMove(self.transform, self.transform.position, destination, duration, ease);

    /// <summary> Scale a GameObject. </summary>
    /// <param name="self">Self.</param>
    /// <param name="origin">Start scale.</param>
    /// <param name="destination">End scale.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Vector3> TweenScale(this GameObject self, Vector3 origin, Vector3 destination, float duration, Ease ease) => TweenScale(self.transform, origin, destination, duration, ease);

    /// <summary> Scale a GameObject, using its current scale as origin. </summary>
    /// <param name="self">Self.</param>
    /// <param name="destination">End scale.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Vector3> TweenScale(this GameObject self, Vector3 destination, float duration, Ease ease) => TweenScale(self.transform, self.transform.localScale, destination, duration, ease);

    /// <summary> Rotate a GameObject. </summary>
    /// <param name="self">Self.</param>
    /// <param name="origin">Start rotation.</param>
    /// <param name="destination">End rotation.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Quaternion> TweenRotation(this GameObject self, Quaternion origin, Quaternion destination, float duration, Ease ease) => TweenRotation(self.transform, origin, destination, duration, ease);

    /// <summary> Rotate a GameObject, using its current rotation as origin. </summary>
    /// <param name="self">Self.</param>
    /// <param name="destination">End rotation.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <returns>Tween.</returns>
    public static Tween<Quaternion> TweenRotation(this GameObject self, Quaternion destination, float duration, Ease ease) => TweenRotation(self.transform, self.transform.rotation, destination, duration, ease);

    /// <summary> Change the color of a Material. </summary>
    /// <param name="self">Self.</param>
    /// <param name="origin">Start color.</param>
    /// <param name="destination">End color.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <param name="name">The name of the color variable.</param>
    /// <returns>Tween.</returns>
    public static Tween<Color> TweenColor(this Material self, Color origin, Color destination, float duration, Ease ease, string name = "_Color") =>
    FronkonGames.TinyTween.TweenColor.Create()
    .Origin(origin)
    .Destination(destination)
    .Duration(duration)
    .OnUpdate(tween => self.SetColor(name, tween.Value))
    .Owner(self)
    .Easing(ease)
    .Start();

    /// <summary> Change the color of a Material, using its current color as origin. </summary>
    /// <param name="self">Self.</param>
    /// <param name="destination">End color.</param>
    /// <param name="duration">Time in seconds.</param>
    /// <param name="ease">Easing.</param>
    /// <param name="name">The name of the color variable.</param>
    /// <returns>Tween.</returns>
    public static Tween<Color> TweenColor(this Material self, Color destination, float duration, Ease ease, string name = "_Color") => TweenColor(self, self.GetColor(name), destination, duration, ease, name);
    }

    /// <summary> Types of Easing functions. See https://easings.net </summary>
    public enum Ease
    {
    None,
    Linear,
    Sine,
    Quad,
    Cubic,
    Quart,
    Quint,
    Expo,
    Circ,
    Back,
    Elastic,
    Bounce,
    }

    /// <summary> Easing functions. </summary>
    internal static class EasingFunctions
    {
    public static float Calculate(Ease ease, bool easingIn, bool easingOut, float t) => ease switch
    {
    Ease.Linear => t,
    Ease.Sine => easingIn && easingOut ? SineInOut(t) : easingIn ? SineIn(t) : SineOut(t),
    Ease.Quad => easingIn && easingOut ? QuadInOut(t) : easingIn ? QuadIn(t) : QuadOut(t),
    Ease.Cubic => easingIn && easingOut ? CubicInOut(t) : easingIn ? CubicIn(t) : CubicOut(t),
    Ease.Quart => easingIn && easingOut ? QuartInOut(t) : easingIn ? QuartIn(t) : QuartOut(t),
    Ease.Quint => easingIn && easingOut ? QuintInOut(t) : easingIn ? QuintIn(t) : QuintOut(t),
    Ease.Expo => easingIn && easingOut ? ExpoInOut(t) : easingIn ? ExpoIn(t) : ExpoOut(t),
    Ease.Circ => easingIn && easingOut ? CircInOut(t) : easingIn ? CircIn(t) : CircOut(t),
    Ease.Back => easingIn && easingOut ? BackInOut(t) : easingIn ? BackIn(t) : BackOut(t),
    Ease.Elastic => easingIn && easingOut ? ElasticInOut(t) : easingIn ? ElasticIn(t) : ElasticOut(t),
    Ease.Bounce => easingIn && easingOut ? BounceInOut(t) : easingIn ? BounceIn(t) : BounceOut(t),
    _ => t
    };

    private static float SineIn(float t) => 1.0f - Mathf.Cos(t * Mathf.PI / 2.0f);
    private static float SineOut(float t) => Mathf.Sin(t * Mathf.PI / 2.0f);
    private static float SineInOut(float t) => 0.5f * (1.0f - Mathf.Cos(Mathf.PI * t));

    private static float QuadIn(float t) => t * t;
    private static float QuadOut(float t) => 1.0f - (1.0f - t) * (1.0f - t);
    private static float QuadInOut(float t) => t < 0.5f ? 2.0f * t * t : 1.0f - Mathf.Pow(-2.0f * t + 2.0f, 2.0f) / 2.0f;

    private static float CubicIn(float t) => t * t * t;
    private static float CubicOut(float t) => --t * t * t + 1.0f;
    private static float CubicInOut(float t) => t < 0.5f ? 4.0f * t * t * t : 1.0f - Mathf.Pow(-2.0f * t + 2.0f, 3.0f) / 2.0f;

    private static float QuartIn(float t) => t * t * t * t;
    private static float QuartOut(float t) => 1.0f - (--t * t * t * t);
    private static float QuartInOut(float t) => (t *= 2.0f) < 1.0f ? 0.5f * t * t * t * t : -0.5f * ((t -= 2.0f) * t * t * t - 2.0f);

    private static float QuintIn(float t) => t * t * t * t * t;
    private static float QuintOut(float t) => --t * t * t * t * t + 1.0f;
    private static float QuintInOut(float t) => t < 0.5f ? 16.0f * t * t * t * t * t : 1.0f - Mathf.Pow(-2.0f * t + 2.0f, 5.0f) / 2.0f;

    private static float ExpoIn(float t) => t == 0.0f ? 0.0f : Mathf.Pow(2.0f, 10.0f * t - 10.0f);
    private static float ExpoOut(float t) => t == 1.0f ? 1.0f : 1.0f - Mathf.Pow(2.0f, -10.0f * t);
    private static float ExpoInOut(float t) => t == 0.0f ? 0.0f
    : t == 1.0f ? 1.0f
    : t < 0.5f ? Mathf.Pow(2.0f, 20.0f * t - 10.0f) / 2.0f
    : (2.0f - Mathf.Pow(2.0f, -20.0f * t + 10.0f)) / 2.0f;

    private static float CircIn(float t) => 1.0f - Mathf.Sqrt(1.0f - Mathf.Pow(t, 2.0f));
    private static float CircOut(float t) => Mathf.Sqrt(1.0f - Mathf.Pow(t - 1.0f, 2.0f));
    private static float CircInOut(float t) => t < 0.5f ? (1.0f - Mathf.Sqrt(1.0f - Mathf.Pow(2.0f * t, 2.0f))) / 2.0f
    : (Mathf.Sqrt(1.0f - Mathf.Pow(-2.0f * t + 2.0f, 2.0f)) + 1.0f) / 2.0f;

    private static float BackIn(float t) => C3 * t * t * t - C1 * t * t;
    private static float BackOut(float t) => 1.0f + C3 * Mathf.Pow(t - 1.0f, 3.0f) + C1 * Mathf.Pow(t - 1.0f, 2.0f);
    private static float BackInOut(float t) => t < 0.5f ? Mathf.Pow(2.0f * t, 2.0f) * ((C2 + 1.0f) * 2.0f * t - C2) / 2.0f
    : (Mathf.Pow(2.0f * t - 2.0f, 2.0f) * ((C2 + 1.0f) * (t * 2.0f - 2.0f) + C2) + 2.0f) / 2.0f;

    private static float ElasticIn(float t) => -Mathf.Pow(2.0f, 10.0f * t - 10.0f) * Mathf.Sin((t * 10.0f - 10.75f) * C4);
    private static float ElasticOut(float t) => Mathf.Pow(2.0f, -10.0f * t) * Mathf.Sin((t * 10.0f - 0.75f) * C4) + 1.0f;
    private static float ElasticInOut(float t) => t < 0.5f ? -(Mathf.Pow(2.0f, 20.0f * t - 10.0f) * Mathf.Sin((20.0f * t - 11.125f) * C5)) / 2f
    : Mathf.Pow(2.0f, -20.0f * t + 10.0f) * Mathf.Sin((20.0f * t - 11.125f) * C5) / 2.0f + 1.0f;

    private static float BounceIn(float t) => 1.0f - BounceOut(1.0f - t);
    private static float BounceOut(float t)
    {
    if (t < 1.0f / 2.75f)
    return 7.5625f * t * t;

    if (t < 2.0f / 2.75f)
    return 7.5625f * (t -= 1.5f / 2.75f) * t + 0.75f;

    if (t < 2.5f / 2.75f)
    return 7.5625f * (t -= 2.25f / 2.75f) * t + 0.9375f;

    return 7.5625f * (t -= 2.625f / 2.75f) * t + 0.984375f;
    }
    private static float BounceInOut(float t) => t < 0.5f ? BounceIn(t * 2.0f) * 0.5f : BounceOut(t * 2.0f - 1.0f) * 0.5f + 0.5f;

    private const float C1 = 1.70158f;
    private const float C2 = C1 * 1.525f;
    private const float C3 = C1 + 1.0f;
    private const float C4 = 2.0f * Mathf.PI / 3.0f;
    private const float C5 = 2.0f * Mathf.PI / 4.5f;
    }
    }