Skip to content

Instantly share code, notes, and snippets.

@tiagosr
Last active August 29, 2015 14:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tiagosr/9b04d183d4c099e5eb1b to your computer and use it in GitHub Desktop.
Save tiagosr/9b04d183d4c099e5eb1b to your computer and use it in GitHub Desktop.
Unity Event/Delegate-based State Machine
using UnityEngine;
using UnityEngine.Events;
using System.Collections;
using System.Collections.Generic;
public class State {
public delegate void EnterStateEvent(State s, State from);
public delegate void ExitStateEvent(State s, State to);
public delegate void UpdateStateEvent(State s);
public delegate void Constructor(State s);
public event EnterStateEvent onEnter, onResume;
public event ExitStateEvent onExit, onSuspend;
public event UpdateStateEvent onUpdate;
public State(Constructor c) {
c(this);
}
public void Push(State state) { currentMachine.PushState(state); }
public void Pop() { currentMachine.PopState(); }
public void SwitchTo(State state) { currentMachine.SwitchState(state); }
internal void Enter(State s) { if(onEnter != null) onEnter(this, s); }
internal void Resume(State s) { if(onResume != null) onResume(this, s); }
internal void Exit(State s) { if(onExit != null) onExit(this, s); }
internal void Suspend(State s) { if(onSuspend != null) onSuspend(this, s); }
internal void Update() { if(onUpdate != null) onUpdate(this); }
internal StateMachine currentMachine;
}
public class StateMachine {
public Stack<State> states = new Stack<State>();
public StateMachine(State s) {
s.currentMachine = this;
states.Push(s);
}
public void PushState(State state) {
//Debug.Log("push! "+states.Count + " "+ states.Peek());
State current = states.Peek();
state.currentMachine = this;
states.Push(state);
if(current != null) current.Suspend(state);
state.Enter(current);
}
public void PopState() {
//Debug.Log("pop! "+states.Count + " "+ states.Peek());
State exited = states.Pop();
State s = states.Peek();
if(exited != null) exited.Exit(s);
if(s != null) {
s.currentMachine = this;
s.Resume(exited);
}
}
public void SwitchState(State state) {
//Debug.Log("switch! "+states.Count + " "+ states.Peek());
State exited = states.Pop();
states.Push(state);
state.currentMachine = this;
if(exited != null) exited.Exit(state);
state.Enter(exited);
}
public void Update() {
State s = states.Peek();
if(s != null) s.Update();
else Debug.LogError("no state!");
}
}
/* Usage example */
using UnityEngine;
using System.Collections;
public class ExampleStateMachineBehaviour: MonoBehaviour {
public string status;
public StateMachine sm;
void Start() {
// declare variables that will be accessed from within a delegate with an initial value,
// or the .NET compiler will "optimize" them away
State start_state = null, idle = null, jumping = null;
start_state = new State((st) => {
float timer = 5f; // will stay 5 seconds in this state.
st.onEnter += (s, from) => {
status = "start state";
};
st.onUpdate += (s) => {
timer -= Time.deltaTime;
if(timer <= 0) {
s.SwitchTo(idle);
}
};
});
idle = new State((st) => {
var enter = (s, from) => {
status = "idle";
};
st.onEnter += enter;
st.onResume += enter;
st.onUpdate += (s) => {
if(Input.GetKeyDown(KeyCode.Space)) {
s.Push(jumping); // thunk into the jumping state
}
}
});
jumping = new State((st) => {
float y = 0;
float yspeed = 0;
float yaccel = -1;
st.onEnter += (s, from) => {
status = "jumping";
// reset yspeed every time it enters the state
yspeed = 10;
};
st.onUpdate += (s) => {
y += yspeed * Time.deltaTime;
yspeed += yaccel * Time.deltaTime;
status = "jumping: y="+y;
if(y < 0) {
s.Pop(); // go back to idle (or whoever pushed this state in)
}
};
});
sm = new StateMachine(start_state);
}
void Update() {
sm.Update();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment