Teach you to do FSM state machine in Unity

Keywords: C# Unity

        The last article talked about the implementation of the general state machine (at the end of the article). This time, I will talk about how to use it in practice. This time, first do a simple state machine to control Animator.
        Since the models I use for animation only have three states: Idle, Run and Die, it's relatively simple. This time I'm just teaching basic use. Next time I'll talk about how to do AI patrol.
        I won't introduce the animation machine. I won't learn the previous tutorial first. I'll skip the construction process.

        If there are walking animations, you can use x to make a simple hybrid tree. What's more, the examples used for practice are not so rigorous. x indicates the current speed, IsDie indicates whether it is dead, and the animation machine is partially completed.
        For the character part, you need to mount the character controller and Animator components, and remember to mount the script components.
        First declare enumeration. There are three states, which can actually be divided into two: Movement and Die. Enumeration completion.

public enum FSMCharacterState
{
    Movement,
    Die
}

        In order to be rigorous, first create a deer's state machine base class, which inherits from the state machine base class (see my last article for details, there is a portal at the end). Because animator controls animation and character controller controls movement, you need to declare these two fields, pass them in and assign values in the constructor, and the deer's state machine base class is also completed.

public abstract class FSMDeer : FSMBase<FSMCharacterState>
{
    protected Animator deerAnimator;
    protected CharacterController characterController;
    protected FSMDeer(FSMManagerBase<FSMCharacterState> manager,GameObject deer) : base(manager)
    {
        deerAnimator = deer.GetComponent<Animator>();
        characterController = deer.GetComponent<CharacterController>();
    }
}

        Next, it's time to implement FSMDeerMove and FSMDeerDie classes. The movement should be implemented by character controller, so you need to re declare the OnAnimatorMove method to cover the displacement in the animation clip. Here's the difference between the constructor and OnEnter. When the constructor is new, it is generated only once, while OnEnter is called every time you enter this state, Similar to the OnEnable function, it is called every time it is activated, so you don't need to write the global variable assigned every time in the constructor.
        Speed must be controlled when moving, so a constant speed is declared, and the. characterController.Move method needs to pass a Vec3 type parameter. In order to avoid gc, the global variable moveStepSize is declared, new is in the constructor, and the set method is called when used.

public class FSMDeerMove : FSMDeer
{
    private static readonly int X = Animator.StringToHash("X");
    private Vector3 _moveStep;
    private sbyte _speed = 8;
    public FSMDeerMove(FSMManagerBase<FSMCharacterState> manager,GameObject deer) : base(manager,deer)
    {
        ClassState = FSMCharacterState.Movement;
        _moveStep = new Vector3();
    }

    public sealed override FSMCharacterState ClassState { get; protected set; }
    public override void OnEnter()
    {

    }

    public override void OnUpdate()
    {

    }

    public override void OnFixedUpdate()
    {
        
    }

    public override void OnExit()
    {

    }

    public void OnAnimatorMove()
    {

    }

        It's not easy to change the death state here, so press the button instead.

    public override void OnUpdate()
    {
        if (Input.GetKeyDown(KeyCode.L))
        {
            manager.SetCurrentState(manager.GetState(FSMCharacterState.Die));
        }
    }

        OnAnimatorMove doesn't need to worry about state transition, so just move and animation. Here, the virtual axis is used to control the movement transition.

        var h = Input.GetAxisRaw("Horizontal");
        var v = Input.GetAxisRaw("Vertical");
        _moveStep.Set(h * Time.deltaTime * _speed, 0, v * Time.deltaTime * _speed);
        characterController.Move(_moveStep);
        if (h != 0 || v != 0)
        {
            deerAnimator.SetFloat(X, _speed);
        }
        else
        {
            deerAnimator.SetFloat(X, 0);
        }

        Here, the Move class code is written. The complete class is:

public class FSMDeerMove : FSMDeer
{
    private static readonly int X = Animator.StringToHash("X");
    private Vector3 _moveStep;
    private sbyte _speed = 8;
    public FSMDeerMove(FSMManagerBase<FSMCharacterState> manager,GameObject deer) : base(manager,deer)
    {
        ClassState = FSMCharacterState.Movement;
        _moveStep = new Vector3();
    }

    public sealed override FSMCharacterState ClassState { get; protected set; }
    public override void OnEnter()
    {

    }

    public override void OnUpdate()
    {
        if (Input.GetKeyDown(KeyCode.L))
        {
            manager.SetCurrentState(manager.GetState(FSMCharacterState.Die));
        }
    }
    
    public override void OnFixedUpdate()
    {
        
    }

    public override void OnExit()
    {

    }

    public void OnAnimatorMove()
    {
        var h = Input.GetAxisRaw("Horizontal");
        var v = Input.GetAxisRaw("Vertical");
        _moveStep.Set(h * Time.deltaTime * _speed, 0, v * Time.deltaTime * _speed);
        characterController.Move(_moveStep);
        if (h != 0 || v != 0)
        {
            deerAnimator.SetFloat(X, _speed);
        }
        else
        {
            deerAnimator.SetFloat(X, 0);
        }
    }

        Let's start writing the Die class. This is even simpler. Just return to the Movement state after the death animation is played. There is too little logic. I'll post it directly.

public class FSMDeerDie : FSMDeer
{
    private static readonly int IsDie = Animator.StringToHash("IsDie");

    public FSMDeerDie(FSMManagerBase<FSMCharacterState> manager, GameObject deer) : base(manager, deer)
    {
        ClassState = FSMCharacterState.Die;
    }

    public sealed override FSMCharacterState ClassState { get; protected set; }
    public override void OnEnter()
    {
        deerAnimator.SetBool(IsDie,true);
    }

    public override void OnUpdate()
    {
        if (deerAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime > 0.95f)
        {
            manager.SetCurrentState(manager.GetState(FSMCharacterState.Movement));
        }
    }

    public override void OnFixedUpdate()
    {
        
    }

    public override void OnExit()
    {
        deerAnimator.SetBool(IsDie, false);
    }
}

        After writing the state class of deer, it's time to write the management class. The base class is abstract, so it should be implemented after inheritance. Because there is a OnAnimatorMove method in the move class of the deer, it is necessary to call the frame by frame in the management class, and then declare the OnAnimatorMove method after inheritance. At this time, only FSMDeerMove calls this method, and then declares a global variable. fsmDeer, when calling the get method, use as to convert the current state. If it is not empty, call or use getType to obtain the type. Therefore, the manager code is:

public class FSMManagerDeer : FSMManagerBase<FSMCharacterState>
{
    private FSMDeerMove _fsmDeer;
    public FSMDeerMove FsmDeer
    {
        get
        {
            _fsmDeer = CurrentState as FSMDeerMove;
            return _fsmDeer;
        }
    }

    public FSMManagerDeer(Dictionary<FSMCharacterState, FSMBase<FSMCharacterState>> dictionary) : base(dictionary)
    {
        
    }
    
    public void OnAnimatorMove()
    {
        FsmDeer?.OnAnimatorMove();
    }
}

        Create a new script and mount it on the deer. You only need to create a manager class, add a state in the manager, and set an entry state to use it happily! Complete!

public class DeerController : MonoBehaviour
{
    private FSMManagerDeer _manager;
    
    private void OnAnimatorMove()
    {
        _manager.OnAnimatorMove();
    }

    private void Update()
    {
        _manager.Update();
    }

    private void FixedUpdate()
    {
        _manager.FixedUpdate();
    }

    private void Awake()
    {
        var list = new Dictionary<FSMCharacterState, FSMBase<FSMCharacterState>>();
        _manager = new FSMManagerDeer(list);
        _manager.AddState(new FSMDeerDie(_manager,gameObject));
        _manager.AddState(new FSMDeerMove(_manager,gameObject));
        _manager.SetCurrentState(_manager.GetState(FSMCharacterState.Movement));
    }
}

        Notice for the next section: AI patrol, catch the last article by the way
       Teach you to do FSM state machine in Unity (I)

Posted by allspiritseve on Sat, 06 Nov 2021 17:10:36 -0700