学习笔记(二) 简单的状态模式&FSM有限状态机框架的实现

首先在我们讲我们的主角之前,先来看一张图。
在这里插入图片描述
你的第一反应是什么?哇,好大一张蜘蛛网!
而事实上它连蜘蛛网都不是。

在制作游戏的过程中,动画是必不可少的东西,Unity的新版动画系统已经很好的帮我们解决了动画管理难的问题。
但是它虽然方便了我们,但同样不可避免的出现了某种意外,也就是上面所示的“蜘蛛网”的状态。
当然,虽然出现了这种情况,也并不是没有解决方案的,也就是我们接下来所说的FSM有限状态机。

FSM有限状态机(Finite-state machine)
它是专门用于解决这种多状态切换导致的问题的。
使用动画状态机中的 Any State(任意状态),可以使得蜘蛛网瞬间变得清晰:
在这里插入图片描述
看,多么清晰明了。
那么接下来,我们需要做的就非常简单了,写一个状态机的管理!
首先来分析,我们什么时候需要状态,或者说,我们的有限状态机能做什么?
接下来给大家简单的分析一下代码实现。
首先创建一个脚本StateBase,是所有状态的父类。
这个脚本中一共有3个方法:进入、持续、退出;和一个管理者一个调用者。

/// <summary>
/// 转换的条件
/// </summary>
public enum Transition
{
    //空状态
    NullTrans = 0,
    //按下空格
    ClickSp,
    //血量清空
    HpClear
}

/// <summary>
/// 状态ID
/// </summary>
public enum State
{
    NullState = 0,
    //idle
    Idle,
    //跳跃中
    Jump,
    //死亡
    Death
}

public abstract class StateBase {
    /// <summary>
    /// 状态字典
    /// </summary>
    protected Dictionary<Transition, State> mStateDic = new Dictionary<Transition, State>();	//定义一个字典,键为转换条件,值为状态ID
    
    /// <summary>
    /// 状态控制器    他们要知道是谁在控制它
    /// </summary>
    public FSMStateCtrl StateCtrl;
    
    /// <summary>
    /// 当前类对应的状态
    /// </summary>
    public State CurrentState;

    public Rigidbody rig;

    /// <summary>
    /// 初始化
    /// </summary>
    public StateBase(State state ,FSMStateCtrl StateCtrl,Rigidbody rig)
    {
        CurrentState = state;
        this.StateCtrl = StateCtrl;
        this.rig = rig;
    }

    /// <summary>
    /// 添加状态
    /// </summary>
    public void AddState(Transition trans,State state)	//添加一个状态进入字典中
    {
        //这里一系列防误操作判断
        if (mStateDic.ContainsKey(trans))
        {
            Debug.LogError("FSM状态机错误:【"+trans+"】状态已添加,不可重复添加!");return;
        }
        mStateDic[trans] = state;
    }
    
    /// <summary>
    /// 删除
    /// </summary>
    public void DeleteState(State state)		//移除一个状态
    {
        foreach (Transition tran in mStateDic.Keys)
        {
            if (mStateDic[tran].Equals(state))
            {
                mStateDic.Remove(tran); return;
            }
        }
    }

    /// <summary>
    /// 从他身上拿到要转换的状态
    /// </summary>
    public State GetState(Transition trans)		//由于分解耦合性的思想,这里将转换条件的获取由条件自己而来,就是当前状态满足了这个条件后,它自身有一个对应的跳转状态。
    {										//这样可以将每个状态都区分开。
        if (mStateDic.ContainsKey(trans))
        {
            return mStateDic[trans];
        }
        return State.NullState;
    }
    //定义虚方法
    //每帧调用  用于检测是否需要转换
    public abstract void Act(Transform player);
    //每帧调用  用于执行事件
    public abstract void Continue(Transform player);
    //进入时调用
    public virtual void OnEnter() { }
    //退出时调用
    public virtual void OnExit() { }
}

然后在创建一个脚本StateManager,状态机的管理类。
它持有一个状态的集合,和一个当前状态。其中有几个方法,分别是往集合中添加状态、移除状态的方法,还有更换当前状态的方法。

//用于控制状态机
public class FSMStateManager : MonoBehaviour
{
    /// <summary>
    /// 状态的集合,所有状态在里面存着
    List<StateBase> mStateList;

    /// <summary>
    /// 当前状态
    /// </summary>
    private StateBase CuttouenState;

    private void Awake()
    {
        //初始化
        mStateList = new List<StateBase>();
    }

    private void Update()
    {
        //如果当前状态不为空
        if (CuttouenState != null)
        {
            CuttouenState.Act(transform);		//执行每帧调用的方法
            CuttouenState.Continue(transform);
        }
    }

    /// <summary>
    /// 添加一个状态
    /// </summary>
    /// <param name="stateb"></param>
    public void AddState(StateBase stateb)
    {
        //这里不能重复添加,所以一系列防误触
        if (mStateList.Contains(stateb))
        {
            Debug.LogError("FSM错误:【"+stateb+"】已存在,添加失败!");
        }
        //将第一个进入的状态设置为初始状态
        if (mStateList.Count == 0)
        {
            CuttouenState = stateb;
        }
        mStateList.Add(stateb);
    }

    /// <summary>
    /// 切换状态,通过条件,状态身上拿到它要转换到的状态ID
    /// </summary>
    public void CutState(Transition trans)
    {
        State s = CuttouenState.GetState(trans);
        if (s == State.NullState)
        {
            Debug.Log("转到状态为空!"); return;
        }
        for (int i = 0; i < mStateList.Count; i++)
        {
            if (s == mStateList[i].CurrentState)
            {
                CuttouenState.OnExit();
                CuttouenState = mStateList[i];
                CuttouenState.OnEnter();
            }
        }
    }
}

之后,当我们需要添加一个状态的时候,就可以继承StateBase状态基类,实现它身上的方法:

public class IdleState : StateBase
{
    //实现父类构造方法
    public IdleState(State CurrentState, FSMStateCtrl StateCtrl,Rigidbody rig) :base(CurrentState, StateCtrl, rig)
    {

    }
    //重写判断条件(Update中每帧调用的)
    public override void Act(Transform player)
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            StateCtrl.CutState(Transition.ClickSp);	//这里枚举是转换条件:当按下空格
                 //对应的事件是:进入跳跃状态	
        }
    }
    public override void Continue(Transform player)	//每帧调用:执行事件
    {
        Debug.Log("我是Idle");
    }
}

接下来只需要一个启动状态机的类,就可以完成啦:

/// <summary>
/// 用于启动状态机
/// </summary>
public class StateCtrl: MonoBehaviour {

    	FSMStateCtrl mFsmStateCtrl;

	void Start () {
        //初始化   得到他身上的FSM控制器
        mFsmStateCtrl = GetComponent<FSMStateCtrl>();
         
        //创建几个状态出来,并且给他们添加上可以转换到的状态和转换条件。
        IdleState idle = new IdleState(State.Idle, mFsmStateCtrl,transform.GetComponent<Rigidbody>());
        idle.AddState(Transition.DoubleClickSp, State.Death);
        idle.AddState(Transition.HpClear, State.Death);

        DeathState death = new DeathState(State.Death, mFsmStateCtrl, transform.GetComponent<Rigidbody>());
        death.AddState(Transition.IsDown, State.Idle);

        //把这几个状态添加进状态机中
        mFsmStateCtrl.AddState(idle);
        mFsmStateCtrl.AddState(death);
    }
}

这样,我们只需要定义自己的状态,然后通过转换,就可以实现所需要的需求啦,是不是很方便。

下面是笔者简单的总结出来这套状态框架的使用方法:
1、给需要使用状态的物体添加上 StateManager(状态管理器)
2、创建出自己的StateCtrl(状态控制器)并挂在在游戏物体上 - 命名格式:物体名+StateCtrl;
3、在状态枚举类中(EnumBase)按分类添加对应的转换条件和状态ID,命名格式要加上物体名。
4、创建出实际状态类,继承StateBase,并且实现方法与构造方法(这里说一下,如果你的执行事件需要其他的某些条件,直接就在这个状态类中获取,它身上有进入方法,可以进行初始化赋值)
5、在StateCtrl中获取到StateManager(GetComponent方法获取)
6、在StateCtrl中创建状态对象(在start中用new)
7、使用状态的Add方法添加转换条件和对应状态
8、将这些状态用创建好的StateManager对象.Add方法添加入管理器中
9、至此完成步骤。

好了,今天的分享就到这里结束啦,感谢大家的观看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值