Last active
October 21, 2022 23:54
-
-
Save mandarinx/734ca9f02a8eccaa3c3e to your computer and use it in GitHub Desktop.
Unity3D Simple Finite State Machine
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 MyGame; | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
namespace MyGame.FSM { | |
public abstract class FSMState { | |
protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>(); | |
protected StateID stateID; | |
protected FSMSystem fsmSystem; | |
public FSMSystem FSMSystem { | |
set { this.fsmSystem = value; } | |
} | |
public StateID ID { | |
get { return stateID; } | |
} | |
public void AddTransition(Transition trans, StateID id) { | |
if (trans == Transition.NullTransition) { | |
Debug.LogError("FSMState ERROR: NullTransition is not allowed for a real transition"); | |
return; | |
} | |
if (id == StateID.NullStateID) { | |
Debug.LogError("FSMState ERROR: NullStateID is not allowed for a real ID"); | |
return; | |
} | |
if (map.ContainsKey(trans)) { | |
Debug.LogError("FSMState ERROR: State " + stateID.ToString() + | |
" already has transition " + trans.ToString() + | |
"Impossible to assign to another state"); | |
return; | |
} | |
map.Add(trans, id); | |
} | |
public void DeleteTransition(Transition trans) { | |
if (trans == Transition.NullTransition) { | |
Debug.LogError("FSMState ERROR: NullTransition is not allowed"); | |
return; | |
} | |
if (map.ContainsKey(trans)) { | |
map.Remove(trans); | |
return; | |
} | |
Debug.LogError("FSMState ERROR: Transition " + trans.ToString() + | |
" passed to " + stateID.ToString() + | |
" was not on the state's transition list"); | |
} | |
public bool SetTransition(Transition trans, System.Object options = null) { | |
return fsmSystem.PerformTransition(trans, options); | |
} | |
public StateID GetOutputState(Transition trans) { | |
if (map.ContainsKey(trans)) { | |
return map[trans]; | |
} | |
return StateID.NullStateID; | |
} | |
public virtual void DoBeforeEntering(System.Object options) { } | |
public virtual void DoBeforeLeaving() { } | |
public virtual bool Reason() { | |
return true; | |
} | |
public abstract void Act(); | |
public override string ToString() { | |
return stateID.ToString(); | |
} | |
} | |
} |
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
namespace MyGame.FSM { | |
public enum Transition { | |
NullTransition = 0, | |
OneTwo = 10, | |
TwoOne = 20 | |
} | |
public enum StateID { | |
NullStateID = 0, | |
One = 10, | |
Two = 20 | |
} | |
} |
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 MyGame.FSM; | |
using UnityEngine; | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
namespace MyGame.FSM { | |
public class FSMSystem { | |
private List<FSMState> states; | |
private StateID currentStateID; | |
private FSMState currentState; | |
public StateID CurrentStateID { | |
get { return currentStateID; } | |
} | |
public FSMState CurrentState { | |
get { return currentState; } | |
} | |
public FSMSystem() { | |
states = new List<FSMState>(); | |
} | |
public void AddState(FSMState state) { | |
if (state == null) { | |
Debug.LogError("FSM ERROR: Null reference is not allowed"); | |
} | |
foreach (FSMState st in states) { | |
if (st.ID == state.ID) { | |
Debug.LogError("FSM ERROR: Impossible to add state " + | |
state.ID.ToString() + | |
" because state has already been added"); | |
return; | |
} | |
} | |
state.FSMSystem = this; | |
states.Add(state); | |
if (states.Count == 1) { | |
currentState = state; | |
currentStateID = state.ID; | |
} | |
} | |
public void DeleteState(StateID id) { | |
if (id == StateID.NullStateID) { | |
Debug.LogError("FSM ERROR: NullStateID is not allowed for a real state"); | |
return; | |
} | |
foreach (FSMState state in states) { | |
if (state.ID == id) { | |
states.Remove(state); | |
return; | |
} | |
} | |
Debug.LogError("FSM ERROR: Impossible to delete state " + | |
id.ToString() + | |
". It was not on the list of states"); | |
} | |
public bool CanPerformTransition(Transition trans) { | |
StateID id = currentState.GetOutputState(trans); | |
if (id == StateID.NullStateID) { | |
return false; | |
} | |
return true; | |
} | |
public bool PerformTransition(Transition trans, System.Object options = null) { | |
if (!CanPerformTransition(trans)) { | |
return false; | |
} | |
if (trans == Transition.NullTransition) { | |
Debug.LogError("FSM ERROR: NullTransition is not allowed for a real transition"); | |
return false; | |
} | |
StateID id = currentState.GetOutputState(trans); | |
if (id == StateID.NullStateID) { | |
Debug.LogError("FSM ERROR: State " + currentStateID.ToString() + | |
" does not have a target state " + | |
" for transition " + trans.ToString()); | |
return false; | |
} | |
foreach (FSMState state in states) { | |
if (state.ID == id) { | |
currentStateID = id; | |
currentState.DoBeforeLeaving(); | |
currentState = state; | |
currentState.DoBeforeEntering(options); | |
break; | |
} | |
} | |
return true; | |
} | |
public void Update() { | |
if (currentState.Reason()) { | |
currentState.Act(); | |
} | |
} | |
} | |
} |
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; | |
namespace MyGame.FSM.States { | |
public class OneOptions : System.Object { | |
public float oneFloat; | |
} | |
public class TwoOptions : System.Object { | |
public bool twoBool; | |
} | |
} |
Here's an example of how to use the files above:
using UnityEngine;
using MyGame.FSM;
using MyGame.FSM.States;
using System.Collections;
namespace MyGame.Characters {
public class Player : MonoBehaviour {
private FSMSystem fsm;
void Awake() {
fsm = new FSMSystem();
One one = new One();
one.AddTransition(Transition.OneTwo, StateID.Two);
fsm.AddState(one);
Two two = new Two();
two.AddTransition(Transition.TwoOne, StateID.One);
fsm.AddState(two);
}
void Update() {
fsm.Update();
}
public void OneTwo( {
TwoOptions opts = new TwoOptions();
opts.twoBool = true;
fsm.PerformTransition(Transition.OneTwo, opts);
}
}
}
And here are the states:
using UnityEngine;
using MyGame.FSM;
using System.Collections;
namespace MyGame.FSM.States {
public class One : FSMState {
public One() {
stateID = StateID.One;
}
public override bool Reason() {
return false;
}
public override void Act() {}
public override void DoBeforeEntering(System.Object options) {}
public override void DoBeforeLeaving() {}
}
public class Two : FSMState {
private TwoOptions opts;
public Two() {
stateID = StateID.Two;
}
public override bool Reason() {
return opts.twoBool;
}
public override void Act() {
SetTransition(Transition.TwoOne);
}
public override void DoBeforeEntering(System.Object options) {
TwoOptions opts = (TwoOptions)options;
Debug.Log("TwoOptions.twoBool: "+opts.twoBool);
}
public override void DoBeforeLeaving() {}
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Based on the FSM at the Unity3D wiki.
I did a few improvements (from my point of view):