设计模式 - 状态机编程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语言实现,此处只展示可运行代码作为示例,以免代码太多导致部分读者昏迷。。。

代码讲解

状态枚举和表数据

-------------------战斗流程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,其余两个并未实现,但用法相同,读者可以自行书写,欢迎留言讨论。

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
有限状态机(Finite State Machine,简称FSM)是一种描述系统行为的数学模型,可以用来描述离散事件系统的状态演变。下面是关于FSM设计的一些建议: 1. 定义清晰的状态:在设计FSM时,需要明确各个状态的含义和转换条件,确保状态的定义清晰且不重叠。每个状态应该唯一表示一个系统或对象的具体情况,以便于理解和再现。 2. 考虑全面的事件:FSM的转换依赖于事件的发生,因此需要考虑到系统可能遇到的所有可能事件。对系统可能的输入进行分析,以确保设计的FSM可以适应各种情况的变化。 3. 确定状态转换条件:每个状态之间的转换需要定义明确的条件,这些条件通常与特定的输入事件相关。在设计过程中,需要详细考虑这些条件,以确保状态之间的转换符合系统的要求。 4. 简化FSM的设计:尽可能地简化FSM的设计,避免过度复杂化。可以通过减少不必要的状态和转换来简化FSM,提高系统的可读性和可维护性。 5. 考虑FSM的扩展性:在设计FSM时,应该考虑到系统可能的扩展需求。确保FSM的设计具有良好的扩展性和灵活性,以便未来可以方便地进行修改和扩展。 6. 进行充分的测试:设计完FSM后,进行充分的测试以验证其正确性。通过提供各种输入和事件的组合,确保FSM能够按照设计预期的方式行为,并正确处理各种情况。 以上是关于FSM设计的一些建议,通过遵循这些建议,可以设计出高效、可靠的FSM,有效地描述系统的行为和状态变化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值