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月前
|
传感器 自然语言处理 资源调度
AR 交互与自动感应技术的博物馆智慧导览系统功能解析
本系统结合AR图像识别、自动感应与多语言资源管理,实现虚拟内容与文物精准叠加、自动讲解与智能导航,提升博物馆导览体验智能化、互动性。
361 1
|
XML 存储 JSON
JSON、JSONObject 与 JSONArray 详细介绍及其应用方式
JSON、JSONObject 与 JSONArray 详细介绍及其应用方式
4542 3
JSON、JSONObject 与 JSONArray 详细介绍及其应用方式
|
消息中间件 NoSQL Java
分布式事务之事务实现模式与技术(四)
在分布式系统中实现的事务就是分布式事务,分布式系统的CAP原则是: • 一致性 • 可用性 • 分区容错性 是分布式事务主要是保证数据的一致性,主要有三种不同的原则 • 强一致性 • 弱一致性 • 最终一致性
564 0
分布式事务之事务实现模式与技术(四)
|
机器学习/深度学习 监控 搜索推荐
从零开始构建:使用Hologres打造个性化推荐系统的完整指南
【10月更文挑战第9天】随着互联网技术的发展,个性化推荐系统已经成为许多在线服务不可或缺的一部分。一个好的推荐系统可以显著提高用户体验,增加用户粘性,并最终提升业务的转化率。本指南将详细介绍如何使用阿里云的Hologres数据库来构建一个高效的个性化推荐系统。我们将涵盖从数据准备、模型训练到实时推荐的整个流程。
704 0
|
缓存 安全 算法
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
168 0
|
11月前
|
SQL 关系型数据库 数据库
国产数据实战之docker部署MyWebSQL数据库管理工具
【10月更文挑战第23天】国产数据实战之docker部署MyWebSQL数据库管理工具
768 4
国产数据实战之docker部署MyWebSQL数据库管理工具
|
Java 监控 自然语言处理
一站式链路追踪:阿里云的端到端解决方案
端到端链路追踪是覆盖全部关联 IT 系统,能够完整记录用户行为在系统间调用路径与状态的最佳实践方案。而真正实现端到端链路追踪,需要解决三个难题:链路插桩、链路采集与加工、链路上下文透传。阿里云 ARMS 目前已支持全链路端到端追踪,快来查看转发吧~
61850 107
|
JavaScript 前端开发 Java
Spring Boot+cucumber
本文介绍了使用 Spring Boot 和 Cucumber 进行行为驱动开发的过程。首先,通过 start.spring.io 创建一个包含 Web 依赖的项目,并修改 `pom.xml` 文件以添加相关依赖。接着,展示了如何编写和运行简单的 Hello World 示例。然后,详细描述了一个更复杂的 ATM 服务示例,包括定义功能、编写测试文件、实现服务类以及验证 PIN 码的功能。最后,通过 JUnit 运行测试以确保功能正确。
236 0
Spring Boot+cucumber
|
Java 测试技术 API
Python的api自动测试选择合适的测试框架
【4月更文挑战第18天】在Python API自动测试中,选择合适的框架至关重要。常见的测试工具有unittest(集成度高,适合基础测试)、pytest(功能强大,支持插件扩展和高级功能)、requests-mock(用于HTTP请求模拟和断言)、rest-assured(针对RESTful API的简洁测试)以及allure-pytest(生成美观的测试报告)。选择时要考虑项目需求、团队熟悉度和社区支持。确保遵循良好测试实践,编写清晰、全面的测试用例。
301 2