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


相关文章
|
2月前
|
人工智能 运维 监控
Agent从一问一答到自主执行面临哪些挑战?
AI任务调度平台(MSE)解决开源Agent定时任务的五大痛点:高可用性、统一运维、细粒度权限、全链路可观测、弹性降本。支持OpenClaw/Hermes/百炼/Dify等多框架,提供任务批处理、自进化、会话管理等企业级能力,现开放免费公测。
Agent从一问一答到自主执行面临哪些挑战?
|
2月前
|
弹性计算 人工智能 测试技术
阿里云服务器0元试用攻略:新手必看!从申请到性能测试流程参考
阿里云服务器ECS免费试用机制为开发者、初创企业及个人用户提供了零风险的上云验证路径。个人用户可享300元、企业用户660元试用额度,覆盖多规格多地域配置。用户通过"先试用后购买"模式,可零成本验证业务构想、深度评估产品匹配度,并熟悉云端运维操作。试用需完成实名认证且为新用户,每个账号限一次。此外,阿里云还提供解决方案免费试用(含POC验证)及超30款AI产品、7000万tokens免费体验,搭配99元起的长期优惠套餐,构建了完善的"先试后买"上云生态。
|
传感器 自然语言处理 资源调度
AR 交互与自动感应技术的博物馆智慧导览系统功能解析
本系统结合AR图像识别、自动感应与多语言资源管理,实现虚拟内容与文物精准叠加、自动讲解与智能导航,提升博物馆导览体验智能化、互动性。
1012 1
|
6月前
|
安全 数据可视化 数据挖掘
2026年数据分析工具推荐:从基础到高级的工具选择全指南
本文全面解析10款国内外主流BI工具,涵盖瓴羊Quick BI、Power BI、Tableau等产品,从核心定位、推荐场景、技术亮点到资质认证多维度对比,重点剖析其在数据可视化、智能分析、安全管控等方面的能力,并提供选型建议与免费试用参考,助力企业高效实现数据驱动决策。
快手自动发私信脚本,快手一键群发私信软件,无限制私信营销工具
这是一款针对快手和抖音平台的私信群发及自动化操作脚本,可大幅提升多账号运营效率,避免手动操作易出错的问题。
|
传感器 人工智能 自动驾驶
如何把技术创新从“无限可能”,聚焦到精选的几个?“TRIZ技术进化AI助手”不妨一试
企业创新面临诸多“坑”,尤其在技术竞争中常遇“卡脖子”问题。法思诺推出TRIZ技术进化AI助手,基于阿奇舒勒理论,集成几十条技术进化路线,从时间、空间和界面三大维度助力研发人员识别问题、分析现状并提供三条进化路线参考。该工具可有效帮助企业节约资源、规避风险,探索不同技术路径。通过测试如触觉传感器等35项我国“卡脖子”关键技术,验证其有效性。真创新,不平凡!欢迎试用与交流。
411 0
|
存储
宇宙竟是一个智能体?万物智能演化Ω理论,探索宇宙终极之迷
《从观察者到智能体:论物理学与智能科学的统一》提出“万物智能演化Ω理论”,旨在融合物理学与智能科学,构建智能体与宇宙演化的统一模型。通过定义智能体的基本能力和极端状态,探讨智能体在α引力和Ω引力作用下的演化过程,为理解宇宙提供新视角。论文链接:https://doi.org/10.20944/preprints202410.0479.v1
394 2
|
架构师 Java 数据安全/隐私保护
Fiddler安装和使用
Fiddler安装和使用
850 0
|
定位技术 项目管理
5W2H分析法:全面思考和解决问题的实用工具
5W2H分析法又叫七问分析法,创于二战中美国陆军兵器修理部。发明者用五个以W开头的英语单词和两个以H开头的英语单词进行设问,发现解决问题的线索,寻找发明思路,进行设计构思,从而搞出新的发明项目。5W2H简单、方便,易于理解,实用,富有启发意义,广泛用于企业管理和技术活动,对于决策和执行性的活动措施也非常有帮助,也有助于弥补考虑问题的疏漏。
2258 0

热门文章

最新文章