我是靠谱客的博主 专注小懒虫,最近开发中收集的这篇文章主要介绍FSM状态机框架,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

编写一个通用的状态框架,不仅可以用于动画的转换,可以针对所有需要用到状态的情况。主要是模拟动画Animator Controller里的作用,写一个简单的框架。

先写一个State的类,放入FSM命名空间,然后创建状态名称StateName与存放状态的字典TransitionStates接着创建切换状态的方法,如添加,移除。接着创建判断状态的三个事件:OnStateEnterOnStateUpdate,OnStateExit。创建进入状态的(带参数)方法:EnterState与ExitState。最后创建判断是否满足过度状态的条件。

using System;
using System.Collections.Generic;

namespace FSM
{
    public class State
    {
        public State(string name)
        {
            StateName = name;
            TransitionStates = new Dictionary<string, Func<bool>>();
            //绑定基础回调
            StateBaseEventBind();
        }
        /// <summary>
        /// 状态名称
        /// </summary>
        public string StateName { get; set; }

        /// <summary>
        /// 标记当前状态是否运行
        /// </summary>
        public bool IsRun { get; set; }
        #region State Base EventBinding
        private void StateBaseEventBind()
        {
            //进入状态标记
            OnStateEnter += objects => { IsRun = true; };
            OnStateExit += objects => { IsRun = false; };
        }
        #endregion
        #region Transition States Control
        /// <summary>
        /// 当前状态可以切换到其他状态
        /// </summary>
        private Dictionary<string, Func<bool>> TransitionStates { get; set; }
        /// <summary>
        /// 注册状态切换
        /// </summary>
        public void RegisterTransitionState(string stateName, Func<bool> condition)
        {
            if (!TransitionStates.ContainsKey(stateName))
            {
                //添加
                TransitionStates.Add(stateName, condition);
            }
            else
            {
                TransitionStates[stateName] = condition;
            }
        }

        /// <summary>
        /// 取消注册切换的状态
        /// </summary>
        /// <param name="stateName"></param>
        public void UnRegisterTransitionState(string stateName)
        {
            if (TransitionStates.ContainsKey(stateName))
            {
                //移除
                TransitionStates.Remove(stateName);
            }
        }
        #endregion

        #region State Machine Event
        /// <summary>
        /// 状态进入事件
        /// </summary>
        public event Action<object[]> OnStateEnter;
        /// <summary>
        /// 状态更新事件
        /// </summary>
        public event Action<object[]> OnStateUpdate;
        /// <summary>
        /// 离开状态事件
        /// </summary>
        public event Action<object[]> OnStateExit;

        /// <summary>
        /// 进入状态
        /// </summary>
        /// <param name="paramters"></param>
        public virtual void EnterState(object[] enterEventparamters,object[] updateEventParamters)
        {
            if(OnStateEnter!=null)
            {
                //执行进入状态的事件
                OnStateEnter(enterEventparamters);
            }
            //绑定当前状态的更新事件,以便后期执行
            MonoHelper.Instance.AddUpdateEvent(StateName, OnStateUpdate, updateEventParamters);
        }
        /// <summary>
        /// 离开状态
        /// </summary>
        /// <param name="paramters"></param>
        public virtual void ExitState(object[] paramters)
        {
            //解除绑定当前状态的更新事件,以便后期停止执行
            MonoHelper.Instance.RemoveUpdateEvent(StateName);
            if(OnStateExit!=null)
            {
                //执行离开状态事件
                OnStateExit(paramters);
            }

        }
        #endregion
        #region
        /// <summary>
        /// 检测状态过度
        /// </summary>
        /// <returns></returns>
        public string CheckTransition()
        {
            foreach (var item in TransitionStates)
            {
                //执行判断条件
                if(item.Value())
                {
                    //条件满足,过度状态
                    return item.Key;
                }
            }
            return null;
        }
        #endregion
    }
}

然后创建一个类:MonoHelper继承MonoBehaviour。在里面创建一个状态更新模块用于存放状态名称,状态更新事件,触发状态更新的参数。创建字典StateUpdateModuleDic与数组stateUpdateModuleArray。创建方法DicToArray将字典里的状态存放进数组用于更新。创建添加事件与移除事件的方法AddUpdateEvent与RemoveUpdateEvent。利用协程控制程序的执行间隔。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using NUnit.Framework;
namespace FSM
{
    public class MonoHelper : MonoBehaviour
    {
        /// <summary>
        /// 状态更新模块
        /// </summary>
        class StateUpdateModule
        {

            /// <summary>
            /// 状态更新事件
            /// </summary>
            public Action<object[]> stateUpdateEvent;
            /// <summary>
            /// 触发状态更新事件的参数
            /// </summary>
            public object[] stateUpdateEventParamters;

            public StateUpdateModule(Action<object[]> e,object[] paras)
            {
                stateUpdateEvent = e;
                stateUpdateEventParamters = paras;
            }
        }
        /// <summary>
        /// 单例脚本
        /// </summary>
        public static MonoHelper Instance;
        private void Awake()
        {
            Instance = this;
            StateUpdateModuleDic = new Dictionary<string, StateUpdateModule>();
        }

        //更新事件执行的时间间隔
        public float invokeInterval = -1;
        //状态更新模块字典
        private Dictionary<string, StateUpdateModule> StateUpdateModuleDic;
        //状态更新模块数组
        private StateUpdateModule[] stateUpdateModuleArray;

        private void DicToArray()
        {
            //实例化数组
            stateUpdateModuleArray = new StateUpdateModule[StateUpdateModuleDic.Count];
            //计数器
            int counter = 0;
            foreach (var item in StateUpdateModuleDic)
            {
                //模块事件对象
                stateUpdateModuleArray[counter] = item.Value;
                //计数器递增
                counter++;
            }
        }

        /// <summary>
        /// 添加更新事件
        /// </summary>
        /// <param name="stateName">状态名称</param>
        /// <param name="updateEvent">更新事件</param>
        /// <param name="updataEventParamters">更新事件执行时用的参数</param>
        public void AddUpdateEvent(string stateName,Action<object[]> updateEvent,object[] updataEventParamters)
        {
            //如果字典中存在该状态
            if(StateUpdateModuleDic.ContainsKey(stateName))
            {
                //更新Update事件及参数
                StateUpdateModuleDic[stateName] = new StateUpdateModule(updateEvent, updataEventParamters)
;            }
            else
            {
                //添加Update事件及参数
                StateUpdateModuleDic.Add(stateName, new StateUpdateModule(updateEvent, updataEventParamters));
            }
            //倒一下
            DicToArray();
        }
        /// <summary>
        /// 移除事件
        /// </summary>
        /// <param name="stateName"></param>
        public void RemoveUpdateEvent(string stateName)
        {
            if(StateUpdateModuleDic.ContainsKey(stateName))
            {
                //移除
                StateUpdateModuleDic.Remove(stateName);
                //倒一下
                DicToArray();
            }
        }

        private IEnumerator Start()
        {        
            while(true)
            {
                if(invokeInterval<=0)
                {
                    //等一帧
                    yield return 0;
                }
                else
                {
                    //等一个时间间隔
                    yield return new WaitForSeconds(invokeInterval);
                }
                //执行事件
                for (int i = 0; i < stateUpdateModuleArray.Length; i++)
                {
                    if(stateUpdateModuleArray[i].stateUpdateEvent!=null)
                    {
                        stateUpdateModuleArray[i].stateUpdateEvent(stateUpdateModuleArray[i].stateUpdateEventParamters);
                    }
                }
            }
        }
    }
}

创建一个控制状态机的类:StateMachine。继承State类。创建一个存放状态的字典manageredStates。创建两个状态一个默认状态,一个当前状态。创建添加状态和移除状态的方法。添加状态时,第一个被添加的状态设置为默认状态。移除状态时需要注意如果要移除的状态是当前运行的状态且它不存在,则移除该状态,如果该状态是默认状态,则需要创建一个方法ChooseNewDefaultState,选择一个新的默认状态。重写State类里的进入状态与离开状态的方法。进入状态方法判断进入的状态是否是普通状态,若是则执行State类的进入状态,若不是则执行StateMachine类的进入状态。创建一个判断是否满足过度条件的方法,若是满足过度条件,那么该状态可以过度到另外一个状态,若是不满足,则不能过度。

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

namespace FSM
{
    public class StateMachine : State
    {
        public StateMachine(string name) : base(name)
        {
            manageredStates = new Dictionary<string, State>();
            //绑定初始事件
            StateMachineEventBind();
        }
        /// <summary>
        /// 绑定初始事件
        /// </summary>
        private void StateMachineEventBind()
        {
            OnStateUpdate += ChectCurrentStateTransition;
        }
        #region Manager States
        /// <summary>
        /// 被管理的状态
        /// </summary>
        private Dictionary<string,State> manageredStates;
        /// <summary>
        /// 默认状态
        /// </summary>
        private State defaultState;
        /// <summary>
        /// 当前状态
        /// </summary>
        private State currentState;
        /// <summary>
        /// 添加状态
        /// </summary>
        /// <param name="stateName"></param>
        public State AddState(string stateName)
        {
            if(manageredStates.ContainsKey(stateName))
            {
                Debug.LogWarning("该状态已存在");
                return manageredStates[stateName];
            }
            //创建状态
            State crtState = new State(stateName);
            
            //添加状态
            manageredStates.Add(stateName,  crtState);
            //当前添加的状态是第一个状态
            if(manageredStates.Count==1)
            {
                //当前状态结束默认状态
                defaultState = crtState;
            }
            //返回状态
            return crtState;
        }
        /// <summary>
        /// 添加状态
        /// </summary>
        /// <param name="crtState"></param>
        public void AddState(State crtState)
        {
            if (manageredStates.ContainsKey(crtState.StateName))
            {
                Debug.LogWarning("该状态已存在");
                return;
            }
            //添加状态
            manageredStates.Add(crtState.StateName, crtState);
            //当前添加的状态是第一个状态
            if (manageredStates.Count == 1)
            {
                //当前状态结束默认状态
                defaultState = crtState;
            }
        }
        /// <summary>
        /// 移除状态
        /// </summary>
        /// <param name="stateName"></param>
        public void RemoveState(string stateName)
        {
            if (IsRun)
            {
                Debug.Log("状态已经启动,不能进行状态删除");
                return;
            }
            //如果当前状态在状态机内,且不是当前状态
            if(manageredStates.ContainsKey(stateName))
            {
                //当前要删除的状态
                State crtState = manageredStates[stateName];
                //移除状态
                manageredStates.Remove(stateName);
                //如果当前状态是默认状态
                if (crtState==defaultState)
                {
                    //清除上一次的默认状态
                    defaultState = null;
                    //选择新的默认状态
                    ChooseNewDefaultState();
                }
            }
        }
        /// <summary>
        /// 选择新的默认状态
        /// </summary>
        private void ChooseNewDefaultState()
        {
            foreach (var item in manageredStates)
            {
                //遍历到的第一个状态即设置为默认状态
                defaultState = item.Value;
                return;
            }
        }
        #endregion
        #region State Machine Event
        /// <summary>
        /// 状态机的进入
        /// </summary>
        /// <param name="enterEventparamters"></param>
        /// <param name="updateEventParamters"></param>
        public override void EnterState(object[] enterEventparamters, object[] updateEventParamters)
        {
            //先执行当前状态机的进入事件
            base.EnterState(enterEventparamters, updateEventParamters);
            //再执行子状态的进入
            //判断是否有默认状态
            if (defaultState==null)
            {
                return;
            }
            //此时当前状态就是默认状态
            currentState = defaultState;
            //当前状态执行进入事件【进入默认的子状态】
            currentState.EnterState(enterEventparamters, updateEventParamters);

        }
        /// <summary>
        /// 状态机的离开
        /// </summary>
        /// <param name="paramters"></param>
        public override void ExitState(object[] paramters)
        {
            //如果当前状态不为空
            if(currentState!=null)
            {
                //当前状态先离开
                currentState.ExitState(paramters);
            }
            //状态机再离开
            base.ExitState(paramters);
        }
        #endregion
        #region State Machine Check State Transition
        /// <summary>
        /// 检测当前状态是否满足过度条件,满足就过度
        /// </summary>
        private void ChectCurrentStateTransition(object[] objs)
        {
            //检测过度事件
            string targetState= currentState.CheckTransition();
            //当前状态的过度时间满足了
            if(targetState!=null)
            {
                //过渡到新状态
                TransitionToState(targetState);
            }
        }

        private void TransitionToState(string targetStateName)
        {
            //要过度的状态是否被当前状态机所管理
            if(manageredStates.ContainsKey(targetStateName))
            {
                //当前状态离开
                currentState.ExitState(null);
                //切换当前状态为新状态
                currentState = manageredStates[targetStateName];
                //新的当前状态执行进入
                currentState.EnterState(null,null);
            }
        }
        #endregion
    }
}

下面看看怎么用:

using UnityEngine;
using FSM;

public class UseFSM : MonoBehaviour
{
    /// <summary>
    /// 总状态机
    /// </summary>
    private StateMachine leader;

    [Range(0,10)]
    [Header("速度参数")]
    public float Speed;

    private void Start()
    {
        //实例化
        leader = new StateMachine("Leader");
        //创建子状态
        State idleState = new State("Idle");
        State walkState = new State("Walk");
        State runState = new State("Run");

        //创建子状态机
        StateMachine locomotionState = new StateMachine("Locomotion");
        //建立状态关系
        locomotionState.AddState(walkState);
        locomotionState.AddState(runState);
        leader.AddState(idleState);
        leader.AddState(locomotionState);

        //添加状态事件
        leader.OnStateEnter += objects => { Debug.Log("Leader Enter"); };
        leader.OnStateUpdate += objects => { Debug.Log("Leader Update"); };
        leader.OnStateExit += objects => { Debug.Log("Leader Exit"); };

        locomotionState.OnStateEnter += objects => { Debug.Log("locomotionState Enter"); };
        locomotionState.OnStateUpdate += objects => { Debug.Log("locomotionState Update"); };
        locomotionState.OnStateExit += objects => { Debug.Log("locomotionState Exit"); };

        idleState.OnStateEnter += objects => { Debug.Log("idleState Enter"); };
        idleState.OnStateUpdate += objects => { Debug.Log("idleState Update"); };
        idleState.OnStateExit += objects => { Debug.Log("idleState Exit"); };

        walkState.OnStateEnter += objects => { Debug.Log("walkState Enter"); };
        walkState.OnStateUpdate += objects => { Debug.Log("walkState Update"); };
        walkState.OnStateExit += objects => { Debug.Log("walkState Exit"); };

        runState.OnStateEnter += objects => { Debug.Log("runState Enter"); };
        runState.OnStateUpdate += objects => { Debug.Log("runState Update"); };
        runState.OnStateExit += objects => { Debug.Log("runState Exit"); };

        //添加状态的过度关系
        idleState.RegisterTransitionState("Locomotion",() => { return Speed > 1; });
        locomotionState.RegisterTransitionState("Idle", () => { return Speed <= 1; });
        walkState.RegisterTransitionState("Run", () => { return Speed > 5; });
        runState.RegisterTransitionState("Walk", () => { return Speed <= 5; });

        //启动状态机
        leader.EnterState(null, null);

    }


}

开始的时候可以看出当Speed为0的时候,开始执行一直执行Leader Enter与idleState Enter,然后就一直执行:Leader Update与idleState Update。

 当Speed大于1之后开始执行Walk状态,Idle状态离开。

当Speed大于5时,Walk状态离开,Run状态开始执行。

 

最后

以上就是专注小懒虫为你收集整理的FSM状态机框架的全部内容,希望文章能够帮你解决FSM状态机框架所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(49)

评论列表共有 0 条评论

立即
投稿
返回
顶部