当前位置:   article > 正文

设计模式 - 状态机编程fsm_状态机乱序

状态机乱序

序言介绍

有限状态机(finite state machine)简称FSM,表示有限个状态及在这些状态之间的转移和动作等行为的数学模型,FSM是一种逻辑单元内部的一种高效编程方法。使得程序逻辑清晰易懂。

用处:各种通信协议发送方和接受方传递数据对消息处理,游戏AI等都有应用场景。

主要分为两种实现方法:

一、if,switch条件语句实现

这是看到问题后最直观的解决办法。

这种方法实现的状态机,在系统较小(状态少)时,简单易懂,条理清晰,但在系统复杂(状态多)时,则有着难以扩展和维护的缺点。这里不做代码分析了。

二、映射实现

映射分为 方法映射 和 表映射。

程序设计思路大致如下:

  • 使用状态转换图描述FSM
  • 状态转换图中的结点对应不同的状态对象
  • 每个状态对象通过满足某个事件(表现为环境中的一个具体条件)转换到另一个状态上,或者保持原状态不变。

a.方法映射

方法映射即状态满足转换条件后,返回某一特定方法,也称为函数指针。

将每个状态写成类,在不同的满足条件下返回不同的函数指针,也执行不同的方法。

可以参考https://blog.csdn.net/qq_22337119/article/details/103310353 糖果机的案例。

而方法映射一般用于这种糖果机这种类单机式的设计。

b.表映射

更准确地说,是表中数据映射,在每个状态中都使用一张转换表来表示映射关系,转换表的索引使用输入字符来表示。此外,由于通过转换表就可以描述不同状态之间的变化,那么就没有必要将每种状态定义为一个类了,即不需要多余的继承和虚函数了,仅使用一个State即可。(也符合少用继承,多用组合的原则)

在项目中,更多的是用此方法,如游戏中的 挑战副本流程, 战斗过程, 角色行为等。

 

进阶 - 分层状态机

(引自https://www.cnblogs.com/chencheng/archive/2012/06/28/2564336.html

对于状态较多的状态机,通常的设计会维护一个庞大的二维矩阵,所有状态耦合在一起,这往往导致维护困难,由于可能存在许多公共的特性,也会导致许多状态具有相同的处理函数。针对这些问题我们可以通过设计分层状态机来解决,主要的思想就是根据不同的功能模块设计出多个状态机,各个状态机分布在不同的层次上。上层状态机调用下层状态机时,上层状态机入栈,下层状态机变为当前处理状态机。通常我们使用堆栈来保存当前状态机的上层状态机信息。

分层状态机图解:

类似上图所示,可以进行多个状态机的嵌套。

下面介绍  副本流程状态机battleSM,战斗状态机fightSM,角色状态机roleSM 三层状态机的使用。

lua语言实现,此处只展示可运行代码作为示例,以免代码太多导致部分读者昏迷。。。

代码讲解

状态枚举和表数据

  1. -------------------战斗流程fsm---------------------
  2. -- 战斗状态枚举
  3. TABLE_STATE_BATTLE = {
  4. STATE_INIT = 1, -- 初始状态
  5. STATE_APPEAR = 2, -- 出场状态
  6. STATE_DIALOG = 3, -- 剧情状态 跑步遇敌+对话
  7. STATE_FIGHT_RULE = 4, -- 战斗开始之前说明
  8. STATE_FIGHT = 5, -- 战斗状态
  9. STATE_SETTLE = 6, -- 战斗结算状态
  10. STATE_END = 7, -- 结束退出
  11. }
  12. -- 战斗状态对应执行函数枚举 与战斗状态枚举TABLE_STATE_BATTLE索引一一对应
  13. STATE_FUNC_BATTLE = {
  14. "ProcFunc_Init",
  15. "ProcFunc_Appear",
  16. "ProcFunc_Dialog",
  17. "ProcFunc_FightRule",
  18. "ProcFunc_Fight",
  19. "ProcFunc_Settle",
  20. "ProcFunc_End",
  21. }
  22. -- 状态转换表 简单的顺序执行
  23. STATE_TRANS_TABLE = {
  24. Chapter1_1 = {
  25. battleSeq = {1, 2, 3, 4, 5, 6, 7}, -- TABLE_STATE_BATTLE表
  26. dialog = {10001}, -- 对话表
  27. },
  28. Chapter1_2 = {
  29. battleSeq = {1, 2, 3, 4, 5, 3, 5, 6, 7},
  30. dialog = {10002, 10003},
  31. },
  32. }

状态机实现

class方法为lua实现 c++类的一种实现,具体参照lua元表实现面向对象。

  1. BattleStateMachine = class("BattleStateMachine", nil)
  2. -- 创建 Battle状态机 - 战斗流程
  3. function BattleStateMachine:Ctor(stateTbl)
  4. -- body
  5. print("-----创建状态机-----\n")
  6. self.curState = TABLE_STATE_BATTLE.STATE_INIT
  7. -- 数据有修改 深拷贝
  8. self.stateSeq = table.deepcopy(stateTbl.battleSeq)
  9. self.dialogSeq = table.deepcopy(stateTbl.dialog)
  10. self.bFightEnd = false -- Fighting是否结束
  11. self.bFightPause = false -- Fighting是否暂停
  12. self.bFightWin = true -- Fighting是否暂停
  13. end
  14. -- 执行状态机
  15. function BattleStateMachine:Execute()
  16. -- body
  17. print("-----状态机开始运行-----\n")
  18. while self.curState do
  19. self[STATE_FUNC_BATTLE[self.curState]](self) -- 相当于.调用
  20. self:ChangeState()
  21. end
  22. print("-----状态机结束运行-----")
  23. end
  24. -- 初始状态
  25. function BattleStateMachine:ProcFunc_Init()
  26. -- body
  27. -- 加载数据 角色信息,地图,spine等
  28. -- 加载完毕
  29. print("初始\n-----加载数据 ... 完毕-----\n")
  30. end
  31. -- 出场状态
  32. function BattleStateMachine:ProcFunc_Appear()
  33. -- body
  34. -- 出场动画,round提示等
  35. -- 播放完毕
  36. -- RoleSM = RoleStateMachine.new()
  37. -- RoleSM:ProcFunc_Appear()
  38. print("出场\n-----动画播放 ... 完毕-----\n")
  39. end
  40. -- 剧情状态 跑步遇敌+对话
  41. function BattleStateMachine:ProcFunc_Dialog()
  42. -- body
  43. -- 跑步遇敌、对话
  44. -- 对话完毕
  45. if next(self.dialogSeq) then
  46. -- dialog
  47. end
  48. print("剧情\n-----对话".. self.dialogSeq[1] .. " ... 完毕-----\n")
  49. self:ChangeDialog()
  50. end
  51. -- 战斗开始之前说明
  52. function BattleStateMachine:ProcFunc_FightRule()
  53. -- body
  54. -- 说明
  55. -- 说明完毕 时间制or玩家点击确定
  56. print("说明\n-----说明 ... 完毕-----\n")
  57. end
  58. -- 战斗状态
  59. function BattleStateMachine:ProcFunc_Fight()
  60. -- body
  61. -- 创建 fighting状态机
  62. -- 战斗完毕 根据:分胜负或时间制
  63. -- self.fightSM = FightStateMachine.new()
  64. local update_fighting = function()
  65. while not self.bFightEnd do
  66. -- 战斗未结束
  67. if self.bFightPause then
  68. -- nothing
  69. else
  70. -- 启动 fighting状态机 类似于 battle状态机 顺序循环执行
  71. -- 更新战斗时间
  72. -- 检测战斗结果 有结果跳出
  73. -- 更新角色信息 死亡、移除
  74. -- 更新角色速度:出手顺序
  75. -- 更新角色对象 状态机 RoleSM
  76. -- RoleSM:Execute()
  77. -- 处理特效碰撞
  78. end
  79. end
  80. end
  81. -- self:setTimmer(update_fighting, 0.3)
  82. print("战斗\n-----战斗中 ... 结束-----\n")
  83. end
  84. -- 战斗结算状态
  85. function BattleStateMachine:ProcFunc_Settle()
  86. -- body
  87. -- 战斗结算界面
  88. -- 时间制or玩家点击确定
  89. print("结算\n-----结算 ... 完毕-----\n")
  90. end
  91. -- 结束退出
  92. function BattleStateMachine:ProcFunc_End()
  93. -- body
  94. -- 释放资源 releaseData
  95. -- 退出
  96. print("结束\n-----释放 ... 完毕-----\n")
  97. end
  98. -- 处理 转换状态
  99. function BattleStateMachine:ChangeState()
  100. -- body
  101. self.preState = self.curState
  102. table.remove(self.stateSeq, 1)
  103. self.curState = self.stateSeq[1]
  104. end
  105. -- 处理 对话
  106. function BattleStateMachine:ChangeDialog()
  107. -- body
  108. table.remove(self.dialogSeq, 1)
  109. end
  110. local battleSM = BattleStateMachine.new(STATE_TRANS_TABLE.Chapter1_2)
  111. battleSM:Execute()

输出

  1. -----创建状态机-----
  2. -----状态机开始运行-----
  3. 初始
  4. -----加载数据 ... 完毕-----
  5. 出场
  6. -----动画播放 ... 完毕-----
  7. 剧情
  8. -----对话10002 ... 完毕-----
  9. 说明
  10. -----说明 ... 完毕-----
  11. 战斗
  12. -----战斗中 ... 结束-----
  13. 剧情
  14. -----对话10003 ... 完毕-----
  15. 战斗
  16. -----战斗中 ... 结束-----
  17. 结算
  18. -----结算 ... 完毕-----
  19. 结束
  20. -----释放 ... 完毕-----
  21. -----状态机结束运行-----

总结

可读性    逻辑清晰易懂 - 易于他人维护
实用性    使用便捷 - 调用方便
强壮性    维护方便 - 增加、修改容易
使代码更加  高内聚 低耦合

本篇只实现了副本流程状态机battleSM,其余两个并未实现,但用法相同,读者可以自行书写,欢迎留言讨论。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/696485
推荐阅读
相关标签
  

闽ICP备14008679号