Created
November 17, 2016 19:23
-
-
Save justonia/d500e5ce0f8d28cb6713a78491587d92 to your computer and use it in GitHub Desktop.
State utility
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 UnityEngine; | |
using System; | |
/// <summary> | |
/// The State class is a specialized StateMachineBehaviour that provides an event interface for | |
/// the messages that Unity calls. Perhaps more importantly, States communicate with the next state | |
/// (when transitioning into something else) or the previous state (when transitioning from | |
/// something else). This allows the end-programmer to utilize transition times without losing the | |
/// reliability of events that can signal changes in control contexts (otherwise there are overlaps | |
/// when 2 control contexts are simultaneously valid). | |
/// </summary> | |
public abstract class State : StateMachineBehaviour | |
{ | |
public string Name = ""; | |
[NonSerialized] public StateMachineEvent StateEnter = new StateMachineEvent(); | |
[NonSerialized] public StateMachineEvent StateUpdate = new StateMachineEvent(); | |
[NonSerialized] public StateMachineEvent StateExit = new StateMachineEvent(); | |
[NonSerialized] public StateMachineEvent ControlEnter = new StateMachineEvent(); | |
[NonSerialized] public StateMachineEvent ControlUpdate = new StateMachineEvent(); | |
[NonSerialized] public StateMachineEvent ControlExit = new StateMachineEvent(); | |
public StateMachineBase StateMachine { get; private set; } | |
public bool IsActive { get { return StateMachine.CurrentState == this; }} | |
protected abstract StateMachineBase GetOrCreateStateMachine(Animator animator, int layerIndex); | |
/// <summary> | |
/// Called when the state begins. | |
/// </summary> | |
/// <param name="animator"></param> | |
/// <param name="stateInfo"></param> | |
/// <param name="layerIndex"></param> | |
public override void OnStateEnter( Animator animator, AnimatorStateInfo stateInfo, int layerIndex ) | |
{ | |
// Get a ref to the StateMachine if one isn't set already. | |
if ( StateMachine == null ) | |
{ | |
StateMachine = GetOrCreateStateMachine(animator, layerIndex); | |
} | |
if (StateMachine == null) | |
{ | |
// If StateMachine is still null then we have a problem. | |
LogStateError("OnStateEnter", "State requires a StateMachine at layer index {0} to support control context callbacks! Control contest events will not be emitted and OnControlEnter(), OnControlUpdate() and OnControlExit() will not be called until a StateMachine is available.", layerIndex ); | |
return; | |
} | |
if (StateMachine.IsDebugLoggingMechanimStates) | |
{ | |
LogStateDebug("OnStateEnter", null); | |
} | |
// Emit an event to signal this state enter to other objects. | |
StateEnter.Invoke( this, animator, stateInfo, layerIndex ); | |
// This state is now entering. | |
StateMachine.SetEnteringState( this ); | |
// Notify the previous state that the next state (this one) has entered. | |
if (StateMachine.DeferredStateExit != null) | |
{ | |
StateMachine.DeferredStateExit.OnControlExit( animator, stateInfo, layerIndex); | |
OnControlEnter( animator, stateInfo, layerIndex ); | |
StateMachine.SetDeferredStateExit( null ); | |
StateMachine.SetExitingState(null); | |
} | |
else if ( StateMachine.CurrentState != null ) | |
{ | |
StateMachine.CurrentState.OnControlExit( animator, stateInfo, layerIndex ); | |
} | |
else if ( !StateMachine.IsStarted ) | |
{ | |
// If CurrentState is null, then the StateMachine just started up and we need to | |
// set it to the current state and start the control context handlers firing. | |
StateMachine.SetIsStarted(); | |
StateMachine.SetCurrentState( this ); | |
OnControlEnter( animator, stateInfo, layerIndex ); | |
} | |
} | |
/// <summary> | |
/// Called when the transition from the previous state has ended. Use this to set up control | |
/// event listeners or otherwise configure your control context for this state. | |
/// </summary> | |
/// <param name="animator"></param> | |
/// <param name="stateInfo"></param> | |
/// <param name="layerIndex"></param> | |
public virtual void OnControlEnter( Animator animator, AnimatorStateInfo stateInfo, int layerIndex ) | |
{ | |
// TODO: MISSING exiting state | |
// This method shouldn't run if no StateMachine is available. | |
if ( StateMachine == null ) | |
{ | |
return; | |
} | |
// This state is no longer entering -- it is now the current state. | |
StateMachine.SetEnteringState( null ); | |
StateMachine.SetCurrentState( this ); | |
LogStateDebug("OnControlEnter", null); | |
// Emit an event to signal this state enter to other objects. | |
ControlEnter.Invoke( this, animator, stateInfo, layerIndex ); | |
} | |
/// <summary> | |
/// Called each frame while the state is active. | |
/// </summary> | |
/// <param name="animator"></param> | |
/// <param name="stateInfo"></param> | |
/// <param name="layerIndex"></param> | |
public override void OnStateUpdate( Animator animator, AnimatorStateInfo stateInfo, int layerIndex ) | |
{ | |
if ( StateMachine.IsDebugLoggingUpdate && StateMachine.IsDebugLoggingMechanimStates) | |
{ | |
LogStateDebug("OnStateUpdate", null); | |
} | |
// Emit an event to signal this state update to other objects. | |
StateUpdate.Invoke( this, animator, stateInfo, layerIndex ); | |
// Drive the special update function below. It only executes when this state is the only | |
// active state. As soon as any transition begins it no longer runs. | |
if ( StateMachine != null ) | |
{ | |
if ( StateMachine.CurrentState == this ) | |
{ | |
OnControlUpdate( animator, stateInfo, layerIndex ); | |
} | |
} | |
} | |
/// <summary> | |
/// Called each frame while the state is the only active state. Use this to execute control | |
/// polling logic. | |
/// </remarks> | |
/// <param name="animator"></param> | |
/// <param name="stateInfo"></param> | |
/// <param name="layerIndex"></param> | |
public virtual void OnControlUpdate( Animator animator, AnimatorStateInfo stateInfo, int layerIndex ) | |
{ | |
// This method shouldn't run if no StateMachine is available. | |
if ( StateMachine == null ) | |
{ | |
return; | |
} | |
if ( StateMachine.IsDebugLoggingUpdate) | |
{ | |
LogStateDebug("OnControlUpdate", null); | |
} | |
// Emit an event to signal this state update to other objects. | |
ControlUpdate.Invoke( this, animator, stateInfo, layerIndex ); | |
} | |
/// <summary> | |
/// Called when the state ends. | |
/// </summary> | |
/// <param name="animator"></param> | |
/// <param name="stateInfo"></param> | |
/// <param name="layerIndex"></param> | |
public override void OnStateExit( Animator animator, AnimatorStateInfo stateInfo, int layerIndex ) | |
{ | |
// Emit an event to signal this state exit to other objects. | |
StateExit.Invoke( this, animator, stateInfo, layerIndex ); | |
if ( StateMachine != null ) | |
{ | |
if ( StateMachine.EnteringState == null ) | |
{ | |
if (StateMachine.IsDebugLoggingMechanimStates) | |
{ | |
LogStateDebug("OnStateExit", "scheduling deferring handling"); | |
} | |
StateMachine.SetCurrentState( null ); | |
StateMachine.SetExitingState( this ); | |
StateMachine.SetDeferredStateExit( this ); | |
} | |
else | |
{ | |
if (StateMachine.IsDebugLoggingMechanimStates) | |
{ | |
LogStateDebug("OnStateExit", null); | |
} | |
// Notify the next state that the previous state (this one) has exited. | |
if ( StateMachine.EnteringState != null ) | |
{ | |
StateMachine.EnteringState.OnControlEnter( animator, stateInfo, layerIndex ); | |
} | |
// This state has now finished exiting. | |
StateMachine.SetExitingState( null ); | |
} | |
} | |
} | |
/// <summary> | |
/// Called when the transition from the next state has begins. Use this to tear down control | |
/// event listeners or otherwise clean up your control context for this state. | |
/// </summary> | |
/// <param name="animator"></param> | |
/// <param name="stateInfo"></param> | |
/// <param name="layerIndex"></param> | |
public virtual void OnControlExit( Animator animator, AnimatorStateInfo stateInfo, int layerIndex ) | |
{ | |
// This method shouldn't run if no StateMachine is available. | |
if ( StateMachine == null ) | |
{ | |
return; | |
} | |
// This state is no longer the current state -- it has begun exiting. | |
StateMachine.SetCurrentState( null ); | |
StateMachine.SetExitingState( this ); | |
LogStateDebug("OnControlExit", null); | |
// Emit an event to signal this state exit to other objects. | |
ControlExit.Invoke( this, animator, stateInfo, layerIndex ); | |
} | |
protected void LogStateDebug(string method, string fmt, params object[] args) | |
{ | |
if (StateMachine.IsDebugLogging) | |
{ | |
Debug.LogFormat("({2}, f:{0}, l:{1}] {3}.{4}: {5} -- exiting: {6}, entering: {7}", | |
Time.frameCount, | |
StateMachine.layerIndex, | |
StateMachine.gameObject.name, | |
Name, | |
method, | |
string.IsNullOrEmpty(fmt) ? "" : string.Format(fmt, args), | |
StateMachine.ExitingState != null ? StateMachine.ExitingState.Name : "<null>", | |
StateMachine.EnteringState != null ? StateMachine.EnteringState.Name : "<null>"); | |
} | |
} | |
protected void LogStateError(string method, string fmt, params object[] args) | |
{ | |
if (StateMachine.IsDebugLogging) | |
{ | |
Debug.LogErrorFormat("({2}, f:{0}, l:{1}) {3}.{4}: {5} -- exiting: {6}, entering: {7}", | |
Time.frameCount, | |
StateMachine.layerIndex, | |
StateMachine.gameObject.name, | |
Name, | |
method, | |
string.IsNullOrEmpty(fmt) ? "" : string.Format(fmt, args), | |
StateMachine.ExitingState != null ? StateMachine.ExitingState.Name : "<null>", | |
StateMachine.EnteringState != null ? StateMachine.EnteringState.Name : "<null>"); | |
} | |
} | |
} | |
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; | |
using System.Linq; | |
using UnityEngine; | |
using UnityEngine.Events; | |
/// <summary> | |
/// An event used to pass information about the state machine and the state in question. | |
/// </summary> | |
[Serializable] public class StateMachineEvent : UnityEvent<State, Animator, AnimatorStateInfo, int> { } | |
/// <summary> | |
/// The StateMachine class is used by States to communicate to one another and supports the action | |
/// of other game objects in response to events signaling state changes. | |
/// </summary> | |
public class StateMachine<T> : StateMachineBase | |
where T : State | |
{ | |
public virtual void Awake() | |
{ | |
Animator = GetComponent<Animator>(); | |
if ( Animator == null ) | |
{ | |
Debug.LogError( "StateMachine requires an animator component!" ); | |
return; | |
} | |
Animator | |
.GetBehaviours<T>() | |
.ToList() | |
.ForEach( state => { | |
state.ControlEnter.AddListener( ControlEnter.Invoke ); | |
state.ControlUpdate.AddListener( ControlUpdate.Invoke ); | |
state.ControlExit.AddListener( ControlExit.Invoke ); | |
state.StateEnter.AddListener( StateEnter.Invoke ); | |
state.StateUpdate.AddListener( StateUpdate.Invoke ); | |
state.StateExit.AddListener( StateExit.Invoke ); | |
} ); | |
} | |
public virtual void OnDestroy() | |
{ | |
if ( Animator == null ) | |
{ | |
return; | |
} | |
Animator | |
.GetBehaviours<T>() | |
.ToList() | |
.ForEach( state => { | |
state.ControlEnter.RemoveListener( ControlEnter.Invoke ); | |
state.ControlUpdate.RemoveListener( ControlUpdate.Invoke ); | |
state.ControlExit.RemoveListener( ControlExit.Invoke ); | |
state.StateEnter.RemoveListener( StateEnter.Invoke ); | |
state.StateUpdate.RemoveListener( StateUpdate.Invoke ); | |
state.StateExit.RemoveListener( StateExit.Invoke ); | |
} ); | |
} | |
} |
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 UnityEngine; | |
using System.Collections; | |
public abstract class StateMachineBase : MonoBehaviour | |
{ | |
public bool IsDebugLogging = false; | |
public bool IsDebugLoggingMechanimStates = false; | |
public bool IsDebugLoggingUpdate = false; | |
public int layerIndex = 0; | |
public Animator Animator { get; protected set; } | |
public State CurrentState { get; protected set; } | |
public State EnteringState { get; protected set; } | |
public State ExitingState { get; protected set; } | |
public State DeferredStateExit { get; protected set; } | |
public bool IsStarted { get; protected set; } | |
public bool IsTransitioning { get; protected set; } | |
public int LayerIndex { get { return layerIndex; }} | |
public StateMachineEvent ControlEnter = new StateMachineEvent(); | |
public StateMachineEvent ControlUpdate = new StateMachineEvent(); | |
public StateMachineEvent ControlExit = new StateMachineEvent(); | |
public StateMachineEvent StateEnter = new StateMachineEvent(); | |
public StateMachineEvent StateUpdate = new StateMachineEvent(); | |
public StateMachineEvent StateExit = new StateMachineEvent(); | |
public void SetCurrentState( State state ) | |
{ | |
CurrentState = state; | |
IsTransitioning = true; | |
} | |
public void SetEnteringState( State state ) | |
{ | |
EnteringState = state; | |
} | |
public void SetExitingState( State state ) | |
{ | |
ExitingState = state; | |
IsTransitioning = false; | |
} | |
public void SetIsStarted() | |
{ | |
IsStarted = true; | |
} | |
public void SetDeferredStateExit( State state ) | |
{ | |
DeferredStateExit = state; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment