Created
February 7, 2024 18:39
-
-
Save LuviKunG/bb87e7053f07ed014d83b367d4c786bf to your computer and use it in GitHub Desktop.
Simple and easy to use of state machines.
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.Runtime.Serialization; | |
namespace LuviKunG.FSM | |
{ | |
/// <summary> | |
/// Exception for the state machine. | |
/// </summary> | |
public class FSMException : Exception | |
{ | |
public FSMException() { } | |
public FSMException(string message) : base(message) { } | |
public FSMException(string message, Exception innerException) : base(message, innerException) { } | |
protected FSMException(SerializationInfo info, StreamingContext context) : base(info, context) { } | |
} | |
} |
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
namespace LuviKunG.FSM | |
{ | |
/// <summary> | |
/// The base class for all state in the state machine. | |
/// Inherit this class to create a new state with the context type. | |
/// </summary> | |
/// <typeparam name="TContext">Type of the context.</typeparam> | |
public abstract class FSMState<TContext> | |
{ | |
private bool isFinished; | |
/// <summary> | |
/// Determine whether the state is finished or not. | |
/// </summary> | |
public bool IsFinished => isFinished; | |
/// <summary> | |
/// Context of the state machine. | |
/// </summary> | |
protected TContext context; | |
/// <summary> | |
/// Finish this state. | |
/// </summary> | |
protected void Finish() | |
{ | |
isFinished = true; | |
} | |
/// <summary> | |
/// Template method when state machine are starting. | |
/// </summary> | |
public virtual void OnStateMachineStart() { } | |
/// <summary> | |
/// Template method when state machine are stopping. | |
/// </summary> | |
public virtual void OnStateMachineStop() { } | |
/// <summary> | |
/// Initialize the state with the context. | |
/// This method will be called before entering the state. | |
/// </summary> | |
/// <param name="context"></param> | |
public void OnStateInitialize(TContext context) | |
{ | |
this.context = context; | |
isFinished = false; | |
} | |
/// <summary> | |
/// Template method when entering the state. | |
/// </summary> | |
public virtual void OnStateEnter() { } | |
/// <summary> | |
/// Template method when exiting the state. | |
/// </summary> | |
public virtual void OnStateExit() { } | |
/// <summary> | |
/// Template method when updating the state. | |
/// </summary> | |
/// <param name="deltaTime">Delta time that updated from state machine.</param> | |
public virtual void OnStateUpdate(in float deltaTime) { } | |
/// <summary> | |
/// Get the next state of the state machine. | |
/// This will be called after the state is finished by <see cref="Finish"/>. | |
/// </summary> | |
/// <returns>The next state. Can be null and the state machine will stop.</returns> | |
public abstract FSMState<TContext> GetNextState(); | |
} | |
} |
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.Collections; | |
using System.Collections.Generic; | |
namespace LuviKunG.FSM | |
{ | |
/// <summary> | |
/// The base class for all state machine. | |
/// Inherit this class to create a new state machine with the context type. | |
/// </summary> | |
/// <typeparam name="TContext">Type of the context.</typeparam> | |
public abstract class FSMStateMachine<TContext> : IEnumerable<FSMState<TContext>> | |
{ | |
private List<FSMState<TContext>> states; | |
private FSMState<TContext> currentState; | |
private TContext curentContext; | |
/// <summary> | |
/// Determine if the state machine is running or not. | |
/// </summary> | |
public bool IsRunning => currentState != null; | |
/// <summary> | |
/// Add a state to this state machine. | |
/// </summary> | |
/// <param name="state">The state.</param> | |
public void Add(FSMState<TContext> state) | |
{ | |
states.Add(state); | |
} | |
/// <summary> | |
/// Remove a state from this state machine. | |
/// </summary> | |
/// <param name="state">The state.</param> | |
/// <returns>Is the state are removed.</returns> | |
public bool Remove(FSMState<TContext> state) | |
{ | |
return states.Remove(state); | |
} | |
/// <summary> | |
/// Clear all state in this state machine. | |
/// </summary> | |
public void Clear() | |
{ | |
states.Clear(); | |
} | |
/// <summary> | |
/// Start the state machine with context. | |
/// </summary> | |
/// <param name="context">Context of this state machine.</param> | |
/// <exception cref="FSMException">Cannot start the state machine because there are no states.</exception> | |
public void Start(TContext context) | |
{ | |
if (IsRunning) | |
return; | |
curentContext = context; | |
foreach (FSMState<TContext> state in states) | |
{ | |
state.OnStateMachineStart(); | |
} | |
if (states.Count == 0) | |
throw new FSMException("Cannot start the state machine because there are no states."); | |
currentState = states[0]; | |
currentState?.OnStateInitialize(curentContext); | |
currentState?.OnStateEnter(); | |
} | |
/// <summary> | |
/// Stop the state machine. | |
/// </summary> | |
public void Stop() | |
{ | |
if (!IsRunning) | |
return; | |
currentState?.OnStateExit(); | |
currentState = null; | |
foreach (FSMState<TContext> state in states) | |
{ | |
state.OnStateMachineStop(); | |
} | |
} | |
/// <summary> | |
/// Update this state machine with desired delta time. | |
/// </summary> | |
/// <param name="deltaTime">Delta time to update the state machine.</param> | |
public void Update(in float deltaTime) | |
{ | |
if (IsRunning) | |
return; | |
currentState.OnStateUpdate(deltaTime); | |
if (currentState.IsFinished) | |
{ | |
currentState?.OnStateExit(); | |
currentState = currentState.GetNextState(); | |
currentState?.OnStateInitialize(curentContext); | |
currentState?.OnStateEnter(); | |
} | |
} | |
/// <summary> | |
/// Get the enumerator of all registed states. | |
/// </summary> | |
/// <returns>Enumuration of all registed states.</returns> | |
public IEnumerator<FSMState<TContext>> GetEnumerator() | |
{ | |
return states.GetEnumerator(); | |
} | |
/// <summary> | |
/// Get the enumerator of all registed states. | |
/// </summary> | |
/// <returns>Enumuration of all registed states.</returns> | |
IEnumerator IEnumerable.GetEnumerator() | |
{ | |
return GetEnumerator(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment