FSM状态机改

一.前言

FSM状态机初版

之前写过一版有限状态机,后来发现很多问题;

前一个版本是记录了当前的状态,切换状态时,要等下一帧状态机Update的时候才会调动上个状态的退出,总会有一帧的延迟

除了导致动作延迟外,状态很多的情况报错也无法追述,断点只能回到状态机中;

因此做了如下修改;

1.状态机不再继承MonoBehaviour,只需要是单例,存储所有状态基类;

2.状态机提供切换状态的方法SwitchAction,传参下个状态ID;

3.切换状态时调用上一个状态的退出周期,再调用当前状态的开始周期;

4.同时将当前状态的引用重新赋值为传入的状态;

5.状态机提供Run方法给角色控制器调用,角色控制器Update只执行当前状态的Run;

效果展示:

Unity——有限状态机FSM修改-LMLPHP

二.修改

修改后FSM,除增删查外添加切换状态函数SwitchState;

提供FSM状态机的生命周期;FSMInit,FSMRun,FSMEnd;

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

    //切换状态时调用
    public void SwitchState(int nextID)
    {
        curState.OnExit();
        curState = FSMActDic[nextID];
        curState.OnEnter();
    }

    public int GetCurState()
    {
        foreach (var kv in FSMActDic)
        {
            if (kv.Value == curState)
                return kv.Key;
        }

        return -1;
    }

    public FSM()
    {
        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];
    }

    //状态机初始化调用,给curState赋值并调用其OnStay
    public void FSMInit(int id)
    {
        curState = FSMActDic[id];
        curState.OnStay();
    }

    //每帧执行
    public void FSMRun()
    {
        curState.OnStay();
    }

    //退出状态机执行
    public void FSMEnd()
    {
        curState.OnExit();
    }
}

三.测试代码

使用状态先初始化,同时设置初始状态;

角色控制类负责初始化和运行FSM状态机;

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

    public 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 ActAttack((int) PlayerState.jump, this));
        mPlayerFSM.FSMInit((int)PlayerState.idle);
    }

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

    void Update()
    {
        //单纯为了在inspector面板中看到当前状态
        mState = (PlayerState)mPlayerFSM.GetCurState();
        mPlayerFSM.FSMRun();
    }
}

在不同的行为类中,监听输入按键通过owner调用fsm的switch方法,切换状态;

举例移动行为类,监听两个轴的输入,切换idle,同时监听攻击按键切换攻击状态;

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

    //给子类提供方法
    public override void OnEnter(params object[] args)
    {
        owner.mAnimator.Play("Run");
    }

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

        if (Input.GetAxis("Horizontal") > 0 && Input.GetAxis("Vertical") == 0)
        {
            owner.moveDir = owner.transform.right;
        }
        else if (Input.GetAxis("Horizontal") < 0 && Input.GetAxis("Vertical") == 0)
        {
            owner.moveDir = -owner.transform.right;
        }
        else if (Input.GetAxis("Horizontal") > 0 && Input.GetAxis("Vertical") < 0)
        {
            owner.moveDir = owner.transform.right - owner.transform.forward;
        }
        else if (Input.GetAxis("Horizontal") < 0 && Input.GetAxis("Vertical") < 0)
        {
            owner.moveDir = -owner.transform.right - owner.transform.forward;
        }
        else if (Input.GetAxis("Horizontal") > 0 && Input.GetAxis("Vertical") > 0)
        {
            owner.moveDir = owner.transform.right + owner.transform.forward;
        }
        else if (Input.GetAxis("Horizontal") < 0 && Input.GetAxis("Vertical") > 0)
        {
            owner.moveDir = -owner.transform.right + owner.transform.forward;
        }
        else if (Input.GetAxis("Horizontal") == 0 && Input.GetAxis("Vertical") < 0)
        {
            owner.moveDir = -owner.transform.forward;
        }
        else if (Input.GetAxis("Horizontal") == 0 && Input.GetAxis("Vertical") > 0)
        {
            owner.moveDir = owner.transform.forward;
        }

        if (Mathf.Abs(Input.GetAxis("Horizontal")) < 0.1f && Mathf.Abs(Input.GetAxis("Vertical")) < 0.1f)
            owner.mPlayerFSM.SwitchState((int)PlayerControl.PlayerState.idle);

        if (Input.GetAxis("Jump") != 0)
            owner.mPlayerFSM.SwitchState((int)PlayerControl.PlayerState.jump);
    }

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

    }
}

自从出了行为树之后,有限状态机就没太大的用武之地了,后面有机会介绍官方的BehaviourTree插件吧;

11-16 01:17