有限状态机FSM(finite state machine) 一

本文介绍了有限状态机(FSM)的概念及其在游戏逻辑中的作用,例如用于模拟学生的一天活动。FSM通过将状态分散到不同类中,降低了代码的耦合度,提高了可读性和可扩展性。通过定义状态基类和具体状态类,如吃饭、休息、打篮球和写作业,实现了状态间的转换。状态机的每个状态具有进入、执行和退出方法,简化了复杂的逻辑判断。虽然代码示例中仍存在一些硬编码条件,但整体展示了FSM如何提高代码组织和维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有限状态机FSM(finite state machine) 一

有限状态机又称有限自动状态机,它拥有有限数量的状态,每个状态代表不同的意义,每个状态可以切换到 零-多 个状态。任意时刻状态机有且只能处在一个状态。
有限状态机可以表示为一个有向图。
如下图
在这里插入图片描述

从图中可以看出一个学生包含四个状态:吃饭、休息、打篮球、写作业
每种带有箭头的连线,表示可以从当前状态切换到其他的状态,以及切换的条件

吃饭休息打篮球写作业
吃饭吃饱了
休息饿了想打球该写作业
打篮球饿了累了该写作业
写作业累了作业写完了

表格中左侧第一列为当前状态
表格中上方第一行为切换的下一个状态
表格中每行从左到右为状态切换的条件(状态A不能切换到状态A)
如下
吃饭->休息:条件 (吃饱了)
休息->吃饭:条件 (饿了)
休息->打篮球:条件 (想打球)
休息->写作业:条件(该写作业)

几个重要概念
状态(State):当前所处的状态,在当前状态下可以有不同的行为和属性
转移(Transition):状态变更,满足条件是从一个状态转移到另一个状态
动作(Action):表示在给定时刻进行的活动
事件/条件(Event、Condition):触发一个事件、当一个条件满足触发状态转移切换到另一个状态

当状态很少时,可以使用 if else 各种嵌套判断来实现逻辑但是当状态不断增加时,代码的可读性以及可拓展性将会是非常严峻的问题,并且当状态不断增加时,常常需要修改之前的各种判断条件,随着状态增加,代码复杂度将难以预测,bug率将不断上升,最终可能导致代码不可读、无法改。

那么状态机是如何实现如上图几种状态之间的逻辑
首先我们需要定义各种状态
每个状态需要三个接口

    // 进入该状态
    void OnEnter();

    // 执行该状态的行为
    void OnExecute();

    // 退出该状态
    void OnExit();

1.当切换到状态 A 时先执行 A.OnEnter 方法,说明开始执行状态A 了,可以在 OnEnter 方法里做一些初始化,
2.然后接下来每帧将会调用 A.OnExecute 方法,不断执行在状态 A下的逻辑
3.当从状态A切换到其他状态时,要先执行 A.OnExit 方法,表示退出状态A了,在这里处理一些状态 A 的收尾工作。
比如从写作业 转换到打篮球,在写作业.OnExit() 中:将作业本放入书包,铅笔收入文具盒等等

我们可以定义(interface)接口或者(abstract class)抽象类,这里我采用定义一个抽象类基类

public abstract class StateBase
{
    // 当前类型
    protected StateEnum _state;
    // 状态转换事件,要转换状态的通知
    protected Action<StateEnum> _transitionEvent;
    public StateBase()  { }

    // 进入该状态
    public abstract void OnEnter();

    // 执行该状态的行为
    public abstract void OnExecute();

    // 退出该状态
    public abstract void OnExit();

    //返回当前类型
    public StateEnum State
    {
        get { return _state; }
    }
    
    public void SetTransitionEvent(Action<StateEnum> transitionEvent)
    {
        _transitionEvent = transitionEvent;
    }
}

定义一个区分不同状态的枚举

public enum StateEnum
{
    EAT = 0,        // 吃饭

    RESET = 1,      // 休息

    BASKETBALL = 2, // 休息

    HOMEWORK = 3,   // 写作业
}

我们还需要一个状态管理类 StateMachine,需要使用的方法如下
1.保存我们所有的状态
2.转换状态的接口
3.获取当前状态的接口
4.执行当前状态的接口

public class StateMachine
{
    // 保存所有的状态
    private Dictionary<StateEnum, StateBase> _stateDic = new Dictionary<StateEnum, StateBase>();
    // 记录当前状态
    private StateBase _currentState;

    public StateMachine()
    {
        // 初始化状态、并存储
        _stateDic[StateEnum.EAT] = new StateEat();
        _stateDic[StateEnum.RESET] = new StateReset();
        _stateDic[StateEnum.BASKETBALL] = new StateBasketball();
        _stateDic[StateEnum.HOMEWORK] = new StateHomeWork();
        
        foreach(var kv in _stateDic)
        {
            // 给所有状态设置状态转换的回调方法
            kv.Value.SetTransitionEvent(TransitionState);
        }
    }

    // 获取当前状态
    public StateBase CurrentState
    {
        get { return _currentState; }
        private set { _currentState = value; }
    }

    // 状态转换方法
    public void TransitionState(StateEnum stateEnum)
    {
        // 如果当前状态不为空,先退出当前状态
        if (null != CurrentState)
        {
            CurrentState.OnExit();
        }

        // 令当前状态等于转换的新状态
        CurrentState = _stateDic[stateEnum];
        // 转换的新状态执行 进入方法
        CurrentState.OnEnter();
    }

    // 每帧执行的方法
    public void OnExecute()
    {
        if (null != CurrentState)
        {
            CurrentState.OnExecute();
        }
    }
}

分别定义各个状态

吃饭状态

public class StateEat : StateBase
{
    public StateEat()
    {
        _state = StateEnum.EAT;
    }

    public override void OnEnter()
    {
        Debug.Log("开始吃饭啦");
    }

    public override void OnExecute()
    {
        Debug.Log("吃饭中");
        // 如果吃饱了,转换到休息状态
        if(吃饱了) 
        {
            _transitionEvent(StateEnum.RESET);
        }
    }

    public override void OnExit()
    {
        Debug.Log("吃的好饱啊,不吃了");
        Debug.Log("刷碗、刷锅");
        Debug.Log("擦桌子");
        Debug.Log("打扫厨房");
    }
}

休息状态

public class StateReset : StateBase
{
    public StateReset()
    {
        _state = StateEnum.RESET;
    }

    public override void OnEnter()
    {
        Debug.Log("我要开始休息了");
    }

    public override void OnExecute()
    {
        // 如果饿了,转换到吃饭状态
        if (饿了)
        {
            _transitionEvent(StateEnum.EAT);
        }
        else if (想打球) // 如果想打球了,切换到打球状态
        {
            _transitionEvent(StateEnum.BASKETBALL);
        }
        else if (该写作业了) // 如果该写作业了,切换到写作业状态
        {
            _transitionEvent(StateEnum.HOMEWORK);
        }
        else
        {
            Debug.Log("休息中");
        }
    }

    public override void OnExit()
    {
        Debug.Log("美美的睡了一觉,好精神");
        Debug.Log("叠被子");
        Debug.Log("收拾房间");
    }
}

写作业状态

public class StateHomeWork : StateBase
{
    public StateHomeWork()
    {
        _state = StateEnum.HOMEWORK;
    }

    public override void OnEnter()
    {
        Debug.Log("开始写作业啦");
    }

    public override void OnExecute()
    {
        // 写作业累了,切换到休息状态
        if (累了)
        {
            _transitionEvent(StateEnum.RESET);
        }
        // 想打球了,切换到打球状态
        else if (想打球了)
        {
            _transitionEvent(StateEnum.BASKETBALL);
        }
        else
        {
            Debug.Log("我在写作业");
        }
    }

    public override void OnExit()
    {
        Debug.Log("停止写作业");
        Debug.Log("作业本收起来");
    }
}

打篮球状态

public class StateBasketball : StateBase
{
    public StateBasketball()
    {
        _state = StateEnum.BASKETBALL;
    }

    public override void OnEnter()
    {
        Debug.Log("开始打篮球啦,好高兴啊");
    }

    public override void OnExecute()
    {
        // 如果饿了
        if (饿了)
        {
            _transitionEvent(StateEnum.EAT);
        }
        else if (累了) //如果累了,切换到休息状态
        {
            _transitionEvent(StateEnum.RESET);
        }
        else if (该写作业了) //如果该写作业了,切换到写作业状态
        {
            _transitionEvent(StateEnum.HOMEWORK);
        }
        else
        {
            Debug.Log("打篮球");
        }
    }

    public override void OnExit()
    {
        Debug.Log("停止打篮球");
    }
}

通过上面代码我们已经能够看到有限状态机的好处了,它将我们的各个状态分散到了不同的类中,这样我们在每个状态中只需要关心自己的逻辑(高内聚),减少了不同状态之间的耦合(低耦合),以及达到某一条件时需要切换到的状态,减少了很多 if else 的判断,提高的代码的可读性和扩展性。

上边代码是有限状态机的大概框架,部分地方使用伪代码,意在说明状态机的形式和大概样子,并不能完整运行,读者应该能发现代码中少了一个重要组成部分 Player,即我们判断的字段中 想打篮球、累了、该写作业了、吃饱了 这些都应该是我们的 Player 拥有的字段,并且在各个状态的 OnExecute 需要补齐逻辑,比如 吃饭状态的 OnExecute 需要每帧不断的去改变吃饭的量,来确定 Player 是否吃饱了。

本篇中还是有一部分使用了 if else

if (饿了)
{
     _transitionEvent(StateEnum.EAT);
}

这样的硬编码条件,灵活性不高,我们可以通过一些修改,让我们的有限状态机变得灵活可配置,本片就不再讲解,后续将补上,如有讲解不对的地方,请留言,谢谢

### 关于面包板电源模块 MB102 的 USB 供电规格及兼容性 #### 1. **MB102 基本功能** 面包板电源模块 MB102 是种常见的实验工具,主要用于为基于面包板的小型电子项目提供稳定的电压输出。它通常具有两路独立的稳压输出:路为 5V 和另路可调电压(般范围为 3V 至 12V)。这种设计使得它可以满足多种芯片和传感器的不同工作电压需求。 #### 2. **USB 供电方式** MB102 支持通过 USB 接口供电,输入电压通常是标准的 5V DC[^1]。由于其内部集成了 LM7805 稳压器以及可调节电位器控制的直流-直流变换电路,因此即使输入来自电脑或其他低功率 USB 设备,也能稳定地向负载供应电力。不过需要注意的是,如果项目的功耗较高,则可能超出某些 USB 端口的最大电流能力(般是 500mA),从而引起不稳定现象或者保护机制启动断开连接的情况发生。 #### 3. **兼容性分析** 该型号广泛适用于各种微控制器单元 (MCU),特别是那些像 Wemos D1 R32 这样可以通过杜邦线轻松接入并共享相同逻辑级别的系统[^2]。另外,在提到 Arduino Uno 板时也表明了良好的互操作性,因为两者均采用相似的标准接口定义与电气特性参数设置[^4]: - 对于需要 3.3V 工作环境下的组件来说,只需调整好对应跳线帽位置即可实现精准匹配; - 当涉及到更多外围扩展应用场合下,例如带有多重模拟信号采集任务的情形里,利用 MB102 提供干净无干扰的基础能源供给就显得尤为重要了[^3]。 综上所述,对于打算构建以单片机为核心的原型验证平台而言,选用具备良好声誉记录且易于获取配件支持服务链路上下游资源丰富的品牌产品——如这里讨论过的这款特定类型的配电装置不失为明智之举之。 ```python # 示例 Python 代码展示如何检测硬件状态 import machine pin = machine.Pin(2, machine.Pin.IN) if pin.value() == 1: print("Power supply is stable.") else: print("Check your connections and power source.") ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值