C# Unity FSM state machine

C# Unity FSM state machine

Using state machines can reduce code coupling, optimize code readability, and facilitate team collaboration.
For game development content, the process of game development and player animation can be implemented using FSM finite state machines.

1.FsmState

Base class for each state, generic parameters represent the owner

public abstract class FsmState<T> where T : class
{<!-- -->
    protected internal abstract void OnInit(IFsm<T> fsm);
    protected internal abstract void OnEnter(IFsm<T> fsm);
    protected internal abstract void OnUpdate(IFsm<T> fsm);
    protected internal abstract void OnLeave(IFsm<T> fsm);
    protected internal abstract void OnDestroy(IFsm<T> fsm);

    protected void ChangeState<TState>(IFsm<T> fsm) where TState : FsmState<T>
    {<!-- -->
        Fsm<T> fsmImplement = (Fsm<T>)fsm;
        if(fsmImplement == null)
        {<!-- -->
            throw new Exception("FSM is invalid.");
        }
        fsmImplement.ChangeState<TState>();
    }
    protected void ChangeState(IFsm<T> fsm, Type stateType)
    {<!-- -->
        Fsm<T> fsmImplement = (Fsm<T>)fsm;
        if (fsmImplement == null)
        {<!-- -->
            throw new Exception("FSM is invalid.");
        }

        if (stateType == null)
        {<!-- -->
            throw new Exception("State type is invalid.");
        }

        if (!typeof(FsmState<T>).IsAssignableFrom(stateType))
        {<!-- -->
            throw new Exception("State type is invalid.");
        }

        fsmImplement.ChangeState(stateType);
    }
}

2.IFsm

Finite state machine interface

public interface IFsm<T> where T : class
{<!-- -->
    string Name
    {<!-- -->
        get;
    }
    string FullName
    {<!-- -->
        get;
    }
    T Owner
    {<!-- -->
        get;
    }
    int FsmStateCount
    {<!-- -->
        get;
    }
    bool IsRunning
    {<!-- -->
        get;
    }
    bool IsDestroyed
    {<!-- -->
        get;
    }
    FsmState<T> CurrentState
    {<!-- -->
        get;
    }
    float CurrentStateTime
    {<!-- -->
        get;
    }

    void Start<TState>() where TState : FsmState<T>;
    bool HasState<TState>() where TState : FsmState<T>;
    TState GetState<TState>() where TState : FsmState<T>;
    FsmState<T> GetState(Type stateType);
    FsmState<T>[] GetAllStates();
}

3.IFsmManager

Finite State Machine Manager Interface

public interface IFsmManager
{<!-- -->
    int Count {<!-- --> get; }
    bool HasFsm<T>() where T : class;
    bool HasFsm(Type ownerType);
    bool HasFsm<T>(string name) where T : class;
    bool HasFsm(Type ownerType, string name);

    IFsm<T> GetFsm<T>() where T : class;
    IFsm<T> GetFsm<T>(string name) where T : class;

    IFsm<T> CreateFsm<T>(T owner, params FsmState<T>[] fsmStates) where T : class;
    IFsm<T> CreateFsm<T>(string name, T owner, params FsmState<T>[] states) where T : class;
    IFsm<T> CreateFsm<T>(T owner, List<FsmState<T>> states) where T : class;
    IFsm<T> CreateFsm<T>(string name, T owner, List<FsmState<T>> states) where T : class;

    bool DestroyFsm<T>() where T : class;
    bool DestroyFsm(Type ownerType);
    bool DestroyFsm<T>(string name) where T : class;
    bool DestroyFsm(Type ownerType, string name);
    bool DestroyFsm<T>(IFsm<T> fsm) where T : class;
}

4.FsmBase

Base class for finite state machines

public abstract class FsmBase
{<!-- -->
    private string m_Name;

    public FsmBase()
    {<!-- -->
        m_Name = string.Empty;
    }

    public string Name
    {<!-- -->
        get
        {<!-- -->
            return m_Name;
        }
        protected set
        {<!-- -->
            m_Name = value  string.Empty;
        }
    }

    public string FullName
    {<!-- -->
        get
        {<!-- -->
            return $"{<!-- -->OwnerType.FullName}.{<!-- -->Name}";
        }
    }

    public abstract Type OwnerType
    {<!-- -->
        get;
    }

    public abstract int FsmStateCount
    {<!-- -->
        get;
    }

    public abstract bool IsRunning
    {<!-- -->
        get;
    }

    public abstract bool IsDestroyed
    {<!-- -->
        get;
    }

    public abstract string CurrentStateName
    {<!-- -->
        get;
    }

    public abstract float CurrentStateTime
    {<!-- -->
        get;
    }

    public abstract void Update();
    public abstract void Shutdown();
}

5.Fsm

State machine class

public class Fsm<T> : FsmBase, IFsm<T> where T : class
{<!-- -->
    private T m_Owner;
    private readonly Dictionary<Type, FsmState<T>> m_States;
    private FsmState<T> m_CurrentState;
    private float m_CurrentStateTime;
    private bool m_IsDestroyed;
    publicFsm()
    {<!-- -->
        m_Owner = null;
        m_States = new Dictionary<Type, FsmState<T>>();
        m_CurrentState = null;
        m_CurrentStateTime = 0f;
        m_IsDestroyed = true;
    }
    public T Owner => m_Owner;
    public FsmState<T> CurrentState => m_CurrentState;
    public override Type OwnerType => typeof(T);
    public override int FsmStateCount => m_States.Count;
    public override bool IsRunning => m_CurrentState != null;
    public override bool IsDestroyed => m_IsDestroyed;
    public override string CurrentStateName => m_CurrentState != null ? m_CurrentState.GetType().FullName : null;
    public override float CurrentStateTime => m_CurrentStateTime;
    public static Fsm<T> Create(string name,T owner,params FsmState<T>[] states)
    {<!-- -->
        if(owner == null)
        {<!-- -->
            throw new Exception("FSM owner is invalid.");
        }
        if(states== null|| states.Length < 1)
        {<!-- -->
            throw new Exception("FSM states is invalid.");
        }

        Fsm<T> fsm = Pool<Fsm<T>>.Rent();
        fsm.Name = name;
        fsm.m_Owner = owner;
        fsm.m_IsDestroyed = false;
        foreach (FsmState<T> oneState in states)
        {<!-- -->
            if(oneState == null)
            {<!-- -->
                throw new Exception("FSM states is invalid.");
            }

            Type stateType = oneState.GetType();
            if (fsm.m_States.ContainsKey(stateType))
            {<!-- -->
                throw new Exception($"{<!-- -->stateType} state is already exist");
            }
            fsm.m_States.Add(stateType, oneState);
            oneState.OnInit(fsm);
        }
        return fsm;
    }
    public static Fsm<T> Create(string name,T owner,List<FsmState<T>> states)
    {<!-- -->
        if (owner == null)
        {<!-- -->
            throw new Exception("FSM owner is invalid.");
        }
        if (states == null || states.Count < 1)
        {<!-- -->
            throw new Exception("FSM states is invalid.");
        }

        Fsm<T> fsm = Pool<Fsm<T>>.Rent();
        fsm.Name = name;
        fsm.m_Owner = owner;
        fsm.m_IsDestroyed = false;
        foreach (FsmState<T> oneState in states)
        {<!-- -->
            if (oneState == null)
            {<!-- -->
                throw new Exception("FSM states is invalid.");
            }

            Type stateType = oneState.GetType();
            if (fsm.m_States.ContainsKey(stateType))
            {<!-- -->
                throw new Exception($"{<!-- -->stateType} state is already exist");
            }
            fsm.m_States.Add(stateType, oneState);
            oneState.OnInit(fsm);
        }
        return fsm;
    }
    public FsmState<T>[] GetAllStates()
    {<!-- -->
        int index = 0;
        FsmState<T>[] arr = new FsmState<T>[m_States.Count];
        foreach (FsmState<T> fsmState in m_States.Values)
        {<!-- -->
            arr[index + + ] = fsmState;
        }
        return arr;
    }
    public bool HasState<TState>() where TState : FsmState<T>
    {<!-- -->
        return m_States.ContainsKey(typeof(TState));
    }
    public override void Shutdown()
    {<!-- -->
        Pool<Fsm<T>>.Return(this, (fsm) =>
         {<!-- -->
             if(m_CurrentState != null)
             {<!-- -->
                 m_CurrentState.OnLeave(this);
             }
             foreach (FsmState<T> oneState in m_States.Values)
             {<!-- -->
                 oneState.OnDestroy(this);
             }

             Name = null;
             m_Owner = null;
             m_States.Clear();



             m_CurrentState = null;
             m_CurrentStateTime = 0f;
             m_IsDestroyed = true;
         });
    }
    public void Start<TState>() where TState : FsmState<T>
    {<!-- -->
        if(IsRunning)
        {<!-- -->
            throw new Exception("FSM is running, can not start again.");
        }
        FsmState<T> state = GetState<TState>();
        if (state == null)
        {<!-- -->
            throw new Exception("can not start state");
        }
        m_CurrentStateTime = 0f;
        m_CurrentState = state;
        m_CurrentState.OnEnter(this);
    }
    public void Start(Type stateType)
    {<!-- -->
        if(IsRunning)
        {<!-- -->
            throw new Exception("FSM is running, can not start again.");
        }

        if (stateType == null)
        {<!-- -->
            throw new Exception("State type is invalid.");
        }

        if (!typeof(FsmState<T>).IsAssignableFrom(stateType))
        {<!-- -->
            throw new Exception("State type is invalid.");
        }

        FsmState<T> state = GetState(stateType);
        if (state == null)
        {<!-- -->
            throw new Exception("FSM can not start state which is not exist.");
        }

        m_CurrentStateTime = 0f;
        m_CurrentState = state;
        m_CurrentState.OnEnter(this);
    }
    public override void Update()
    {<!-- -->
        m_CurrentStateTime + = Time.deltaTime;
        m_CurrentState.OnUpdate(this);
    }
    public TState GetState<TState>() where TState : FsmState<T>
    {<!-- -->
        if (m_States.TryGetValue(typeof(TState), out FsmState<T> fsmState))
        {<!-- -->
            return (TState)fsmState;
        }
        return null;
    }
    public FsmState<T> GetState(Type stateType)
    {<!-- -->
        if (stateType == null)
        {<!-- -->
            throw new Exception("State type is invalid.");
        }

        if (!typeof(FsmState<T>).IsAssignableFrom(stateType))
        {<!-- -->
            throw new Exception("State type is invalid.");
        }

        if (m_States.TryGetValue(stateType, out FsmState<T> fsmState))
        {<!-- -->
            return fsmState;
        }
        return null;
    }
    public void ChangeState<TState>()
    {<!-- -->
        ChangeState(typeof(TState));
    }
    public void ChangeState(Type stateType)
    {<!-- -->
        if (m_CurrentState == null)
        {<!-- -->
            throw new Exception("Current state is invalid.");
        }

        FsmState<T> state = GetState(stateType);
        if (state == null)
        {<!-- -->
            throw new Exception("FSM can not change state which is not exist.");
        }

        m_CurrentState.OnLeave(this);
        m_CurrentStateTime = 0f;
        m_CurrentState = state;
        m_CurrentState.OnEnter(this);
    }
}

6.FsmManager

State machine manager

public class FsmManager : Singleton<FsmManager>,IFsmManager,IUpdateSingleton
{<!-- -->
    private readonly Dictionary<string, FsmBase> m_FsmDic;
    private readonly List<FsmBase> m_TempFsms;
    public FsmManager()
    {<!-- -->
        m_FsmDic = new Dictionary<string, FsmBase>();
        m_TempFsms = new List<FsmBase>();
    }
    public int Count => m_FsmDic.Count;

    public IFsm<T> CreateFsm<T>(T owner, params FsmState<T>[] fsmStates) where T : class
    {<!-- -->
        return CreateFsm(string.Empty, owner, fsmStates);
    }
    public IFsm<T> CreateFsm<T>(string name, T owner, params FsmState<T>[] states) where T : class
    {<!-- -->
        if (HasFsm<T>(name))
        {<!-- -->
            throw new Exception("Already exist FSM");
        }
        Fsm<T> fsm = Fsm<T>.Create(name, owner, states);
        m_FsmDic.Add(fsm.FullName, fsm);
        return fsm;
    }
    public IFsm<T> CreateFsm<T>(T owner, List<FsmState<T>> states) where T : class
    {<!-- -->
        return CreateFsm(string.Empty, owner, states);
    }
    public IFsm<T> CreateFsm<T>(string name, T owner, List<FsmState<T>> states) where T : class
    {<!-- -->
        if (HasFsm<T>(name))
        {<!-- -->
            throw new Exception("Already exist FSM");
        }
        Fsm<T> fsm = Fsm<T>.Create(name, owner, states);
        m_FsmDic.Add(fsm.FullName, fsm);
        return fsm;
    }

    public bool DestroyFsm<T>() where T : class
    {<!-- -->
        return InternalDestroyFsm($"{<!-- -->typeof(T).FullName}.{<!-- -->string.Empty}");
    }
    public bool DestroyFsm(Type ownerType)
    {<!-- -->
        if (ownerType == null)
        {<!-- -->
            throw new Exception("Owner type is invalid.");
        }
        return InternalDestroyFsm($"{<!-- -->ownerType.FullName}.{<!-- -->string.Empty}");
    }
    public bool DestroyFsm<T>(string name) where T : class
    {<!-- -->
        return InternalDestroyFsm($"{<!-- -->typeof(T).FullName}.{<!-- -->name}");
    }
    public bool DestroyFsm(Type ownerType, string name)
    {<!-- -->
        if (ownerType == null)
        {<!-- -->
            throw new Exception("Owner type is invalid.");
        }
        return InternalDestroyFsm($"{<!-- -->ownerType.FullName}.{<!-- -->name}");
    }
    public bool DestroyFsm<T>(IFsm<T> fsm) where T : class
    {<!-- -->
        if (fsm == null)
        {<!-- -->
            throw new Exception("FSM is invalid.");
        }
        return InternalDestroyFsm(fsm.FullName);
    }

    public IFsm<T> GetFsm<T>() where T : class
    {<!-- -->
        return (IFsm<T>)InternalGetFsm($"{<!-- -->typeof(T).FullName}.{<!-- -->string.Empty}");
    }
    public IFsm<T> GetFsm<T>(string name) where T : class
    {<!-- -->
        return (IFsm<T>)InternalGetFsm($"{<!-- -->typeof(T).FullName}.{<!-- -->name}");
    }

    public bool HasFsm<T>() where T : class
    {<!-- -->
        return InternalHasFsm($"{<!-- -->typeof(T).FullName}.{<!-- -->string.Empty}");
    }
    public bool HasFsm(Type ownerType)
    {<!-- -->
        if(ownerType == null)
        {<!-- -->
            throw new Exception("Owner type is invalid.");
        }
        return InternalHasFsm($"{<!-- -->ownerType.FullName}.{<!-- -->string.Empty}");
    }
    public bool HasFsm<T>(string name) where T : class
    {<!-- -->
        return InternalHasFsm($"{<!-- -->typeof(T).FullName}.{<!-- -->name}");
    }
    public bool HasFsm(Type ownerType, string name)
    {<!-- -->
        if (ownerType == null)
        {<!-- -->
            throw new Exception("Owner type is invalid.");
        }
        return InternalHasFsm($"{<!-- -->ownerType.FullName}.{<!-- -->name}");
    }


    private bool InternalDestroyFsm(string name)
    {<!-- -->
        if(m_FsmDic.TryGetValue(name,out FsmBase fsmBase))
        {<!-- -->
            fsmBase.Shutdown();
            return m_FsmDic.Remove(name);
        }
        return false;
    }
    private FsmBase InternalGetFsm(string name)
    {<!-- -->
        FsmBase fsm = null;
        if (m_FsmDic.TryGetValue(name, out fsm))
        {<!-- -->
            return fsm;
        }

        return null;
    }
    private bool InternalHasFsm(string name)
    {<!-- -->
        return m_FsmDic.ContainsKey(name);
    }

    public void Update()
    {<!-- -->
        m_TempFsms.Clear();
        if (m_FsmDic.Count <= 0)
            return;


        foreach (FsmBase fsmBase in m_FsmDic.Values)
        {<!-- -->
            m_TempFsms.Add(fsmBase);
        }

        foreach (FsmBase fsmBase in m_TempFsms)
        {<!-- -->
            if (fsmBase.IsDestroyed)
                continue;
            fsmBase.Update();
        }
    }

    protected override void Load(int assemblyName)
    {<!-- -->
        
    }

    protected override void UnLoad(int assemblyName)
    {<!-- -->
        
    }
}

7. Test

(1) IdleState

public class IdleState : FsmState<FsmTest>
{<!-- -->
    protected internal override void OnDestroy(IFsm<FsmTest> fsm)
    {<!-- -->
        Debug.Log("Destroy IdleState");
    }

    protected internal override void OnEnter(IFsm<FsmTest> fsm)
    {<!-- -->
        Debug.Log("Enter IdleState");
    }

    protected internal override void OnInit(IFsm<FsmTest> fsm)
    {<!-- -->
    }

    protected internal override void OnLeave(IFsm<FsmTest> fsm)
    {<!-- -->
        Debug.Log("Leave IdleState");
    }

    protected internal override void OnUpdate(IFsm<FsmTest> fsm)
    {<!-- -->
        if (Input.GetKeyDown(KeyCode.A))
        {<!-- -->
            ChangeState<WalkState>(fsm);
        }
    }
}

(2) WalkState

public class WalkState : FsmState<FsmTest>
{<!-- -->
    protected internal override void OnDestroy(IFsm<FsmTest> fsm)
    {<!-- -->
        Debug.Log("Destroy WalkState");
    }

    protected internal override void OnEnter(IFsm<FsmTest> fsm)
    {<!-- -->
        Debug.Log("Enter WalkState");
    }

    protected internal override void OnInit(IFsm<FsmTest> fsm)
    {<!-- -->
    }

    protected internal override void OnLeave(IFsm<FsmTest> fsm)
    {<!-- -->
        Debug.Log("Leave WalkState");
    }

    protected internal override void OnUpdate(IFsm<FsmTest> fsm)
    {<!-- -->
        if (Input.GetKeyDown(KeyCode.B))
        {<!-- -->
            ChangeState<RunState>(fsm);
        }
    }
}

(3) RunState

public class RunState : FsmState<FsmTest>
{<!-- -->
    protected internal override void OnDestroy(IFsm<FsmTest> fsm)
    {<!-- -->
        Debug.Log("Destroy RunState");
    }

    protected internal override void OnEnter(IFsm<FsmTest> fsm)
    {<!-- -->
        Debug.Log("Enter RunState");
    }

    protected internal override void OnInit(IFsm<FsmTest> fsm)
    {<!-- -->
    }

    protected internal override void OnLeave(IFsm<FsmTest> fsm)
    {<!-- -->
        Debug.Log("Leave RunState");
    }

    protected internal override void OnUpdate(IFsm<FsmTest> fsm)
    {<!-- -->
        if (Input.GetKeyDown(KeyCode.C))
        {<!-- -->
            ChangeState<IdleState>(fsm);
        }
    }
}

mono test

public class FsmTest : MonoBehaviour
{<!-- -->
    private IFsm<FsmTest> m_TestFsm;
    void Start()
    {<!-- -->
        SingletonSystem.Initialize();
        AssemblyManager.Load(1, GetType().Assembly);

        m_TestFsm = FsmManager.Instance.CreateFsm<FsmTest>("MyTestFsm",this, new IdleState(),new WalkState(),new RunState());
        m_TestFsm.Start<IdleState>();
    }

    void Update()
    {<!-- -->
        SingletonSystem.Update();
        if (Input.GetKeyDown(KeyCode.P))
        {<!-- -->
            FsmManager.Instance.DestroyFsm(m_TestFsm);
        }
    }
}