Skip to content

Instantly share code, notes, and snippets.

@st4rdog
Last active January 17, 2024 22:51
Show Gist options
  • Save st4rdog/0baf29c196e8b62041e2be71c53ca389 to your computer and use it in GitHub Desktop.
Save st4rdog/0baf29c196e8b62041e2be71c53ca389 to your computer and use it in GitHub Desktop.
C# Finite State Machine for Unity
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