简单有限状态机代码实现

2023-11-06

学习链接:Unity有限状态机编写 - 木子微冷 - 博客园

实现思路:

   1.创建状态基类BasState,所有具体状态类继承该类。基类有3个方法:进入状态、状态中、离开状态;

   2.创建管理类来管理状态机(StateMgr),并实现状态机的各个方法:状态转换、设置默认状态等;

   3.创建具体状态类(继承BasState),并实现其3个方法(virtual方法如果没有重载override会默认调用父一级的该方法);

   4.初始化管理器(StateMgr)。

注意事项:

   1.代码实现为C#语言的具体实现,仅供参考,已在Unity上测试运行成功,(修改StateMgr继承的Singleton为MonoBehaviour并运行Init()方法后即可)请放心食用;

   2.“StateMgr : Singleton<StateMgr>”管理器类中继承的Singleton是我框架设计的父类,仅仅为了单例实现;

   3.StateMgr我为了能一个管理器管理多个不同状态机而改进了下:多了一个字典保存状态机。所以会比学习链接稍微复杂点;

   4.为了增强鲁棒性,代码中存在比较多的数据检测,如果移除可以增加可读性。在第一次使用时(Main方法),将StateMgr中的Init()方法调用即可;

   5.(可选)string改为Enum枚举类型,对应字典的string部分改为自己设计的Enum,方便后续开发(代码提示)也易于维护(测试开发时是可行的)。存入数据库中需要创建对应表的类,会更复杂些,不过扩展性会更强,效果会比改为枚举类型会更佳,需要增删改状态时可以直接在外部表中修改,并在对应存储数据表的类修改下代码即可。

不足:

   1.状态、状态机没有优化好,暂时是直接指定字符串来存储。不方便开发,后续考虑改为枚举类型或者存入数据库中保存;

   2.性能可能不是特别好,有待优化。

代码:

BaseState.cs

基类

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

public class BaseState
{
    public StateMgr stateMgr;

    public virtual void OnEnterState() { }
    public virtual BaseState OnStayState() { return null; }
    public virtual void OnExitState() { }
}

CtrlState.cs   

具体行为类

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


public class CtrlState : BaseState
{
    public StateMgr stateMgr;

    public override void OnEnterState()
    {
        Debug.Log("Enter CtrlState");
    }
    public override BaseState OnStayState()
    {
        Debug.Log("Stay CtrlState");
        return this;
    }
    public override void OnExitState()
    {
        Debug.Log("Exit CtrlState");
    }

}

StateMgr.cs

管理器类

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

/// <summary>
/// 状态机管理器
/// 说明:
/// </summary>
public class StateMgr : Singleton<StateMgr>
{
    //管理所有状态机
    private Dictionary<string, Dictionary<string, BaseState>> stateDic;
    //一个字典保存各个状态机,需要一个枚举
    //...

    #region 状态机字典集
    //当前焦点状态机
    private Dictionary<string, BaseState> stateFocusDic;

    #endregion

    //"当前"状态字典
    private Dictionary<string, BaseState> currentState;
    //"前一个"状态字典
    private Dictionary<string, BaseState> lastState;

    public StateMgr()
    {
        stateDic = new Dictionary<string, Dictionary<string, BaseState>>();
        stateFocusDic = new Dictionary<string, BaseState>();
        currentState = new Dictionary<string, BaseState>();
        lastState = new Dictionary<string, BaseState>();
    }

    /// <summary>
    /// 初始化状态机
    /// </summary>
    public void Init()
    {
        //初始注册当前焦点状态机
        RegisterStateMachine("stateFocusDic",stateFocusDic);
        //初始焦点状态机数据----设计为更便捷的方法?text读取自动初始化?枚举?
        RegisterState("stateFocusDic", "none", new NoneState());
        RegisterState("stateFocusDic", "ctrl", new CtrlState());
        RegisterState("stateFocusDic", "interactive", new InteractiveState());
        //设置默认状态
        SetDefaultState("stateFocusDic", "ctrl");
        //测试
        GotoState("stateFocusDic", "interactive");
    }

    public void Clear()
    {

    }

    /// <summary>
    /// 注册对应状态机字典,同时给"当前"与"之前"状态字典初始化Key
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <param name="newDic">目标状态机字典</param>
    public void RegisterStateMachine(string stateMachineName,Dictionary<string,BaseState> newDic)
    {
        //注册状态机
        stateDic.Add(stateMachineName,newDic);
        //初始化"当前"与"之前状态"字典
        currentState.Add(stateMachineName, null);
        lastState.Add(stateMachineName, null);
    }

    /// <summary>
    /// 注册状态机的状态
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <param name="stateName">目标状态名</param>
    /// <param name="state">目标状态类</param>
    public void RegisterState(string stateMachineName,string stateName,BaseState state)
    {
        if (!stateDic.ContainsKey(stateMachineName))
        {
            Debug.LogError("状态机列表无此状态机" + stateMachineName);
            return;
        }
        //指定状态类的指向对象为此Mgr类
        state.stateMgr = this;
        //为目标状态机添加状态
        stateDic[stateMachineName].Add(stateName,state);
    }

    /// <summary>
    /// 获取目标状态机的"当前"状态
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <returns></returns>
    public BaseState GetCurrentState(string stateMachineName)
    {
        if (!currentState.ContainsKey(stateMachineName))
        {
            Debug.LogError("目标状态列表无此状态" + stateMachineName);
            return null;
        }
        return currentState[stateMachineName];
    }

    /// <summary>
    /// 获取目标状态机之前的状态
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <returns></returns>
    public BaseState GetLastState(string stateMachineName)
    {
        if (!lastState.ContainsKey(stateMachineName))
        {
            Debug.LogError("目标状态列表无此状态" + stateMachineName);
            return null;
        }
        return lastState[stateMachineName];
    }

    /// <summary>
    /// 设置状态机默认状态
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <param name="stateName">目标状态名</param>
    public void SetDefaultState(string stateMachineName, string stateName)
    {
        if (!stateDic.ContainsKey(stateMachineName))
        {
            Debug.LogError("状态机列表无此状态机" + stateMachineName);
            return;
        }
        if (!currentState.ContainsKey(stateMachineName))
        {
            Debug.LogError("当前状态机列表无此状态机" + stateMachineName);
            return;
        }
        if (!stateDic[stateMachineName].ContainsKey(stateName))
        {
            Debug.LogError("目标状态机状态列表无此状态" + stateName);
            return;
        }
        //将目标状态机的当前状态设置为stateName
        currentState[stateMachineName] = stateDic[stateMachineName][stateName];
    }

    /// <summary>
    /// 改变状态机的当前状态
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <param name="stateName">目标状态名</param>
    public void ChangeState(string stateMachineName, string stateName)
    {
        if (!stateDic.ContainsKey(stateMachineName))
        {
            Debug.LogError("状态机列表无此状态机" + stateMachineName);
            return;
        }
        if (!currentState.ContainsKey(stateMachineName))
        {
            Debug.LogError("当前状态机列表无此状态机" + stateMachineName);
            return;
        }
        if (!stateDic[stateMachineName].ContainsKey(stateName))
        {
            Debug.LogError("目标状态机状态列表无此状态" + stateName);
            if (!lastState.ContainsKey(stateMachineName))
            {
                Debug.LogError("之前状态机列表无此状态机" + stateMachineName);
                return;
            }
            //改变失败,设置状态为"之前"状态
            currentState[stateMachineName] = lastState[stateMachineName];
        }
        //改变成功,设置状态
        currentState[stateMachineName] = stateDic[stateMachineName][stateName];
    }

    /// <summary>
    /// 目标状态机切换到目标状态
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <param name="stateName">目标状态名</param>
    public void GotoState(string stateMachineName, string stateName)
    {
        if (!stateDic.ContainsKey(stateMachineName))
        {
            Debug.LogError("状态机列表无此状态机" + stateMachineName);
            return;
        }
        if (!stateDic[stateMachineName].ContainsKey(stateName))
        {
            Debug.LogError("目标状态机状态列表无此状态" + stateName);
            return;
        }

        //如果"之前"状态与"当前"状态不同,则更新记录
        if (currentState[stateMachineName] != lastState[stateMachineName])
        {
            lastState[stateMachineName] = currentState[stateMachineName];
        }
        //执行"之前"状态的"状态后"方法(currentState此时未更新)
        lastState[stateMachineName].OnExitState();
        //改变目标状态机当前状态
        ChangeState(stateMachineName,stateName);
        //执行当前状态的"状态前",然后执行"状态中"方法
        currentState[stateMachineName].OnEnterState();
        currentState[stateMachineName].OnStayState();
    }//GotoState_end

}//class_end

StateMgr.cs

(枚举升级版本)

多了curState与oldState,用于获取enum State,方便在其他类中的比较判断。

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

/// <summary>
/// 状态机管理器
/// 说明:
/// 新添状态机:
///             1.新建一个状态机字典集,并在enum Machine中添加
///             2.StateMgr进行new
///             3.Init()进行状态添加,并设置默认状态
/// 新添状态:
///             1.在enum State中添加枚举
///             2.Init()中目标状态机进行状态添加
/// </summary>
public class StateMgr : Singleton<StateMgr>
{
    //管理所有状态机
    private Dictionary<Machine, Dictionary<State, BaseState>> stateDic;

    #region 状态机字典集
    //当前焦点状态机
    private Dictionary<State, BaseState> stateFocusDic;

    #endregion

    //"当前"状态字典
    private Dictionary<Machine, BaseState> currentState;
    //"前一个"状态字典
    private Dictionary<Machine, BaseState> lastState;
    //"当前"状态字典
    private Dictionary<Machine, State> curState;
    //"前一个"状态字典
    private Dictionary<Machine, State> oldState;

    /// <summary>
    /// 状态机
    /// </summary>
    public enum Machine
    {
        FocusMachine,

    }

    /// <summary>
    /// 状态
    /// </summary>
    public enum State
    {
        None,
        Ctrl,
        Interactive,
    }

    public StateMgr()
    {
        stateDic = new Dictionary<Machine, Dictionary<State, BaseState>>();
        stateFocusDic = new Dictionary<State, BaseState>();
        currentState = new Dictionary<Machine, BaseState>();
        lastState = new Dictionary<Machine, BaseState>();
        curState = new Dictionary<Machine, State>();
        oldState = new Dictionary<Machine, State>();
    }

    /// <summary>
    /// 初始化状态机
    /// </summary>
    public void Init()
    {
        //初始注册当前焦点状态机
        RegisterStateMachine(Machine.FocusMachine,stateFocusDic);
        //初始焦点状态机数据----设计为更便捷的方法?text读取自动初始化?
        RegisterState(Machine.FocusMachine, State.None, new NoneState());
        RegisterState(Machine.FocusMachine, State.Ctrl, new CtrlState());
        RegisterState(Machine.FocusMachine, State.Interactive, new InteractiveState());

        //设置默认状态
        SetDefaultState(Machine.FocusMachine, State.None);

    }

    public void Clear()
    {

    }

    /// <summary>
    /// 注册对应状态机字典,同时给"当前"与"之前"状态字典初始化Key
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <param name="newDic">目标状态机字典</param>
    public void RegisterStateMachine(Machine stateMachineName,Dictionary<State, BaseState> newDic)
    {
        //注册状态机
        stateDic.Add(stateMachineName,newDic);
        //初始化"当前"与"之前状态"字典
        currentState.Add(stateMachineName, null);
        lastState.Add(stateMachineName, null);
        curState.Add(stateMachineName, State.None);
        oldState.Add(stateMachineName, State.None);
    }

    /// <summary>
    /// 注册状态机的状态
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <param name="stateName">目标状态名</param>
    /// <param name="state">目标状态类</param>
    public void RegisterState(Machine stateMachineName,State stateName,BaseState state)
    {
        if (!stateDic.ContainsKey(stateMachineName))
        {
            Debug.LogError("状态机列表无此状态机" + stateMachineName);
            return;
        }
        //指定状态类的指向对象为此Mgr类
        state.stateMgr = this;
        //为目标状态机添加状态
        stateDic[stateMachineName].Add(stateName,state);
    }

    /// <summary>
    /// 获取目标状态机的"当前"状态BaseState
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <returns></returns>
    public BaseState GetCurrentBaseState(Machine stateMachineName)
    {
        if (!currentState.ContainsKey(stateMachineName))
        {
            Debug.LogError("目标状态列表无此状态" + stateMachineName);
            return null;
        }
        return currentState[stateMachineName];
    }

    /// <summary>
    /// 获取目标状态机的"当前"状态
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <returns></returns>
    public State GetCurrentState(Machine stateMachineName) {
        if (!curState.ContainsKey(stateMachineName)) {
            Debug.LogError("目标状态列表无此状态" + stateMachineName);
            return State.None;
        }
        return curState[stateMachineName];
    }

    /// <summary>
    /// 获取目标状态机之前的状态
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <returns></returns>
    public State GetLastState(Machine stateMachineName) {
        if (!oldState.ContainsKey(stateMachineName)) {
            Debug.LogError("目标状态列表无此状态" + stateMachineName);
            return State.None;
        }
        return oldState[stateMachineName];
    }

    /// <summary>
    /// 获取目标状态机之前的状态BaseState
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <returns></returns>
    public BaseState GetLastBaseState(Machine stateMachineName)
    {
        if (!lastState.ContainsKey(stateMachineName))
        {
            Debug.LogError("目标状态列表无此状态" + stateMachineName);
            return null;
        }
        return lastState[stateMachineName];
    }

    /// <summary>
    /// 设置状态机默认状态
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <param name="stateName">目标状态名</param>
    public void SetDefaultState(Machine stateMachineName, State stateName)
    {
        if (!stateDic.ContainsKey(stateMachineName))
        {
            Debug.LogError("状态机列表无此状态机" + stateMachineName);
            return;
        }
        if (!currentState.ContainsKey(stateMachineName))
        {
            Debug.LogError("当前状态机列表无此状态机" + stateMachineName);
            return;
        }
        if (!stateDic[stateMachineName].ContainsKey(stateName))
        {
            Debug.LogError("目标状态机状态列表无此状态" + stateName);
            return;
        }
        //将目标状态机的当前状态设置为stateName
        currentState[stateMachineName] = stateDic[stateMachineName][stateName];
        curState[stateMachineName] = stateName;
    }

    /// <summary>
    /// 改变状态机的当前状态
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <param name="stateName">目标状态名</param>
    public void ChangeState(Machine stateMachineName, State stateName)
    {
        if (!stateDic.ContainsKey(stateMachineName))
        {
            Debug.LogError("状态机列表无此状态机" + stateMachineName);
            return;
        }
        if (!currentState.ContainsKey(stateMachineName))
        {
            Debug.LogError("当前状态机列表无此状态机" + stateMachineName);
            return;
        }
        if (!stateDic[stateMachineName].ContainsKey(stateName))
        {
            Debug.LogError("目标状态机状态列表无此状态" + stateName);
            if (!lastState.ContainsKey(stateMachineName))
            {
                Debug.LogError("之前状态机列表无此状态机" + stateMachineName);
                return;
            }
            //改变失败,设置状态为"之前"状态
            currentState[stateMachineName] = lastState[stateMachineName];
            curState[stateMachineName] = oldState[stateMachineName];
        }
        //改变成功,设置状态
        currentState[stateMachineName] = stateDic[stateMachineName][stateName];
        curState[stateMachineName] = stateName;
    }

    /// <summary>
    /// 目标状态机切换到目标状态
    /// </summary>
    /// <param name="stateMachineName">目标状态机名字</param>
    /// <param name="stateName">目标状态名</param>
    public void GotoState(Machine stateMachineName, State stateName)
    {
        if (!stateDic.ContainsKey(stateMachineName))
        {
            Debug.LogError("状态机列表无此状态机" + stateMachineName);
            return;
        }
        if (!stateDic[stateMachineName].ContainsKey(stateName))
        {
            Debug.LogError("目标状态机状态列表无此状态" + stateName);
            return;
        }

        //如果"之前"状态与"当前"状态不同,则更新记录
        if (currentState[stateMachineName] != lastState[stateMachineName])
        {
            lastState[stateMachineName] = currentState[stateMachineName];
            oldState[stateMachineName] = curState[stateMachineName];
        }
        //执行"之前"状态的"状态后"方法(currentState此时未更新)
        lastState[stateMachineName].OnExitState();
        //改变目标状态机当前状态
        ChangeState(stateMachineName,stateName);
        //执行当前状态的"状态前",然后执行"状态中"方法
        currentState[stateMachineName].OnEnterState();
        currentState[stateMachineName].OnStayState();
    }//GotoState_end

}//class_end

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

简单有限状态机代码实现 的相关文章

  • 千兆路由器怎么设置网速最快_如何设置路由器使网速最快

    有些时候我们自己家路由器的网速达不到办理宽带时的要求 通讯商又说那边没有问题 改怎么办呢 其实我们可以设置一下路由器进行加快网速 下面是学习啦小编整理的设置路由器网速最快的方法 供您参考 设置路由器网速最快的方法 在浏览器里输入路由器IP地

随机推荐

  • Java 无限级递归树形菜单

    文章目录 建立数据模型SysDept 创建数据库表 建立树形结构工具类 测试 递归生成一个树状json 需要提供一个数据模型Sys Dept和建立树形结构的TreeNodeUtils 首先获取所有节点的父节点 递归创建父节点下的子节点树 并
  • css全站变灰

    css属性全网站变灰 filter grayscale 使用可以调整元素的灰度值 html filter grayscale 0 95 webkit filter grayscale 0 95 moz filter grayscale 0
  • 以组织的名义

    以组织的名义 原创 你兽爷 兽楼处 今年4月17日 是医生谭秦东出狱两周年的日子 那天他发了一条朋友圈 自由两周年纪念 感谢万千朋友的帮助 两年多前 憨厚的谭医生写文章质疑鸿茅药酒的安全性 内蒙古凉城警方千里迢迢从广东把他请回内蒙喝茶 看守
  • flask导入一个模板加CSS js

    from flask import Flask redirect url for render template app Flask name app route def index return render template login
  • ant-vue table换页以后选中的数据无法记住前一页已勾选的数据

    ant vue table换页以后选中的数据无法记住前一页已勾选的数据 解决方法 使用组件自带的onSelect事件和onSelectAll事件来记录 HTML Markup
  • TiDB quick start

    文章目录 使用 TiUP 部署 TiDB 集群 TiUP playground 部署本地测试环境 错误 TiUP cluster 单机模拟生成环境部署 访问集群 报错 附录 topo yaml 网卡配置模板 https docs pingc
  • Springboot中使用线程拿到当前账号信息

    Springboot中使用线程的思想拿到当前账号信息 1 简介 我查看源码发现springboot中有定义好了的线程还有线程池 下面这个类就是关于请求的线程 我们通过线程拿到请求对象 这个线程在用户有效连接服务器就在 所以用户在线的状态下
  • Ubuntu多CUDA版本安装及切换

    可同时安装多个CUDA版本在 usr local路径下 使用软连接实现版本的切换 一 查看已安装的CUDA版本 所有已安装的CUDA版本默认保存在 usr local路径下 cd到该路径下通过ls命令查看 文件夹中cuda 11 0表示当前
  • 谈谈 <script> 标签以及其加载顺序问题,包含 defer & async

  • C语言数组练习

    1 打印杨辉三角 include
  • 底层注解-@Configuration详解 & @Configuration(proxyBeanMethods = true/false)

    一 Configuration详解 Configuration注解的作用 声明一个类为配置类 用于定义配置类 可替换xml配置文件注册bean对象 被注解的类内部包含有一个或多个被 Bean注解的方法 这些方法将会被AnnotationCo
  • 微信公众号网页开发之拍照、上传本地图片

    微信网页开发 JS SDK说明文档 https developers weixin qq com doc offiaccount OA Web Apps JS SDK html 0 绑定域名 登录微信公众平台进入 公众号设置 gt 功能设置
  • 前端blob下载文件

    在一个项目中 需要下载excel pdf word zip等数据模板 而后端返回的是一个文件流 前端就需要使用blob来下载 1 首先要在接口中添加 responseType blob 模板下载 export function downLo
  • wazuh常用内容、防御sql注入

    目录 安装wazuh 常用内容 检测sql注入 主动响应 安装wazuh 本地测试的话建议用ova文件 直接导入虚拟机就能用了 官网 Virtual Machine OVA Installation alternatives 常用内容 目录
  • (字典树)acwing835. Trie字符串统计 算法基础班第二讲

    题目 维护一个字符串集合 支持两种操作 I x 向集合中插入一个字符串 x Q x 询问一个字符串在集合中出现了多少次 共有 N 个操作 输入的字符串总长度不超过 105 字符串仅包含小写英文字母 输入格式 第一行包含整数 N 表示操作数
  • 【目标检测】20、ATSS: bridging the gap between anchor-based and anchor-free detection via ATSS

    文章目录 一 背景和动机 二 方法 2 1 分析不同 2 2 方法 2 3 嵌入 FCOS 三 效果 四 代码 本文贡献点 指出了 anchor based 和 anchor free 方法性能差别的决定性因素 如何定义正样本和负样本 提出
  • WPF编程,Live Charts使用说明(4)——主题

    可以为应用程序中的任何图表设置默认样式 必须根据需要选择颜色 大小和笔触 色彩 这组颜色将是系列中的默认颜色 当系列数大于主题中的颜色数时 这些颜色将重复 材质 基于Google的材质设计 基于MoedernUi设计的Metro 蓝色蓝调
  • 打印沙漏 C语言

    题目 本题要求你写个程序把给定的符号打印成沙漏的形状 例如给定17个 要求按下列格式打印 所谓 沙漏形状 是指每行输出奇数个符号 各行符号中心对齐 相邻两行符号数差2 符号数先从大到小顺序递减到1 再从小到大顺序递增 首尾符号数相等 给定任
  • android Intent常用标识

    Intent常用标识 FLAG ACTIVITY BROUGHT TO FRONT 这个标志一般不是由程序代码设置的 如在launchMode中设置singleTask模式时系统帮你设定 FLAG ACTIVITY CLEAR TOP 如果
  • 简单有限状态机代码实现

    学习链接 Unity有限状态机编写 木子微冷 博客园 实现思路 1 创建状态基类BasState 所有具体状态类继承该类 基类有3个方法 进入状态 状态中 离开状态 2 创建管理类来管理状态机 StateMgr 并实现状态机的各个方法 状态