U3D客户端框架之商业项目中的 FSM 有限状态机 实现代码

简介: FSM有限状态机在游戏中的作用主要是做场景的流程管理,进入场景状态后 加载资源初始化,更新状态时执行更新逻辑,离开场景状态时销毁场景资源,数据清理、角色动作状态切换,进入时播放动作,离开时播放下一个当作等。

一、有限状态机介绍


有限状态机(Finite State Machine, FSM),又称有限状态自动机,简称状态机,是指在有限个状态之间按照一定规律转换的逻辑状态。


状态机有 3 个组成部分:状态、事件、动作。


状态:所有可能存在的状态。包括当前状态和条件满足后要迁移的状态。


事件:也称为转移条件,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。


动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是* 必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。


FSM有限状态机在游戏中的作用主要是做场景的流程管理,进入场景状态后 加载资源初始化,更新状态时执行更新逻辑,离开场景状态时销毁场景资源,数据清理、角色动作状态切换,进入时播放动作,离开时播放下一个当作等。


二、有限状态机的设计


设计的时候考虑了可能同时存在多个状态机,所以把状态机单独的分成了一个类;状态机里面有具体的状态,负责具体状态的生命周期;状态机多了之后就需要有一个管理者去管理状态机的整个生命周期:创建、删除、释放内存;


主要分成了4个类文件:


1.状态机基类 FsmBase.cs


包含状态机所需要的通用数据,所有的基类其实都是设计模式中的模板方法模式,因为基类里面的所有方法对于子类来说,都是一样的,所以叫做模板方法。


FsmBase.cs 代码


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Myh
{
    /// <summary>
    /// 状态机基类
    /// </summary>
    public abstract class FsmBase
    {
        //状态机编号
        public int FsmId { private set; get; }
        //当前状态的类型
        public sbyte CurrStateType;
        public FsmBase(int fsmId)
        {
            FsmId = fsmId;
        }
        //关闭状态机
        public abstract void ShutDown();
    }
}


2.状态机类 Fsm.cs


状态机的具体类,继承了FsmBase类;内部实现了所有具体状态的管理、添加、更新、切换等。


Fsm.cs实现代码


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Myh
{
    //拥有者,拥有这个状态的类型
    public class Fsm<T> : FsmBase where T : class
    {
        //拥有者
        public T Owner { private set; get; }
        //当前状态
        private FsmState<T> m_CurrState;
        //状态字典
        private Dictionary<sbyte, FsmState<T>> m_dicState;
        //参数字典(??切换状态的时候还传参数??)
        private Dictionary<string, VariableBase> m_dicParam;
        /* 
         * 构造函数  需要以下参数
         * fsmId:状态机的id
         * owner:拥有者
         * states:状态机里的所有状态
         */
        public Fsm(int fsmId, T owner, FsmState<T>[] states) : base(fsmId)
        {
            m_dicState = new Dictionary<sbyte, FsmState<T>>();
            m_dicParam = new Dictionary<string, VariableBase>();
            Owner = owner;
            //把状态放入字典
            int len = states.Length;
            for (int i = 0; i < len; ++i)
            {
                FsmState<T> state = states[i];
                if (null != state)
                {
                    state.CurrFsm = this;
                }
                m_dicState[(sbyte)i] = state;
            }
            CurrStateType = -1;
        }
        //获取状态 by 状态类型
        public FsmState<T> GetState(sbyte stateType)
        {
            FsmState<T> state = null;
            m_dicState.TryGetValue(stateType,out state);
            return state;
        }
        //状态机更新
        public void OnUpdate()
        {
            if (null != m_CurrState)
            {
                m_CurrState.OnUpdate();
            }
        }
        //状态机切换 by newStateType
        public void ChangeState(sbyte newState)
        {
            //如果两个状态一致,不再进入
            if (CurrStateType == newState)
                return;
            //先让当前状态执行OnLeave流程,执行上一个状态离开的逻辑
            if (null != m_CurrState)
                m_CurrState.OnLeave();
            CurrStateType = newState;
            m_CurrState = m_dicState[CurrStateType];
            //然后再让新状态执行OnEnter流程,执行新状态的进入逻辑
            m_CurrState.OnEnter();
        }
        //设置参数值
        public void SetData<TData>(string key, TData value)
        {
            VariableBase itemBase = null;
            if (m_dicParam.TryGetValue(key, out itemBase))
            {
                //参数原来存在,更新值
                Variable<TData> item = itemBase as Variable<TData>;
                item.Value = value;
                //其实这里不重新写入也行,itemBase本来就是个class,拿到的本来就是引用
                //m_dicParam[key] = item;
            }
            else
            {
                //参数原来不存在,插入
                Variable<TData> item = new Variable<TData>();
                item.Value = value;
                m_dicParam[key] = item;
            }
        }
        //获取参数值
        public TData GetData<TData>(string key)
        {
            VariableBase itemBase = null;
            if (m_dicParam.TryGetValue(key, out itemBase))
            {
                Variable<TData> item = itemBase as Variable<TData>;
                if (null == item)
                    return default(TData);
                return item.Value;
            }
            //返回该结构的实例?
            return default(TData);
        }
        public override void ShutDown()
        {
            if (null != m_CurrState)
                m_CurrState.OnLeave();
            //循环遍历,执行所有状态的OnDestroy函数,执行销毁逻辑,销毁所有的状态
            foreach (KeyValuePair<sbyte, FsmState<T>> kvPair in m_dicState)
            {
                if (null != kvPair.Value)
                {
                    kvPair.Value.OnDestroy();
                }
            }
            m_dicState.Clear();
            m_dicParam.Clear();
        }
    }
}


3.状态机 状态抽象类 FsmState.cs


状态抽象类不可以直接使用,在实际的开发中使用一个具体的状态来继承 状态抽象类FsmState ,具体的例子可以看测试代码中的实现。abstract改成接口也行,但是后来感觉没啥必要,有些派生类未必一定需要实现所有虚函数,所以就写成了抽象类,4个事件都写成了虚函数。派生类实现不实现都可。


FsmState.cs 代码实现


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Myh
{
    //状态机的状态
    public abstract class FsmState<T> where T : class
    {
        //状态对应的状态机
        public Fsm<T> CurrFsm;
        //进入状态
        public virtual void OnEnter() { }
        //更新状态
        public virtual void OnUpdate() { }
        //离开状态
        public virtual void OnLeave() { }
        //状态机销毁时调用(销毁状态)
        public virtual void OnDestroy() { }
    }
}


4.状态机管理器 FsmManager.cs


状态机管理器内缓存下来来了所有的,状态机;主要负责创建、销毁,有点像是一个模型类,大部分的职责都是增删状态机。


FsmManager.cs代码实现


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Myh
{
    //状态机管理器
    public class FsmManager : ManagerBase, IDisposable
    {
        //状态机字典
        private Dictionary<int, FsmBase> m_dicFsm;
        //状态机的临时编号
        private int m_TemFsmId = 0;
        public FsmManager()
        {
            m_dicFsm = new Dictionary<int, FsmBase>();
        }
        //初始化
        public override void Init()
        {
        }
        #region Create
        /*
         * 创建状态机实例
         * T:拥有者类型
         * fsmId:状态机Id
         * owner:拥有者实例
         * states:状态数组
         */
        public Fsm<T> Create<T>(int fsmId, T owner, FsmState<T>[] states) where T : class 
        {
            Fsm<T> fsm = new Fsm<T>(fsmId, owner, states);
            m_dicFsm[fsmId] = fsm;
            return fsm;
        }
        public Fsm<T> Create<T>(T owner, FsmState<T>[] states) where T : class
        {
            return Create(++m_TemFsmId, owner, states);
        }
        #endregion
        #region 销毁状态机
        //销毁状态机
        public void DestroyFsm(int fsmId)
        {
            FsmBase fsm = null;
            if (m_dicFsm.TryGetValue(fsmId, out fsm))
            {
                fsm.ShutDown();
                m_dicFsm.Remove(fsmId);
            }
        }
        #endregion
        public void Dispose()
        {
            IEnumerator<KeyValuePair<int, FsmBase>> iter= m_dicFsm.GetEnumerator();
            for (; iter.MoveNext();)
            {
                iter.Current.Value.ShutDown();
            }
            m_dicFsm.Clear();
        }
    }
}


三、测试与总结


因为刚开始写,还没办法在实际的工程中去做测试,所以主要通过按下按键就切换状态,测了一下状态切换是否符合预期,结果很好,符合预期。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Myh;
using YouYou;
using UnityEngine;
public class FmsTest : ITest
{
    static private void PrintStateLog(int id, string strEvent)
    {
        Debug.Log("状态机:" + id + " 触发了:" + strEvent + " 事件");
    }
    #region 测试状态类
    enum EnumTestClassType
    {
        eTest1,
        eTest2,
        eTest3,
        eTest4,
    }
    class FsmTestState1 : FsmState<FmsTest>
    {
        public override void OnEnter()
        {
            base.OnEnter();
            PrintStateLog(1, "OnEnter");
        }
        public override void OnUpdate()
        {
            base.OnUpdate();
            PrintStateLog(1, "OnUpdate");
        }
        public override void OnLeave()
        {
            base.OnLeave();
            PrintStateLog(1, "OnLeave");
        }
        public override void OnDestroy()
        {
            base.OnDestroy();
            PrintStateLog(1, "OnDestroy");
        }
    }
    class FsmTestState2 : FsmState<FmsTest>
    {
        public override void OnEnter()
        {
            base.OnEnter();
            PrintStateLog(2, "OnEnter");
        }
        public override void OnUpdate()
        {
            base.OnUpdate();
            PrintStateLog(2, "OnUpdate");
        }
        public override void OnLeave()
        {
            base.OnLeave();
            PrintStateLog(2, "OnLeave");
        }
        public override void OnDestroy()
        {
            base.OnDestroy();
            PrintStateLog(2, "OnDestroy");
        }
    }
    class FsmTestState3 : FsmState<FmsTest>
    {
        public override void OnEnter()
        {
            base.OnEnter();
            PrintStateLog(3, "OnEnter");
        }
        public override void OnUpdate()
        {
            base.OnUpdate();
            PrintStateLog(3, "OnUpdate");
        }
        public override void OnLeave()
        {
            base.OnLeave();
            PrintStateLog(3, "OnLeave");
        }
        public override void OnDestroy()
        {
            base.OnDestroy();
            PrintStateLog(3, "OnDestroy");
        }
    }
    class FsmTestState4 : FsmState<FmsTest>
    {
        public override void OnEnter()
        {
            base.OnEnter();
            PrintStateLog(4, "OnEnter");
        }
        public override void OnUpdate()
        {
            base.OnUpdate();
            PrintStateLog(4, "OnUpdate");
        }
        public override void OnLeave()
        {
            base.OnLeave();
            PrintStateLog(4, "OnLeave");
        }
        public override void OnDestroy()
        {
            base.OnDestroy();
            PrintStateLog(4, "OnDestroy");
        }
    }
    #endregion
    private Fsm<FmsTest> m_fsm=null;
    public void OnTestStart()
    {
        FsmState<FmsTest>[] arrStates = new FsmState<FmsTest>[4];
        arrStates[(int)EnumTestClassType.eTest1] = new FsmTestState1();
        arrStates[(int)EnumTestClassType.eTest2] = new FsmTestState2();
        arrStates[(int)EnumTestClassType.eTest3] = new FsmTestState3();
        arrStates[(int)EnumTestClassType.eTest4] = new FsmTestState4();
        m_fsm = GameEntry.FSM.Create(this, arrStates);
    }
    public void OnTestUpdate()
    {
        if (null == m_fsm)
            return;
        //m_fsm.OnUpdate();
        if (Input.GetKeyDown(KeyCode.Keypad1))
        {
            m_fsm.ChangeState((int)EnumTestClassType.eTest1);
        }
        else if (Input.GetKeyDown(KeyCode.Keypad2))
        {
            m_fsm.ChangeState((int)EnumTestClassType.eTest2);
        }
        else if (Input.GetKeyDown(KeyCode.Keypad3))
        {
            m_fsm.ChangeState((int)EnumTestClassType.eTest3);
        }
        else if (Input.GetKeyDown(KeyCode.Keypad4))
        {
            m_fsm.ChangeState((int)EnumTestClassType.eTest4);
        }
    }
}


相关文章
|
3月前
|
前端开发 JavaScript 中间件
【前端状态管理之道】React Context与Redux大对决:从原理到实践全面解析状态管理框架的选择与比较,帮你找到最适合的解决方案!
【8月更文挑战第31天】本文通过电子商务网站的具体案例,详细比较了React Context与Redux两种状态管理方案的优缺点。React Context作为轻量级API,适合小规模应用和少量状态共享,实现简单快捷。Redux则适用于大型复杂应用,具备严格的状态管理规则和丰富的社区支持,但配置较为繁琐。文章提供了两种方案的具体实现代码,并从适用场景、维护成本及社区支持三方面进行对比分析,帮助开发者根据项目需求选择最佳方案。
59 0
|
4月前
共识协议的技术变迁问题之Acceptor重建窗口的作用是什么
共识协议的技术变迁问题之Acceptor重建窗口的作用是什么
|
4月前
共识协议的技术变迁问题之状态机的命令序列实现如何解决
共识协议的技术变迁问题之状态机的命令序列实现如何解决
|
4月前
|
架构师 存储
软件交付问题之在设计领域模型和状态机时,模型和状态机,如何解决
软件交付问题之在设计领域模型和状态机时,模型和状态机,如何解决
|
4月前
|
测试技术
领域驱动设计问题之状态同步模型与状态机模型的主要区别是什么
领域驱动设计问题之状态同步模型与状态机模型的主要区别是什么
|
6月前
|
数据库 对象存储
状态机的原理简析及重要用途
状态机的原理简析及重要用途
84 1
|
6月前
|
存储 前端开发 JavaScript
【思维扩展】状态机与 React 中的状态
【思维扩展】状态机与 React 中的状态
|
设计模式 数据可视化 Linux
为Linux应用构造有限状态机
为Linux应用构造有限状态机
148 0
|
存储 开发框架 缓存
数据流动的精妙之道——UniApp中的数据通信与状态管理解析
数据流动的精妙之道——UniApp中的数据通信与状态管理解析
|
存储 Kubernetes 负载均衡
【k8s 系列】k8s 学习二十六,有状态的应用如何部署 1?
前面我们分享很多关于 K8S 的内容,有没有发现 pod 都是无状态,RS / RC 管理的 pod 也是无状态的,我们可以任意删除一个 pod,副本管理器又会马上给我们创建一个 pod 那么如果咱们的这个 pod 是有挂载持久卷的,那么我们用老方法可还行?
191 0