Skip to content

Instantly share code, notes, and snippets.

@p3nGu1nZz
Created March 1, 2024 18:22
Show Gist options
  • Save p3nGu1nZz/92bb6f3f11d332e9f7bfbc88efb7b11e to your computer and use it in GitHub Desktop.
Save p3nGu1nZz/92bb6f3f11d332e9f7bfbc88efb7b11e to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using UnityEngine;
namespace HFSM
{
public class StateMachine<T> : Singleton<StateMachine<T>>
{
T context;
StateMachine<T> currentState;
StateMachine<T> defaultState;
StateMachine<T> parentState;
public StateMachine<T> ParentState { get { return parentState; } }
public StateMachine<T> CurrentState { get { return currentState; } }
Dictionary<Type, StateMachine<T>> states = new Dictionary<Type, StateMachine<T>>();
Dictionary<int, StateMachine<T>> transitions = new Dictionary<int, StateMachine<T>>();
public Dictionary<Type, StateMachine<T>> States { get { return states; } }
public Dictionary<int, StateMachine<T>> Transitions { get { return transitions; } }
public readonly struct Triggers { }
public void Bind(T context)
{
this.context = context;
OnLoad(context);
}
public void Start()
{
OnStart(context);
}
public void Enter()
{
OnEnter(context);
if (currentState == null && defaultState != null)
{
currentState = defaultState;
}
currentState?.Enter();
}
public void FixedUpdate()
{
OnFixedUpdate(context);
currentState?.FixedUpdate();
}
public void Update()
{
OnUpdate(context);
currentState?.Update();
}
public void LateUpdate()
{
OnLateUpdate(context);
currentState?.LateUpdate();
}
public void AnimateUpdate()
{
OnAnimateUpdate(context);
currentState?.AnimateUpdate();
}
public void CollisionEnter(Collision collision)
{
OnCollisionEnter(context, collision);
currentState?.CollisionEnter(collision);
}
public void CollisionExit(Collision collision)
{
OnCollisionExit(context, collision);
currentState?.CollisionExit(collision);
}
public void Exit()
{
currentState?.Exit();
OnExit(context);
}
protected virtual void OnLoad(T context) { }
protected virtual void OnStart(T context) { }
protected virtual void OnEnter(T context) { }
protected virtual void OnFixedUpdate(T context) { }
protected virtual void OnUpdate(T context) { }
protected virtual void OnLateUpdate(T context) { }
protected virtual void OnAnimateUpdate(T context) { }
protected virtual void OnCollisionEnter(T context, Collision collision) { }
protected virtual void OnCollisionExit(T context, Collision collision) { }
protected virtual void OnExit(T context) { }
public void LoadState(StateMachine<T> state)
{
if (states.Count == 0)
{
defaultState = state;
}
state.parentState = this;
if (context != null)
{
state.Bind(context);
}
try
{
states.Add(state.GetType(), state);
}
catch (ArgumentException)
{
throw new Exception($"State {GetType()} already contains a substate of a type {state.GetType()}");
}
}
public void AddTransition(StateMachine<T> from, StateMachine<T> to, int trigger)
{
if (!states.TryGetValue(from.GetType(), out _))
{
throw new Exception($"State {GetType()} does not have a substate of type {from.GetType()} to transition from");
}
if (!states.TryGetValue(to.GetType(), out _))
{
throw new Exception($"State {GetType()} does not have a substate of type {to.GetType()} to transition into");
}
try
{
from.transitions.Add(trigger, to);
}
catch (ArgumentException)
{
throw new Exception($"State {from.GetType()} already has a transition defined for trigger {trigger}");
}
}
public void AddTransitionToChild(StateMachine<T> from, StateMachine<T> to, int trigger)
{
if (!states.TryGetValue(from.GetType(), out _))
{
throw new Exception($"State {GetType()} does not have a substate of type {from.GetType()} to transition from");
}
if (!to.parentState.states.TryGetValue(to.GetType(), out _))
{
throw new Exception($"State {to.parentState.GetType()} does not have a child substate of type {to.GetType()} to transition into");
}
try
{
from.transitions.Add(trigger, to);
}
catch (ArgumentException)
{
throw new Exception($"State {from.GetType()} already has a transition defined for trigger {trigger}");
}
}
public void SendTrigger(int trigger)
{
var root = this;
while (root?.parentState != null)
{
root = root.parentState;
}
while (root != null)
{
if (root.transitions.TryGetValue(trigger, out StateMachine<T> toState))
{
root.parentState?.ChangeState(toState);
return;
}
root = root.currentState;
}
throw new Exception($"Trigger {trigger} was not consumed by any transition");
}
private void ChangeState(StateMachine<T> state)
{
currentState?.Exit();
if (states.TryGetValue(state.GetType(), out _))
{
currentState = states[state.GetType()];
currentState.Enter();
return;
}
if (state.parentState.states.TryGetValue(state.GetType(), out _))
{
currentState.parentState.currentState = state.parentState;
state.parentState.currentState = state.parentState.states[state.GetType()];
state.parentState.currentState.parentState.Enter();
return;
}
throw new Exception($"State {state.GetType()} does not exist in {state.parentState.GetType()}");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment