Skip to content

Instantly share code, notes, and snippets.

@xanathar
Created March 2, 2016 21:16
Show Gist options
  • Save xanathar/d5dd1a78c33fa5ccab0d to your computer and use it in GitHub Desktop.
Save xanathar/d5dd1a78c33fa5ccab0d to your computer and use it in GitHub Desktop.
Animator state machine for simple, stateful, immediate mode 2D animations in Unity3D
using System;
using System.Collections.Generic;
using UnityEngine;
public struct StateAnimatorData<T>
{
public StateAnimator<T> Animator { get; internal set; }
public StateAnimatorHandler<T> AnimatorState { get; internal set; }
public T State { get; internal set; }
public float StateTime { get; internal set; }
public float AbsoluteTime { get; internal set; }
public float StateProgress { get; internal set; }
}
public class StateAnimatorHandler<T>
{
private List<Action<StateAnimatorData<T>>> m_CallbackList = new List<Action<StateAnimatorData<T>>>();
internal StateAnimatorHandler (T state)
{
State = state;
}
public T State { get; private set; }
public float? Duration { get; private set; }
public T ExpireState { get; private set; }
public bool HasExpireState { get; private set; }
public StateAnimatorHandler<T> Calls(Action<StateAnimatorData<T>> callback)
{
m_CallbackList.Add(callback);
return this;
}
public StateAnimatorHandler<T> ExpireAfter(float duration)
{
Duration = duration;
return this;
}
public StateAnimatorHandler<T> ExpireTo(T state)
{
ExpireState = state;
HasExpireState = true;
return this;
}
public void Trigger(StateAnimatorData<T> data)
{
foreach(var callback in m_CallbackList)
callback(data);
}
}
public class StateAnimator<T>
{
Dictionary<T, StateAnimatorHandler<T>> m_StateHandlers = new Dictionary<T, StateAnimatorHandler<T>>();
T m_CurrentState = default(T);
StateAnimatorHandler<T> m_CurrentHandler;
float m_TimeBase = -1f;
float m_StateTime = -1f;
public StateAnimatorHandler<T> Add(T state)
{
var animstate = new StateAnimatorHandler<T>(state);
m_StateHandlers.Add(state, animstate);
return animstate;
}
public void Process()
{
float totalTime = Time.realtimeSinceStartup;
float stateTime = (totalTime - m_StateTime);
float stateProgress = 0f;
if (m_CurrentHandler.Duration.HasValue)
{
stateProgress = Mathf.Clamp(stateTime / m_CurrentHandler.Duration.Value, 0f, 1f);
}
var data = new StateAnimatorData<T>()
{
Animator = this,
AnimatorState = m_CurrentHandler,
State = m_CurrentState,
StateTime = stateTime,
AbsoluteTime = totalTime,
StateProgress = stateProgress
};
m_CurrentHandler.Trigger(data);
if (stateProgress >= 1f && m_CurrentHandler.HasExpireState)
{
CurrentState = m_CurrentHandler.ExpireState;
}
}
public T CurrentState
{
get { return m_CurrentState; }
set
{
Debug.LogFormat("Changing state from {0} to {1}", m_CurrentState, value);
if (m_TimeBase < 0)
m_TimeBase = Time.realtimeSinceStartup;
m_StateTime = Time.realtimeSinceStartup;
m_CurrentHandler = m_StateHandlers[value];
m_CurrentState = value;
}
}
}
@xanathar
Copy link
Author

xanathar commented Mar 2, 2016

Sample usage:

    // State definition
    private enum LevelState
    {
        Play,
        FadeIn,
        FadeOut,
        WaitLoad
    }


    // Initialization:
    StateAnimator<LevelState> m_Animator;

    void Awake()
    {
        m_Animator = new StateAnimator<LevelState>();

        m_Animator.Add(LevelState.FadeIn)
            .Calls(d => Utils.DrawFullScreenQuad(new Color(0f, 0f, 0f, 1f - d.StateProgress)))
            .ExpireAfter(1f)
            .ExpireTo(LevelState.Play);

        m_Animator.Add(LevelState.FadeOut)
            .Calls(d => Utils.DrawFullScreenQuad(new Color(0f, 0f, 0f, d.StateProgress)))
            .ExpireAfter(1f)
            .ExpireTo(LevelState.WaitLoad);

        m_Animator.Add(LevelState.WaitLoad)
            .Calls(d => Utils.DrawFullScreenQuad(new Color(0f, 0f, 0f, 1f)));

        m_Animator.Add(LevelState.Play);

        m_Animator.CurrentState = LevelState.FadeIn;
    }

    // Call to process animations.
    // Unless needed (as in this case), can be done in Update instead of OnRenderObject.
    void OnRenderObject()
    {   
        m_Animator.Process();
    }


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment