Unity advanced study notes: finite state machine

Generally speaking, each game object has multiple states, and each state corresponds to a specific animation. For example, a game character may have a static state, a moving state, and an attacking state. Each state has a corresponding animation. If we simply use if statements to determine which control the player controls to switch animations, the readability and maintainability of the program will be poor. Using FSM (Finite State Machine) can encapsulate each state separately, making the system structure clearer

Let's first build a game scene for the example.
insert image description here
Import the material Character Pack: Free Sample, which includes a character model and a series of character animations. Here we use the idle, run, and wave animations
insert image description here
to add animator components to the characters. The boolean value IsRun is used to switch between idle and run, and the trigger Wave is used to switch between idle and wave.

1 Encapsulating state as a method

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum State {
    
    
    idle,
    run,
    wave
}

public class TestFSM_1 : MonoBehaviour
{
    
    

    private Animator ani;
    private State state = State.idle;
    // Start is called before the first frame update
    void Start()
    {
    
    
        ani = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
    
    
        switch (state) {
    
    
            case State.idle:
                idle();
                break;
            case State.run:
                run();
                break;
            case State.wave:
                wave();
                break;
        }
    }

    void idle() {
    
    
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir == Vector3.zero) {
    
    
            ani.SetBool("IsRun", false);
        } else {
    
    
            state = State.run;
        }
        if (Input.GetKeyDown(KeyCode.Space)) {
    
    
            state = State.wave;
        }
    }

    void run() {
    
    
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir != Vector3.zero) {
    
    
            ani.SetBool("IsRun", true);
            transform.rotation = Quaternion.LookRotation(dir);
            transform.Translate(Vector3.forward * 3 * Time.deltaTime);
        } else {
    
    
            state = State.idle;
        }
    }

    void wave() {
    
    
        ani.SetTrigger("Wave");
        if (!ani.GetCurrentAnimatorStateInfo(0).IsName("wave")) {
    
    
            state = State.idle;
        }
    }
}

First we create an enumeration State to save the names of each state

Here we create idle() run() wave() methods. In the idle() method, if it is found that the player presses the arrow key to switch the state to State.run, and the state is switched to State.wave when the space is pressed

In the run() method, we implemented the character movement. If the direction is (0,0,0), it means that the player releases the arrow keys and switches the state to idle

Set the Trigger Wave in the wave() method. Here we want to switch the state back to idle when the waving animation exits. We can use the GetCurrentAnimatorStateInfo method to judge

2 Encapsulating state as a class

state base class

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class FSMState
{
    
    
    public int StateID;
    public MonoBehaviour Mono;
    public FSMManager Manager;

    public FSMState(int stateID, MonoBehaviour mono, FSMManager manager) {
    
    
        StateID = stateID;
        Mono = mono;
        Manager = manager;
    }

    public abstract void OnEnter();
    public abstract void OnUpdate();
}

The state base class saves the ID of the state (that is, the value in the State enumeration), Mono includes the game object that uses the state, and the Manager is the manager of the state (implemented in the code behind)

OnEnter (the method executed when entering the state) and OnUpdate (the method called every frame in the state) are defined in the state base class. These two methods are implemented in subclasses

FSMManager class

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FSMManager
{
    
    

    public List<FSMState> StateList = new List<FSMState>();
    public int CurrentIndex = -1;

    public void ChangeState(int StateID) {
    
    
        CurrentIndex = StateID;
        StateList[CurrentIndex].OnEnter();
    }

    public void Update() {
    
    
        if (CurrentIndex != -1) {
    
    
            StateList[CurrentIndex].OnUpdate();
        }
    }
}

In this class we use the list StateList to save each state. The ChangeState method is used to change the state, and the OnEnter method of the new state is called once after the state is changed. The Update method is used to execute the OnUpdate of the current state

After creating these two classes, we start to use the state machine framework to implement the game program

1 IdleState

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class IdleState : FSMState
{
    
    

    public IdleState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager) {
    
    

    }

    
    public override void OnEnter() {
    
    
        Mono.GetComponent<Animator>().SetBool("IsRun", false);
    }

    public override void OnUpdate() {
    
    
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir != Vector3.zero) {
    
    
            Manager.ChangeState((int)State.run);
        }
        if (Input.GetKeyDown(KeyCode.Space)) {
    
    
            Manager.ChangeState((int)State.wave);
        }
    }

}

For the rest state, set the IsRun parameter to false in OnEnter. In the OnUpdate method, detect if the arrow key is pressed to switch the state to State.run, and if the space is pressed to switch the state to State.wave

2 RunState

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RunState : FSMState
{
    
    

    public RunState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager) {
    
    

    }

    public override void OnEnter() {
    
    
        Mono.GetComponent<Animator>().SetBool("IsRun", true);
    }

    public override void OnUpdate() {
    
    
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir != Vector3.zero) {
    
    
            Mono.transform.rotation = Quaternion.LookRotation(dir);
            Mono.transform.Translate(Vector3.forward * 3 * Time.deltaTime);
        } else {
    
    
            Manager.ChangeState((int)State.idle);
        }
    }

}

In the OnEnter method, we set the animator parameter IsRun to true. Control the movement of the character in the OnUpdate method, if the player does not press the arrow keys to switch the state to idle

3 WaveState

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class WaveState : FSMState
{
    
    
    public WaveState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager) {
    
    

    }

    public override void OnEnter() {
    
    
       Mono.GetComponent<Animator>().SetTrigger("Wave");
    }

    public override void OnUpdate() {
    
    
        if (!Mono.GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).IsName("wave")) {
    
    
            Manager.ChangeState((int)State.idle);
        }
    }
}

Trigger the Wave Trigger in the OnEnter method. Detect the current animation in OnUpdate and change the state to idle after the wave animation is played

Guess you like

Origin blog.csdn.net/Raine_Yang/article/details/130554161