赞
踩
有限状态机(finite state machine)简称FSM,表示有限个状态及在这些状态之间的转移和动作等行为的数学模型,FSM是一种逻辑单元内部的一种高效编程方法。使得程序逻辑清晰易懂。
用处:各种通信协议发送方和接受方传递数据对消息处理,游戏AI等都有应用场景。
主要分为两种实现方法:
这是看到问题后最直观的解决办法。
这种方法实现的状态机,在系统较小(状态少)时,简单易懂,条理清晰,但在系统复杂(状态多)时,则有着难以扩展和维护的缺点。这里不做代码分析了。
映射分为 方法映射 和 表映射。
程序设计思路大致如下:
方法映射即状态满足转换条件后,返回某一特定方法,也称为函数指针。
将每个状态写成类,在不同的满足条件下返回不同的函数指针,也执行不同的方法。
可以参考https://blog.csdn.net/qq_22337119/article/details/103310353 糖果机的案例。
而方法映射一般用于这种糖果机这种类单机式的设计。
更准确地说,是表中数据映射,在每个状态中都使用一张转换表来表示映射关系,转换表的索引使用输入字符来表示。此外,由于通过转换表就可以描述不同状态之间的变化,那么就没有必要将每种状态定义为一个类了,即不需要多余的继承和虚函数了,仅使用一个State即可。(也符合少用继承,多用组合的原则)
在项目中,更多的是用此方法,如游戏中的 挑战副本流程, 战斗过程, 角色行为等。
(引自https://www.cnblogs.com/chencheng/archive/2012/06/28/2564336.html)
对于状态较多的状态机,通常的设计会维护一个庞大的二维矩阵,所有状态耦合在一起,这往往导致维护困难,由于可能存在许多公共的特性,也会导致许多状态具有相同的处理函数。针对这些问题我们可以通过设计分层状态机来解决,主要的思想就是根据不同的功能模块设计出多个状态机,各个状态机分布在不同的层次上。上层状态机调用下层状态机时,上层状态机入栈,下层状态机变为当前处理状态机。通常我们使用堆栈来保存当前状态机的上层状态机信息。
类似上图所示,可以进行多个状态机的嵌套。
下面介绍 副本流程状态机battleSM,战斗状态机fightSM,角色状态机roleSM 三层状态机的使用。
lua语言实现,此处只展示可运行代码作为示例,以免代码太多导致部分读者昏迷。。。
- -------------------战斗流程fsm---------------------
-
- -- 战斗状态枚举
- TABLE_STATE_BATTLE = {
- STATE_INIT = 1, -- 初始状态
- STATE_APPEAR = 2, -- 出场状态
- STATE_DIALOG = 3, -- 剧情状态 跑步遇敌+对话
- STATE_FIGHT_RULE = 4, -- 战斗开始之前说明
- STATE_FIGHT = 5, -- 战斗状态
- STATE_SETTLE = 6, -- 战斗结算状态
- STATE_END = 7, -- 结束退出
- }
-
- -- 战斗状态对应执行函数枚举 与战斗状态枚举TABLE_STATE_BATTLE索引一一对应
- STATE_FUNC_BATTLE = {
- "ProcFunc_Init",
- "ProcFunc_Appear",
- "ProcFunc_Dialog",
- "ProcFunc_FightRule",
- "ProcFunc_Fight",
- "ProcFunc_Settle",
- "ProcFunc_End",
- }
-
- -- 状态转换表 简单的顺序执行
- STATE_TRANS_TABLE = {
- Chapter1_1 = {
- battleSeq = {1, 2, 3, 4, 5, 6, 7}, -- TABLE_STATE_BATTLE表
- dialog = {10001}, -- 对话表
- },
- Chapter1_2 = {
- battleSeq = {1, 2, 3, 4, 5, 3, 5, 6, 7},
- dialog = {10002, 10003},
- },
- }
class方法为lua实现 c++类的一种实现,具体参照lua元表实现面向对象。
- BattleStateMachine = class("BattleStateMachine", nil)
- -- 创建 Battle状态机 - 战斗流程
- function BattleStateMachine:Ctor(stateTbl)
- -- body
- print("-----创建状态机-----\n")
- self.curState = TABLE_STATE_BATTLE.STATE_INIT
- -- 数据有修改 深拷贝
- self.stateSeq = table.deepcopy(stateTbl.battleSeq)
- self.dialogSeq = table.deepcopy(stateTbl.dialog)
-
- self.bFightEnd = false -- Fighting是否结束
- self.bFightPause = false -- Fighting是否暂停
- self.bFightWin = true -- Fighting是否暂停
- end
-
- -- 执行状态机
- function BattleStateMachine:Execute()
- -- body
- print("-----状态机开始运行-----\n")
- while self.curState do
- self[STATE_FUNC_BATTLE[self.curState]](self) -- 相当于.调用
- self:ChangeState()
- end
- print("-----状态机结束运行-----")
- end
-
- -- 初始状态
- function BattleStateMachine:ProcFunc_Init()
- -- body
- -- 加载数据 角色信息,地图,spine等
- -- 加载完毕
-
- print("初始\n-----加载数据 ... 完毕-----\n")
- end
-
- -- 出场状态
- function BattleStateMachine:ProcFunc_Appear()
- -- body
- -- 出场动画,round提示等
- -- 播放完毕
-
- -- RoleSM = RoleStateMachine.new()
- -- RoleSM:ProcFunc_Appear()
- print("出场\n-----动画播放 ... 完毕-----\n")
- end
-
- -- 剧情状态 跑步遇敌+对话
- function BattleStateMachine:ProcFunc_Dialog()
- -- body
- -- 跑步遇敌、对话
- -- 对话完毕
- if next(self.dialogSeq) then
- -- dialog
- end
- print("剧情\n-----对话".. self.dialogSeq[1] .. " ... 完毕-----\n")
- self:ChangeDialog()
- end
-
- -- 战斗开始之前说明
- function BattleStateMachine:ProcFunc_FightRule()
- -- body
- -- 说明
- -- 说明完毕 时间制or玩家点击确定
- print("说明\n-----说明 ... 完毕-----\n")
- end
-
- -- 战斗状态
- function BattleStateMachine:ProcFunc_Fight()
- -- body
- -- 创建 fighting状态机
- -- 战斗完毕 根据:分胜负或时间制
-
- -- self.fightSM = FightStateMachine.new()
- local update_fighting = function()
- while not self.bFightEnd do
- -- 战斗未结束
- if self.bFightPause then
- -- nothing
- else
- -- 启动 fighting状态机 类似于 battle状态机 顺序循环执行
-
- -- 更新战斗时间
- -- 检测战斗结果 有结果跳出
- -- 更新角色信息 死亡、移除
- -- 更新角色速度:出手顺序
- -- 更新角色对象 状态机 RoleSM
- -- RoleSM:Execute()
- -- 处理特效碰撞
- end
- end
- end
- -- self:setTimmer(update_fighting, 0.3)
- print("战斗\n-----战斗中 ... 结束-----\n")
- end
-
- -- 战斗结算状态
- function BattleStateMachine:ProcFunc_Settle()
- -- body
- -- 战斗结算界面
- -- 时间制or玩家点击确定
- print("结算\n-----结算 ... 完毕-----\n")
- end
-
- -- 结束退出
- function BattleStateMachine:ProcFunc_End()
- -- body
- -- 释放资源 releaseData
- -- 退出
- print("结束\n-----释放 ... 完毕-----\n")
- end
-
- -- 处理 转换状态
- function BattleStateMachine:ChangeState()
- -- body
- self.preState = self.curState
- table.remove(self.stateSeq, 1)
- self.curState = self.stateSeq[1]
- end
-
- -- 处理 对话
- function BattleStateMachine:ChangeDialog()
- -- body
- table.remove(self.dialogSeq, 1)
- end
-
- local battleSM = BattleStateMachine.new(STATE_TRANS_TABLE.Chapter1_2)
- battleSM:Execute()
- -----创建状态机-----
-
- -----状态机开始运行-----
-
- 初始
- -----加载数据 ... 完毕-----
-
- 出场
- -----动画播放 ... 完毕-----
-
- 剧情
- -----对话10002 ... 完毕-----
-
- 说明
- -----说明 ... 完毕-----
-
- 战斗
- -----战斗中 ... 结束-----
-
- 剧情
- -----对话10003 ... 完毕-----
-
- 战斗
- -----战斗中 ... 结束-----
-
- 结算
- -----结算 ... 完毕-----
-
- 结束
- -----释放 ... 完毕-----
-
- -----状态机结束运行-----
可读性 逻辑清晰易懂 - 易于他人维护
实用性 使用便捷 - 调用方便
强壮性 维护方便 - 增加、修改容易
使代码更加 高内聚 低耦合
本篇只实现了副本流程状态机battleSM,其余两个并未实现,但用法相同,读者可以自行书写,欢迎留言讨论。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。