Skip to content

Instantly share code, notes, and snippets.

@LuviKunG
Created February 7, 2024 18:39
Show Gist options
  • Save LuviKunG/bb87e7053f07ed014d83b367d4c786bf to your computer and use it in GitHub Desktop.
Save LuviKunG/bb87e7053f07ed014d83b367d4c786bf to your computer and use it in GitHub Desktop.
Simple and easy to use of state machines.
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) { }
}
}
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();
}
}
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