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

您所在的位置:网站首页 停止打球英语 有限状态机FSM(finite state machine) 一

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

2024-07-14 15:54| 来源: 网络整理| 查看: 265

有限状态机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 _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 transitionEvent) { _transitionEvent = transitionEvent; } }

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

public enum StateEnum { EAT = 0, // 吃饭 RESET = 1, // 休息 BASKETBALL = 2, // 休息 HOMEWORK = 3, // 写作业 }

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

public class StateMachine { // 保存所有的状态 private Dictionary _stateDic = new Dictionary(); // 记录当前状态 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); }

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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3