//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // _____ _ _____ // |_ _|_|___ _ _ |_ _|_ _ _ ___ ___ ___ // | | | | | | | | | | | | | -_| -_| | // |_| |_|_|_|_ | |_| |_____|___|___|_|_| // |___| // 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 // // 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 { /// State of a Tween operation. public enum TweenState { Running, Paused, Finished } /// Execution modes of a Tween operation. public enum TweenLoop { // Just once. Start over from the beginning. Back and forth loop. Once, Loop, YoYo, } /// Interface of a tween. public interface ITween { /// Tween status. TweenState State { get; } /// Update the Tween operation. void Update(); } /// Generic interface of a tween. public interface ITween : ITween where T : struct { /// Current value. T Value { get; } /// Tween operation progress (0, 1). float Progress { get; } /// Executions counter. int ExecutionCount { get; } /// Tween status. TweenState State { get; } /// Execute a tween operation. Tween Start(); /// Pause the tween. void Pause(); /// Continue the tween. void Resume(); /// Finish the Tween operation. /// Move the value at the end or leave it as this. void Stop(bool moveToEnd = true); /// Sets tween value at origin and time to 0. void Reset(); /// Update the Tween operation. void Update(); } /// Tween operation. If it is created manually, Update() must be called. public abstract class Tween : ITween where T : struct { /// public TweenState State { get; private set; } = TweenState.Paused; /// public T Value { get; private set; } /// public float Progress { get; private set; } /// public int ExecutionCount { get; private set; } /// Time that the operation takes. public float Time { get; private set; } /// Does the Tween depend on another object? 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> updateFunction; private Action> endFunction; private Func, bool> condition; private readonly Func, T, T, float, bool, T> interpolationFunction; // Tween, start, end, progress, clamp. /// Constructor. /// Interpolation function. protected Tween(Func, T, T, float, bool, T> interpolationFunction) => this.interpolationFunction = interpolationFunction; /// Initial value. Use it only to create a new Tween. /// This. public Tween Origin(T start) { origin = start; return this; } /// Final value. Use it only to create a new Tween. /// This. public Tween Destination(T end) { destination = end; return this; } /// Time to execute the operation, must be greater than 0. Use it only to create a new Tween. /// This. public Tween Duration(float duration) { this.duration = Mathf.Max(duration, 0.0f); return this; } /// Execution mode. Use it only to create a new Tween. /// This. public Tween Loop(TweenLoop loop) { this.loop = loop; return this; } /// Easing function. Overwrite the In and Out functions. Use it only to create a new Tween. /// Easing function. /// This. public Tween Easing(Ease ease) { easeIn = easeOut = ease; return this; } /// Easing In function. Use it only to create a new Tween. /// Easing function. /// This. public Tween EasingIn(Ease ease) { easeIn = ease; return this; } /// Easing Out function. Use it only to create a new Tween. /// Easing function. /// This. public Tween EasingOut(Ease ease) { easeOut = ease; return this; } /// Update callback. Use it to apply the Tween values. /// Callback. /// This. public Tween OnUpdate(Action> updateCallback) { updateFunction = updateCallback; return this; } /// Executed at the end of the operation (optional). /// Callback. /// This. public Tween OnEnd(Action> endCallback) { endFunction = endCallback; return this; } /// Condition of progress, stops if the operation is not true (optional). /// Condition function. /// This. public Tween Condition(Func, bool> condition) { this.condition = condition; return this; } /// Limits the values of the interpolation to the range [0, 1]. /// Clamp. /// This. public Tween Clamp(bool clamp) { this.clamp = clamp; return this; } /// /// I set an object as the 'owner' of the Tween. If the object is destroyed, the Tween ends and is destroyed. /// /// Owner /// This. public Tween Owner(object owner) { IsOwned = owner != null; this.owner = owner; return this; } /// public Tween 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; } /// public void Pause() => State = TweenState.Paused; /// public void Resume() => State = TweenState.Running; /// public void Stop(bool moveToEnd = true) { if (State != TweenState.Finished) { State = TweenState.Finished; if (moveToEnd == true) { currentTime = duration; UpdateValue(); } endFunction?.Invoke(this); } } /// public void Reset() { currentTime = 0.0f; Value = origin; } /// 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); } } /// /// Tweens manager, update tweens and delete those that have already ended. /// It is not necessary if you are in charge of maintaining your Tweens ;) /// public sealed class TinyTween : MonoBehaviour { public static TinyTween Instance => LazyInstance.Value; private static readonly Lazy LazyInstance = new(CreateSingleton); private readonly List tweens = new(); /// Add an existing tween. /// New tween /// Tween. public ITween Add(ITween tween) { tweens.Add(tween); return tween; } private static TinyTween CreateSingleton() { GameObject ownerObject = new("TinyTween"); TinyTween instance = ownerObject.AddComponent(); 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(); } /// Tween float. public sealed class TweenFloat : Tween { /// Create a Tween float and add it to the TinyTween manager. /// Tween. public static Tween Create() => TinyTween.Instance.Add(new TweenFloat()) as Tween; private static float Lerp(ITween 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) { } } /// Tween Vector2. public sealed class TweenVector2 : Tween { /// Create a Tween Vector2 and add it to the TinyTween manager. /// Tween. public static Tween Create() => TinyTween.Instance.Add(new TweenVector2()) as Tween; private static Vector2 Lerp(ITween 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) { } } /// Tween Vector3. public sealed class TweenVector3 : Tween { /// Create a Tween Vector3 and add it to the TinyTween manager. /// Tween. public static Tween Create() => TinyTween.Instance.Add(new TweenVector3()) as Tween; private static Vector3 Lerp(ITween 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) { } } /// Tween Vector4. public sealed class TweenVector4 : Tween { /// Create a Tween Vector4 and add it to the TinyTween manager. /// Tween. public static Tween Create() => TinyTween.Instance.Add(new TweenVector4()) as Tween; private static Vector4 Lerp(ITween 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) { } } /// Tween Quaternion. public sealed class TweenQuaternion : Tween { /// Create a Tween Quaternion and add it to the TinyTween manager. /// Tween. public static Tween Create() => TinyTween.Instance.Add(new TweenQuaternion()) as Tween; private static Quaternion Lerp(ITween 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) { } } /// Tween Color. public sealed class TweenColor : Tween { /// Create a Tween Color and add it to the TinyTween manager. /// Tween. public static Tween Create() => TinyTween.Instance.Add(new TweenColor()) as Tween; private static Color Lerp(ITween 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) { } } /// Extensions to make it easy to use TinyTween. public static class TweenExtensions { /// Create and execute a tween. /// Self. /// Start value. /// End value. /// Time in seconds. /// Easing. /// Tween. public static Tween 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(); /// Create and execute a tween, using as initial value the current value. /// Self. /// End value. /// Time in seconds. /// Easing. /// Tween. public static Tween Tween(this float self, float destination, float duration, Ease ease) => Tween(self, self, destination, duration, ease); /// Create and execute a tween. /// Self. /// Start value. /// End value. /// Time in seconds. /// Easing. /// Tween. public static Tween 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(); /// Create and execute a tween, using as initial value the current value. /// Self. /// End value. /// Time in seconds. /// Easing. /// Tween. public static Tween Tween(this Vector3 self, Vector3 destination, float duration, Ease ease) => Tween(self, self, destination, duration, ease); /// Create and execute a tween. /// Self. /// Start value. /// End value. /// Time in seconds. /// Easing. /// Tween. public static Tween 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(); /// Create and execute a tween, using as initial value the current value. /// Self. /// End value. /// Time in seconds. /// Easing. /// Tween. public static Tween Tween(this Quaternion self, Quaternion destination, float duration, Ease ease) => Tween(self, self, destination, duration, ease); /// Create and execute a tween. /// Self. /// Start value. /// End value. /// Time in seconds. /// Easing. /// Tween. public static Tween 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(); /// Create and execute a tween, using as initial value the current value. /// Self. /// End value. /// Time in seconds. /// Easing. /// Tween. public static Tween Tween(this Color self, Color destination, float duration, Ease ease) => Tween(self, self, destination, duration, ease); /// Moves a Transform. /// Self. /// Start position. /// End position. /// Time in seconds. /// Easing. /// Tween. public static Tween 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(); /// Moves a Transform, using its current position as origin. /// Self. /// End position. /// Time in seconds. /// Easing. /// Tween. public static Tween TweenMove(this Transform self, Vector3 destination, float duration, Ease ease) => TweenMove(self, self.position, destination, duration, ease); /// Scale a Transform. /// Self. /// Start scale. /// End scale. /// Time in seconds. /// Easing. /// Tween. public static Tween 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(); /// Scale a Transform, using its current scale as origin. /// Self. /// End scale. /// Time in seconds. /// Easing. /// Tween. public static Tween TweenScale(this Transform self, Vector3 destination, float duration, Ease ease) => TweenScale(self, self.localScale, destination, duration, ease); /// Rotate a Transform. /// Self. /// Start rotation. /// End rotation. /// Time in seconds. /// Easing. /// Tween. public static Tween 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(); /// Rotate a Transform, using its current rotation as origin. /// Self. /// End rotation. /// Time in seconds. /// Easing. /// Tween. public static Tween TweenRotation(this Transform self, Quaternion destination, float duration, Ease ease) => TweenRotation(self, self.rotation, destination, duration, ease); /// Moves a GameObject. /// Self. /// Start position. /// End position. /// Time in seconds. /// Easing. /// Tween. public static Tween TweenMove(this GameObject self, Vector3 origin, Vector3 destination, float duration, Ease ease) => TweenMove(self.transform, origin, destination, duration, ease); /// Moves a GameObject, using its current position as origin. /// Self. /// End position. /// Time in seconds. /// Easing. /// Tween. public static Tween TweenMove(this GameObject self, Vector3 destination, float duration, Ease ease) => TweenMove(self.transform, self.transform.position, destination, duration, ease); /// Scale a GameObject. /// Self. /// Start scale. /// End scale. /// Time in seconds. /// Easing. /// Tween. public static Tween TweenScale(this GameObject self, Vector3 origin, Vector3 destination, float duration, Ease ease) => TweenScale(self.transform, origin, destination, duration, ease); /// Scale a GameObject, using its current scale as origin. /// Self. /// End scale. /// Time in seconds. /// Easing. /// Tween. public static Tween TweenScale(this GameObject self, Vector3 destination, float duration, Ease ease) => TweenScale(self.transform, self.transform.localScale, destination, duration, ease); /// Rotate a GameObject. /// Self. /// Start rotation. /// End rotation. /// Time in seconds. /// Easing. /// Tween. public static Tween TweenRotation(this GameObject self, Quaternion origin, Quaternion destination, float duration, Ease ease) => TweenRotation(self.transform, origin, destination, duration, ease); /// Rotate a GameObject, using its current rotation as origin. /// Self. /// End rotation. /// Time in seconds. /// Easing. /// Tween. public static Tween TweenRotation(this GameObject self, Quaternion destination, float duration, Ease ease) => TweenRotation(self.transform, self.transform.rotation, destination, duration, ease); /// Change the color of a Material. /// Self. /// Start color. /// End color. /// Time in seconds. /// Easing. /// The name of the color variable. /// Tween. public static Tween 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(); /// Change the color of a Material, using its current color as origin. /// Self. /// End color. /// Time in seconds. /// Easing. /// The name of the color variable. /// Tween. public static Tween TweenColor(this Material self, Color destination, float duration, Ease ease, string name = "_Color") => TweenColor(self, self.GetColor(name), destination, duration, ease, name); } /// Types of Easing functions. See https://easings.net public enum Ease { None, Linear, Sine, Quad, Cubic, Quart, Quint, Expo, Circ, Back, Elastic, Bounce, } /// Easing functions. 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; } }