Unity——FSM有限状态机
阅读原文时间:2023年07月09日阅读:2

FSM有限状态机

1.共同的状态父类,提供可重写的进入,保持,退出该状态的生命周期方法;

2.状态机,管理所有状态(增删查改),状态机运行方法(Run);

3.在角色控制器中,实例化状态机,并初始化添加状态;

1.StateBase

给物体所有状态提供的基类,所有状态比较继承这个基类,并且重写生命周期的方法;

泛型T为角色控制类;

字段:

public int stateID;        //状态ID,string也可
public T owner;           //角色的实例

生命周期:

public abstract void OnEnter(params object[] args);    //进入状态调用
public abstract void OnStay(params object[] args);    //保持状态调用
public abstract void OnExit(params object[] args);    //退出状态调用

完整代码:

public abstract class StateBase<T>
{
    //给每个状态设置一个ID
    public int stateID;
    public T owner;   //拥有者(范型)

    public StateBase(int id,T o)
    {
        this.stateID = id;
        owner = o;
    }

    //给子类提供方法
    public abstract void OnEnter(params object[] args);
    public abstract void OnStay(params object[] args);
    public abstract void OnExit(params object[] args);
}

2.StateMachine

private Dictionary<int, StateBase<T>> FSMActDic;    //存所有状态的字典
private int curState;                                //当前状态
public int nextState = 0;                            //下一个状态

关键方法:

切换状态时,调用当前方法的退出OnExit()后,继续调用下一个方法的进入OnEnter();

其他时候调用当前方法的OnStay();

public void FSMRun()
{
    FSMActDic[curState].OnStay();
    if (nextState != 0 && nextState != curState)
    {
        FSMActDic[curState].OnExit();
        curState = nextState;
        FSMActDic[curState].OnEnter();
        nextState = 0;
    }
}

完整代码:

public class FSM<T>
{
    private Dictionary<int, StateBase<T>> FSMActDic;
    private int curState;
    public int nextState = 0;

    private void SwitchState()
    {
        if (nextState != 0 && nextState != curState)
        {
            FSMActDic[curState].OnExit();
            curState = nextState;
            FSMActDic[curState].OnEnter();
            nextState = 0;
        }
    }

    public FSM(int id)
    {
        curState = id;
        FSMActDic = new Dictionary<int, StateBase<T>>();
    }

    //增
    public void AddState(int id, StateBase<T> state)
    {
        if(FSMActDic.ContainsKey(id))
            return;

        FSMActDic.Add(id, state);
    }

    //删
    public void RemoveSatate(int id)
    {
        if (FSMActDic.ContainsKey(id))
            FSMActDic.Remove(id);
    }

    //获取
    public StateBase<T> GetState(int id)
    {
        if (!FSMActDic.ContainsKey(id))
            return null;

        return FSMActDic[id];
    }

    public void FSMRun()
    {
        FSMActDic[curState].OnStay();
        SwitchState();
    }
}

1.PlayerControl

实例状态机,添加几个状态,测试效果;这里我只做了简单的移动;

完整代码:

public class PlayerControl : MonoBehaviour
{
    public enum PlayerState
    {
        none = 0,
        idle,
        move,
        jump,
    }

    private FSM<PlayerControl> mPlayerFSM;
    public PlayerState mState;
    public Animator mAnimator;
    public float mSpeed;
    public Vector3 moveDir;

    private void InitFSM()
    {
        mPlayerFSM.AddState((int) PlayerState.idle, new ActIdle((int) PlayerState.idle, this));
        mPlayerFSM.AddState((int) PlayerState.move, new ActMove((int) PlayerState.move, this));
        mPlayerFSM.AddState((int) PlayerState.jump, new ActScream((int) PlayerState.jump, this));
    }

    void Start()
    {
        mAnimator = GetComponentInChildren<Animator>();
        mSpeed = 10;
        mPlayerFSM = new FSM<PlayerControl>((int) PlayerState.idle);
        InitFSM();
        mState = PlayerState.idle;
    }   

    void Update()
    {
        mPlayerFSM.FSMRun();  

        SwitchState();

        mPlayerFSM.nextState = (int)mState;
    }

    private void SwitchState()
    {
        if (Input.GetAxis("Horizontal") > 0 && Input.GetAxis("Vertical") == 0)
        {
            moveDir = transform.right;
            mState = PlayerState.move;
        }
        else if (Input.GetAxis("Horizontal") < 0 && Input.GetAxis("Vertical") == 0)
        {
            moveDir = -transform.right;
            mState = PlayerState.move;
        }
        else if (Input.GetAxis("Horizontal") > 0 && Input.GetAxis("Vertical") < 0)
        {
            moveDir = transform.right - transform.forward;
            mState = PlayerState.move;
        }
        else if (Input.GetAxis("Horizontal") < 0 && Input.GetAxis("Vertical") < 0)
        {
            moveDir = -transform.right - transform.forward;
            mState = PlayerState.move;
        }
        else if (Input.GetAxis("Horizontal") > 0 && Input.GetAxis("Vertical") > 0)
        {
            moveDir = transform.right + transform.forward;
            mState = PlayerState.move;
        }
        else if (Input.GetAxis("Horizontal") < 0 && Input.GetAxis("Vertical") > 0)
        {
            moveDir = -transform.right + transform.forward;
            mState = PlayerState.move;
        }
        else if (Input.GetAxis("Horizontal") == 0 && Input.GetAxis("Vertical") < 0)
        {
            moveDir = -transform.forward;
            mState = PlayerState.move;
        }
        else if (Input.GetAxis("Horizontal") == 0 && Input.GetAxis("Vertical") > 0)
        {
            moveDir = transform.forward;
            mState = PlayerState.move;
        }

        if (Mathf.Abs(Input.GetAxis("Horizontal")) < 0.1f && Mathf.Abs(Input.GetAxis("Vertical")) < 0.1f)
            mState = PlayerControl.PlayerState.idle;

        if (Input.GetAxis("Jump") != 0)
        {
            mState = PlayerState.jump;
        }
    }
}

2.行为类

设置的移动,待机,吼叫三个行为类;

代码展示:

public class ActMove : StateBase<PlayerControl>
{
    public ActMove(int id, PlayerControl t) : base(id, t)
    {
    }

    //给子类提供方法
    public override void OnEnter(params object[] args)
    {
    }

    public override void OnStay(params object[] args)
    {
        owner.mAnimator.Play("Walk");
        owner.transform.position += owner.moveDir * Time.deltaTime * owner.mSpeed;
    }

    public override void OnExit(params object[] args)
    {
    }
}

以上是我对FSM的总结,如果有更好的意见,欢迎给作者评论留言;