Skip to content

Instantly share code, notes, and snippets.

@mstevenson
Last active July 10, 2017 14:47
Show Gist options
  • Save mstevenson/4615512 to your computer and use it in GitHub Desktop.
Save mstevenson/4615512 to your computer and use it in GitHub Desktop.
Untested off-the-cuff example of a possible method-based state machine design for Unity.
// Defines the required parameters and return value type for a StateMachine state.
// Returns a bool representing whether or not the state has finished running.
public delegate bool StateDelegate ();
// To use, create an instance of StateMachine inside of a MonoBehaviour, load it up with
// references to state methods with ChangeState(), then call its Execute() method during the
// MonoBehaviour's Update cycle. An example MonoBehaviour is included at the bottom of this file.
public class StateMachine
{
// Keep track of the currently running state
enum State {
None,
Entering,
Running,
Exiting
}
State state = State.None;
// These states will be cached when calling ChangeState(). They'll be copied into
// currentStateMethod in succession as each state finishes running. The Execute()
// method will execute currentStateMethod on each update, running whatever method
// is stored there.
StateDelegate enter;
StateDelegate run;
StateDelegate exit;
// After being called by the Execute() method, the current state will be replaced with
// the next most appropriate state if the current state returns 'true', signifying that
// it has finished running.
StateDelegate currentStateMethod;
// A single state may be stored at any given time. If you need to queue more than
// one state, you could conceivably replace ChangeState() with AddState() and append
// the three state parameters to a list. As its currently written, changing the state
// immediatley calls the current 'exit' state, then overwrites the cached enter/run/exit
// states with these new states.
public void ChangeState (StateDelegate enter, StateDelegate run, StateDelegate exit)
{
bool isStateCurrentlyRunning = currentStateMethod != null;
// If a state is currently running, it should be allowed to gracefully exit
// before the next state takes over
if (isStateCurrentlyRunning) {
SwitchCurrentState (State.Exiting);
}
// Cache the given state values
this.enter = enter;
this.run = run;
this.exit = exit;
// If a state isn't currently running, we can immediately switch to our entering
// state using the state delegates we cached a few lines above
if (!isStateCurrentlyRunning) {
SwitchCurrentState (State.Entering);
}
}
// Call this during
public void Execute ()
{
if (currentStateMethod == null)
return;
// Execute the current state method
bool finished = currentStateMethod ();
// If we've reached the end of the current enter/run/exit, advance to the next one
if (finished) {
switch (state) {
case State.None:
SwitchCurrentState (State.Entering);
break;
case State.Entering:
SwitchCurrentState (State.Running);
enter = null;
break;
case State.Running:
SwitchCurrentState (State.Exiting);
run = null;
break;
case State.Exiting:
// If an Enter behavior exists, it must have been added by ChangeState. We should
// start running again from the top instead of coming to a halt.
if (enter != null) {
SwitchCurrentState (State.Entering);
} else {
SwitchCurrentState (State.None);
exit = null;
}
break;
}
}
}
// Utility method for performing the state delegate swapping logic based on an enum value
void SwitchCurrentState (State state)
{
this.state = state;
switch (state) {
case State.None:
currentStateMethod = null;
break;
case State.Entering:
currentStateMethod = this.enter;
break;
case State.Exiting:
currentStateMethod = this.exit;
break;
case State.Running:
currentStateMethod = this.run;
break;
}
}
}
// An example of how to embed a StateMachine in a Character
// and load a few locally defined state methods into it.
public class Character : MonoBehaviour
{
StateMachine stateMachine = new StateMachine ();
// All states conform to the StateDelegate method signature
// (no input parameters, and a bool return value)
bool EnterState ()
{
return true;
}
bool RunState ()
{
return true;
}
bool ExitState ()
{
return true;
}
// Unity callbacks for setting up an initial state and pumping the
// StateMachine on every render cycle
void Start ()
{
stateMachine.ChangeState (EnterState, RunState, ExitState);
}
void Update ()
{
stateMachine.Execute ();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment