Last active
July 10, 2017 14:47
-
-
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.
This file contains hidden or 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
// 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