Teach you to do FSM state machine in Unity

        The introduction of FSM state machine Baidu Bing has a lot. I won't talk about it here. I mainly talk about how to realize it. One of the main advantages of state machine is that it can handle complex states, such as fist, leg, jump, rubbing and other relationships in fighting games. If it is only written as switch, it will increase the maintenance difficulty. There are too many case s, and the readability of code will be greatly reduced, so state machine is needed to maintain. It can be used in Animator, AI patrol, skill connection, etc. next, let's talk about how to implement a simple state machine.
        First of all, it is clear that although the base class and manager of the state machine do not inherit from MonoBehavior, they still need to be controlled through the class inherited from MonoBehavior (otherwise they cannot be implemented). Therefore, it is natural to need functions such as Update and FixedUpdate. For the sake of generality, it is implemented by generics. Using struct, Enum constraint T can only be enumeration type. (why use enumeration? Because the readability of sbyte code is so poor that no one can know what state 01 represents.) because it is a base class, it is decorated with abstract.

public abstract class FSMManagerBase<T> where T:struct,Enum
{
    protected FSMManagerBase()
    {
        
    }
    public void Update()
    {

    }

    public void FixedUpdate()
    {

    }
}
public abstract class FSMBase<T> where T:struct,Enum
{
    protected FSMBase()
    {
    
    }
    
    public abstract void OnEnter();
    public abstract void OnUpdate();
    public abstract void OnFixedUpdate();
    public abstract void OnExit();
}

        Next, add fields and attributes to the class. Each state class must have a state. This state is T type, that is, enumerated type, so we need to add the ClassState attribute to FSMBase: public abstract T ClassState {get; protected set;}, and the administrator class also needs to know the properties of the current call state, so add: public set. { get; protected set; } Now that you know the current state, you should also know what states the manager manages to facilitate reading operations. This is implemented with a dictionary: public dictionary < T, fsmbase < T > > fsmdictionary. This dictionary can be imported from the outside or added by providing methods after generation in the class. Therefore, you can make some changes in the constructor, so now the class is like this .

public abstract class FSMManagerBase<T> where T:struct,Enum
{
    public Dictionary<T, FSMBase<T>> fsmDictionary;
    public FSMBase<T> CurrentState { get; protected set; }

    protected FSMManagerBase(Dictionary<T, FSMBase<T>> dictionary)
    {
        fsmDictionary = dictionary;

    }

    protected FSMManagerBase()
    {
        fsmDictionary = new Dictionary<T, FSMBase<T>>();
    }
    
    public void Update()
    {

    }

    public void FixedUpdate()
    {

    }
}
public abstract class FSMBase<T> where T:struct,Enum
{
	public abstract T ClassState { get; protected set; }
	
    protected FSMBase()
    {
    	
    }
    
    public abstract void OnEnter();
    public abstract void OnUpdate();
    public abstract void OnFixedUpdate();
    public abstract void OnExit();
}

        With the dictionary list, you can provide the add and get methods.

	public FSMBase<T> GetState(T state)
    {
        return fsmDictionary[state];
    }

    public void AddState(FSMBase<T> fsmState)
    {
        fsmDictionary.Add(fsmState.ClassState, fsmState);
    }
    
    public void DeleteState(T state)
    {
        fsmDictionary.Remove(state);
    }

        These three methods are very simple. When using them, pay attention to catching exceptions such as duplicate key values.
        Now that the basic properties are written, the manager's update needs to call the update in the current state, but judge whether it is empty before calling.

    public virtual void Update()
    {
        CurrentState?.OnUpdate();//?. This means that OnUpdate is called when the current state is not empty
    }

    public virtual void FixedUpdate()
    {
        CurrentState?.OnFixedUpdate();
    }

        So now the two classes are

public abstract class FSMManagerBase<T> where T:struct,Enum
{
    public Dictionary<T, FSMBase<T>> fsmDictionary;
    public FSMBase<T> CurrentState { get; protected set; }

    protected FSMManagerBase(Dictionary<T, FSMBase<T>> dictionary)
    {
        fsmDictionary = dictionary;

    }

    protected FSMManagerBase()
    {
        fsmDictionary = new Dictionary<T, FSMBase<T>>();
    }

    public FSMBase<T> GetState(T state)
    {
        return fsmDictionary[state];
    }

    public void AddState(FSMBase<T> fsmState)
    {
        fsmDictionary.Add(fsmState.ClassState, fsmState);
    }
    public void DeleteState(T state)
    {
        fsmDictionary.Remove(state);
    }

    public virtual void Update()
    {
        CurrentState?.OnUpdate();
    }

    public virtual void FixedUpdate()
    {
        CurrentState?.OnFixedUpdate();
    }
}
public abstract class FSMBase<T> where T:struct,Enum
{
    public abstract T ClassState { get; protected set; }
    
    protected FSMBase()
    {
    
    }
    
    public abstract void OnEnter();
    public abstract void OnUpdate();
    public abstract void OnFixedUpdate();
    public abstract void OnExit();
}

        Now the manager also needs to call the entry and exit functions in the state. When switching to this state, he needs to call OnEnter. When switching out of this state, he needs to call OnExit. Therefore, the function SetCurrentState is declared:

    public void SetCurrentState(FSMBase<T> state)
    {
        CurrentState?.OnExit();
        CurrentState = state;
        CurrentState?.OnEnter();
    }

        If you still don't understand the logic of the state machine, explain it again. The state subclass inherited from the state base class only cares about how the current state transitions to other states. Draw a diagram.

        For example, in state a, state a only cares about the state transition from itself to others, but does not care about the state transition from others to itself. For example, in this figure, a only cares about how to transition from itself to its own, B and C States, but does not care about how to transition from State C to state A. therefore, in class A, only the logic of what conditions will be converted to itself, what conditions will be converted to C and what conditions will be converted to B is written Is the essence.
        It can be seen from the above that the state transition of the state machine is completed inside the state class, and the management class has the function of state switching. Therefore, the manager needs to be passed into the state as a parameter, and the state informs the manager to change the state. Therefore, the state class only needs to add a field and modify the constructor.

    protected FSMManagerBase<T> manager;

    protected FSMBase(FSMManagerBase<T> manager)
    {
        this.manager = manager;
    }

        So far, the simple state machine has been completed, and the following is the complete code.

public abstract class FSMBase<T> where T:struct,Enum
{
    protected FSMManagerBase<T> manager;
    public abstract T ClassState { get; protected set; }
    protected FSMBase(FSMManagerBase<T> manager)
    {
        this.manager = manager;
    }
    
    public abstract void OnEnter();
    public abstract void OnUpdate();
    public abstract void OnFixedUpdate();
    public abstract void OnExit();
}
public abstract class FSMManagerBase<T> where T:struct,Enum
{
    public Dictionary<T, FSMBase<T>> fsmDictionary;
    public FSMBase<T> CurrentState { get; protected set; }

    protected FSMManagerBase(Dictionary<T, FSMBase<T>> dictionary)
    {
        fsmDictionary = dictionary;

    }

    protected FSMManagerBase()
    {
        fsmDictionary = new Dictionary<T, FSMBase<T>>();
    }

    public void SetCurrentState(FSMBase<T> state)
    {
        CurrentState?.OnExit();
        CurrentState = state;
        CurrentState?.OnEnter();
    }

    public FSMBase<T> GetState(T state)
    {
        return fsmDictionary[state];
    }

    public void AddState(FSMBase<T> fsmState)
    {
        fsmDictionary.Add(fsmState.ClassState, fsmState);
    }
    public void DeleteState(T state)
    {
        fsmDictionary.Remove(state);
    }

    public virtual void Update()
    {
        CurrentState?.OnUpdate();
    }

    public virtual void FixedUpdate()
    {
        CurrentState?.OnFixedUpdate();
    }
}

        If the namespace reference is missing, please refer to it yourself and update the instance of state machine in the next article.
        Reference from: Implementing a Finite State Machine Using C# in Unity

Tags: C# Unity

Posted on Fri, 05 Nov 2021 00:32:54 -0400 by gli