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);
        }
    }
}


相关文章
|
4月前
共识协议的技术变迁问题之状态机的命令序列实现如何解决
共识协议的技术变迁问题之状态机的命令序列实现如何解决
|
4月前
|
架构师 存储
软件交付问题之在设计领域模型和状态机时,模型和状态机,如何解决
软件交付问题之在设计领域模型和状态机时,模型和状态机,如何解决
|
6月前
|
数据库 对象存储
状态机的原理简析及重要用途
状态机的原理简析及重要用途
85 1
|
设计模式 数据可视化 Linux
为Linux应用构造有限状态机
为Linux应用构造有限状态机
148 0
|
存储 开发框架 缓存
数据流动的精妙之道——UniApp中的数据通信与状态管理解析
数据流动的精妙之道——UniApp中的数据通信与状态管理解析
|
存储 缓存 负载均衡
计网 - 怎样实现 RPC 框架
计网 - 怎样实现 RPC 框架
107 0
|
存储 缓存 NoSQL
【Laravel框架】对于Laravel框架架构的研究以及视图方法和内置会话在项目里的运用
【Laravel框架】对于Laravel框架架构的研究以及视图方法和内置会话在项目里的运用
295 0
【Laravel框架】对于Laravel框架架构的研究以及视图方法和内置会话在项目里的运用
|
算法 Java Android开发
抽丝剥茧聊Kotlin协程之Job是如何建立结构化并发的双向传播机制关系的
抽丝剥茧聊Kotlin协程之Job是如何建立结构化并发的双向传播机制关系的
抽丝剥茧聊Kotlin协程之Job是如何建立结构化并发的双向传播机制关系的
|
缓存 移动开发 前端开发
从 SWR 开始 — 一窥现代请求 hooks 设计模型
本文将以 swr 为例子,讲述现在最热门的 useRequest、swr 和 react-query 三个请求 hooks 的新机制,以及新机制后 class Component 和 hooks 在设计上的区别。
从 SWR 开始 — 一窥现代请求 hooks 设计模型
|
存储 数据库 开发框架
Newbe.Claptrap - 一套以 “事件溯源” 和“Actor 模式”作为基本理论的服务端开发框架
Newbe.Claptrap - 一套以 “事件溯源” 和“Actor 模式”作为基本理论的服务端开发框架 本文是关于 Newbe.Claptrap 项目主体内容的介绍,读者可以通过这篇文章,大体了解项目内容。
8931 0
下一篇
无影云桌面