Created
February 27, 2022 23:49
-
-
Save nukadelic/4dc0c27ae8d3b4661d1b8b6e41686179 to your computer and use it in GitHub Desktop.
Unity Tweener for MonoBehaviours
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using UnityEngine; | |
#region global component extension functions | |
public static class TweenEx | |
{ | |
/// <summary> Main tweener fucntion </summary> | |
public static Tween tween( this Component comp, float duration = 1f, Tween.EaseType ease = default, float delay = 0f, bool autoStart = true ) | |
{ | |
var _tween = comp.gameObject.AddComponent<Tween>(); | |
_tween.easeType = ease; | |
_tween.delay = delay; | |
_tween.duration = duration; | |
if (autoStart) _tween.Begin(); | |
else _tween.enabled = false; | |
return _tween; | |
} | |
public static Tween tween( this Component comp, float duration , float delay ) | |
{ | |
return comp.tween( duration, default, delay ); | |
} | |
/// <summary> Remove all active tweens in the current gameObject of the component, if invokeEnd is true it will invoke OnEnd event before destruction </summary> | |
public static void tweenClear( this Component comp, bool invokeEnd = false ) | |
{ | |
foreach (var t in comp.GetComponents<Tween>()) | |
{ | |
if (invokeEnd) t.End(); | |
// regadless of autoRemoveOnEnd , destroy component | |
t.Destroy(); | |
} | |
} | |
} | |
#endregion | |
public class Tween : MonoBehaviour | |
{ | |
public static void SplitT2( float t, out float t1, out float t2, float middle = 0.5f ) | |
{ | |
// this is based on a function i made here : https://www.desmos.com/calculator/lnyuf88aqw | |
var a = ( 1 + ( Mathf.Floor( t - middle ) % 2f ) ); | |
t1 = ( 1 - a ) * ( t - middle ) / middle + 1f; | |
t2 = a * (t - middle) / ( 1f - middle ); | |
} | |
static int globalCounter = 0; | |
// ... idk why i need this static list , but its here ?_? | |
// each tween is reponsible to add / remove itself to the list | |
// added tweens are active, thie list does not include idle or destroyed tweens | |
public static List<Tween> inputs = new List<Tween>(); | |
/// <summary> can be changed OnEnd just before removal </summary> | |
public bool autoDestroy = true; | |
/// <summary> This will only override the OnUpdate event input value , so the range of `this.value` var will stay the same | |
/// but u will get one minus value onUpdate </summary> | |
public bool reversed { get; private set; } = false; | |
public int uniqueIndex { get; private set; } = 0; | |
public EaseType easeType = default; | |
public float duration = 0.6f; | |
public float delay = 0f; | |
public int repeatCount = 0; | |
float timer = 0f; | |
bool destroyed = false; | |
float value = 0f; | |
Tween next; | |
private void Awake( ) | |
{ | |
uniqueIndex = ( globalCounter ++ ); | |
// normally we want auto distruction , im too lazy to make a single static mono that will pool animation struct data | |
if ( autoDestroy ) hideFlags = HideFlags.HideAndDontSave; | |
} | |
public void Begin() | |
{ | |
inputs.Add( this ); | |
enabled = true; | |
timer = 0f; | |
// note when using delay smaller then zero , the first on OnUdpate value will never be zero | |
// and instead start somewhere in the middle of the tween | |
if ( delay == 0 ) Update(); | |
} | |
void Update( ) | |
{ | |
var d = Mathf.Abs(duration) < Mathf.Epsilon ? 0.001f : Mathf.Abs( duration ); // prevent division by zero | |
var t = ( ( timer += Time.deltaTime ) - delay ) / d ; | |
var ease_t = Mathf.Clamp01( t ); // if delay is < 0 , t will start negative | |
value = Functions.Ease( easeType, ease_t ); | |
// we don't want to invoke update before dalay , in case there is a chain of tweens that edit the same value, so they won't override each other | |
if( t >= 0 ) onUpdate?.Invoke( reversed ? 1 - value : value); | |
if ( t >= 1 ) | |
{ | |
if( repeatCount -- == 0 ) End(); | |
else Begin(); | |
} | |
} | |
public void End() | |
{ | |
enabled = false; | |
// at this point value must equal to 1 ( we clamp it and check for equality in update ) | |
onEnd?.Invoke( ); | |
inputs.Remove( this ); | |
if( next != null ) next.Begin(); | |
if ( autoDestroy ) Destroy(); | |
} | |
public void Destroy() | |
{ | |
if( destroyed ) return; | |
destroyed = true; | |
onUpdate = null; | |
onEnd = null; | |
next = null; | |
Destroy( this ); | |
} | |
#region Chaininig methods | |
/// <summary> Prevent autoRemoveOnEnd and use this to get reveresed tween from 1 to 0 </summary> | |
public Tween Reverse() | |
{ | |
if ( destroyed ) Debug.LogError("pepsi"); | |
reversed = !reversed; | |
Begin(); | |
return this; | |
} | |
/// <summary> | |
/// Combine mubltiple tweens , use chain to auto start a new tween once the current one is complete | |
/// note function chanining will scope to new tweener | |
/// </summary> | |
public Tween Chain(float duration = 1f, Tween.EaseType ease = default, float delay = 0f) | |
{ | |
if ( destroyed ) Debug.LogError("pepsi"); | |
return (next = this.tween(duration, ease, delay, false)); | |
} | |
/// <summary> If you need to get access to this as a varialbe from inside a chain of functions </summary> | |
public Tween Self( out Tween reference ) => reference = this; | |
public Tween SetEase( Tween.EaseType ease ) { easeType = ease; return this; } | |
#endregion | |
#region Expose private data | |
public float Value => value; | |
public float Timer => timer; | |
public bool IsDead => destroyed; | |
#endregion | |
#region Events | |
public event System.Action onEnd; | |
public event System.Action<float> onUpdate; | |
public Tween OnEnd( System.Action callback ) | |
{ | |
onEnd += callback; | |
return this; | |
} | |
public Tween OnUpdate( System.Action<float> callback ) | |
{ | |
onUpdate += callback; | |
return this; | |
} | |
#endregion | |
#region Easing functions | |
// TODO: add more easing functions !! : https://gist.github.com/MattRix/feea68fd3dae16c760d6c665fd530d46 | |
public enum EaseType | |
{ | |
/// <summary> accelerating from zero velocity [default] </summary> | |
InQuad | |
/// <summary> decelerating to zero velocity </summary> | |
, OutQuad | |
/// <summary> acceleration until halfway, then deceleration </summary> | |
, InOutQuad | |
/// <summary> accelerating from zero velocity </summary> | |
, InCubic | |
/// <summary> decelerating to zero velocity </summary> | |
, OutCubic | |
/// <summary> acceleration until halfway, then deceleration </summary> | |
, InOutCubic | |
/// <summary> accelerating from zero velocity </summary> | |
, InQuart | |
/// <summary> decelerating to zero velocity </summary> | |
, OutQuart | |
/// <summary> acceleration until halfway, then deceleration </summary> | |
, InOutQuart | |
/// <summary> accelerating from zero velocity </summary> | |
, InQuint | |
/// <summary> decelerating to zero velocity </summary> | |
, OutQuint | |
/// <summary> acceleration until halfway, then deceleration </summary> | |
, InOutQuint | |
/// <summary> no easing, no acceleration </summary> | |
, Linear | |
} | |
public static class Functions | |
{ | |
public static float Ease( EaseType ease, float t ) | |
{ | |
switch( ease ) | |
{ | |
case EaseType.Linear: default: return t; | |
case EaseType.InQuad: return EaseInQuad( t ); | |
case EaseType.OutQuad: return EaseOutQuad( t ); | |
case EaseType.InOutQuad: return EaseInOutQuad( t ); | |
case EaseType.InCubic: return EaseInCubic( t ); | |
case EaseType.OutCubic: return EaseOutCubic( t ); | |
case EaseType.InOutCubic: return EaseInOutCubic( t ); | |
case EaseType.InQuart: return EaseInQuart( t ); | |
case EaseType.OutQuart: return EaseOutQuart( t ); | |
case EaseType.InOutQuart: return EaseInOutQuart( t ); | |
case EaseType.InQuint: return EaseInQuint( t ); | |
case EaseType.OutQuint: return EaseOutQuint( t ); | |
case EaseType.InOutQuint: return EaseInOutQuint( t ); | |
} | |
} | |
// ease math functions from : https://gist.github.com/gre/1650294 | |
public static float EaseInQuad( float t ) => t * t; | |
public static float EaseOutQuad( float t ) => t * ( 2 - t ); | |
public static float EaseInOutQuad( float t ) => t < .5f ? 2 * t * t : -1 + ( 4 - 2 * t ) * t; | |
public static float EaseInCubic( float t ) => t * t * t; | |
public static float EaseOutCubic( float t ) => ( --t ) * t * t + 1; | |
public static float EaseInOutCubic( float t ) => t < .5f ? 4 * t * t * t : ( t - 1 ) * ( 2 * t - 2 ) * ( 2 * t - 2 ) + 1; | |
public static float EaseInQuart( float t ) => t * t * t * t; | |
public static float EaseOutQuart( float t ) => 1 - ( --t ) * t * t * t; | |
public static float EaseInOutQuart( float t ) => t < .5f ? 8 * t * t * t * t : 1 - 8 * ( --t ) * t * t * t; | |
public static float EaseInQuint( float t ) => t * t * t * t * t; | |
public static float EaseOutQuint( float t ) => 1 + ( --t ) * t * t * t * t; | |
public static float EaseInOutQuint( float t ) => t < .5f ? 16 * t * t * t * t * t : 1 + 16 * ( --t ) * t * t * t * t; | |
} | |
#endregion | |
#if UNITY_EDITOR | |
#region Preview info for testing | |
[UnityEditor.CustomEditor(typeof(Tween))] | |
class TweenInfo : UnityEditor.Editor | |
{ | |
public override void OnInspectorGUI() | |
{ | |
var _tween = ( Tween ) target; | |
var _infoTextBox = | |
string.Format( "Value:\t{0:0.00}\n", _tween.value ) | |
+ string.Format( "Timer:\t{0:0.00}\n", _tween.timer) | |
+ string.Format( "Reversed:\t{0}\n", _tween.reversed ) | |
; | |
base.OnInspectorGUI(); | |
GUILayout.Space( 10 ); | |
UnityEditor.EditorGUILayout.HelpBox( _infoTextBox, UnityEditor.MessageType.None ); | |
} | |
} | |
#endregion | |
#endif | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment