Last active
January 17, 2024 22:51
-
-
Save st4rdog/0baf29c196e8b62041e2be71c53ca389 to your computer and use it in GitHub Desktop.
C# Finite State Machine 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
using System; | |
using System.Collections.Generic; | |
using UnityEngine; | |
/* Example usage: | |
public FSM GameFSM = new(); | |
private void Awake() | |
{ | |
GameFSM.AddState("Init", | |
onEnter: fsm => | |
{ | |
print("Init"); | |
fsm.ChangeState("Play"); | |
} | |
); | |
GameFSM.AddState("Play", | |
onEnter: _ => print("Play"), | |
onUpdate: fsm => | |
{ | |
if (Input.GetKeyDown(KeyCode.Space)) | |
fsm.Trigger("OnLevelCompleted"); | |
}, | |
onExit: _ => print("Exiting Play state"), | |
triggers: new() | |
{ | |
("OnLevelCompleted", fsm => fsm.ChangeState("LevelCompleted")), | |
("OnPlayerDeath", fsm => fsm.ChangeState("GameOver")) | |
} | |
); | |
GameFSM.AddState("LevelCompleted", | |
onEnter: _ => print("Level was completed") | |
); | |
GameFSM.AddState("GameOver", | |
onEnter: _ => print("Game Over!") | |
); | |
GameFSM.ChangeState("Init"); // Set starting state | |
} | |
void FixedUpdate() | |
{ | |
GameFSM.FixedUpdate(); | |
} | |
void Update() | |
{ | |
GameFSM.Update(); | |
} | |
void LateUpdate() | |
{ | |
GameFSM.LateUpdate(); | |
} | |
*/ | |
public class FSM | |
{ | |
public string CurrentState = string.Empty; | |
public Dictionary<string, Dictionary<string, Action<FSM>>> StatesTable = new(); | |
public FSM AddState(string stateName, Action<FSM> onEnter = null, Action<FSM> onFixedUpdate = null, Action<FSM> onUpdate = null, Action<FSM> onLateUpdate = null, Action<FSM> onExit = null, List<(string triggerName, Action<FSM> action)> triggers = null) | |
{ | |
if (StatesTable.ContainsKey(stateName)) | |
{ | |
Debug.Log($"FSM AddState - State name ({stateName}) already exists."); | |
return this; | |
} | |
else | |
{ | |
string.Intern(stateName); | |
} | |
// Assign actions | |
onEnter ??= DoNothing; | |
onFixedUpdate ??= DoNothing; | |
onUpdate ??= DoNothing; | |
onLateUpdate ??= DoNothing; | |
onExit ??= DoNothing; | |
void DoNothing(FSM fsm) { } | |
StatesTable.Add(stateName, new Dictionary<string, Action<FSM>> | |
{ | |
["OnEnter"] = onEnter, | |
["OnFixedUpdate"] = onFixedUpdate, | |
["OnUpdate"] = onUpdate, | |
["OnLateUpdate"] = onLateUpdate, | |
["OnExit"] = onExit | |
}); | |
// Add triggers | |
triggers ??= new(); | |
foreach (var (triggerName, action) in triggers) | |
{ | |
StatesTable[stateName].Add(triggerName, action); | |
string.Intern(triggerName); | |
} | |
return this; | |
} | |
public void ChangeState(string targetState) | |
{ | |
if (!StatesTable.ContainsKey(targetState)) | |
{ | |
Debug.Log($"FSM ChangeState - No matching state ({targetState}) in machine."); | |
return; | |
} | |
if (!string.IsNullOrWhiteSpace(CurrentState)) | |
StatesTable[CurrentState]["OnExit"](this); | |
CurrentState = string.Intern(targetState); | |
StatesTable[targetState]["OnEnter"](this); | |
} | |
public void Trigger(string triggerName) | |
{ | |
if (!StatesTable[CurrentState].ContainsKey(triggerName)) | |
{ | |
Debug.Log($"FSM Trigger - No matching trigger ({triggerName}) handler in the current state ({CurrentState})."); | |
return; | |
} | |
StatesTable[CurrentState][triggerName](this); | |
} | |
public void FixedUpdate() | |
{ | |
StatesTable[CurrentState]["OnFixedUpdate"](this); | |
} | |
public void Update() | |
{ | |
StatesTable[CurrentState]["OnUpdate"](this); | |
} | |
public void LateUpdate() | |
{ | |
StatesTable[CurrentState]["OnLateUpdate"](this); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment