Skip to content

Instantly share code, notes, and snippets.

@nukadelic
Created February 27, 2022 23:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nukadelic/4dc0c27ae8d3b4661d1b8b6e41686179 to your computer and use it in GitHub Desktop.
Save nukadelic/4dc0c27ae8d3b4661d1b8b6e41686179 to your computer and use it in GitHub Desktop.
Unity Tweener for MonoBehaviours
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