Created
March 1, 2024 18:22
-
-
Save p3nGu1nZz/92bb6f3f11d332e9f7bfbc88efb7b11e to your computer and use it in GitHub Desktop.
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; | |
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