当前位置:网站首页>FSM有限状态机
FSM有限状态机
2022-04-23 06:24:00 【0zien0】
当你要制作的功能需要通过不同的情况(条件),用不同的方式去处理问题时,一般情况下,会写大量的if-else代码来进行判断,这种写法在状态之间切换或者加入新的状态时都会比较混乱。 而使用状态机的话,每种状态的功能都相对比较独立清晰。 23种设计模式中的状态模式,就是实现有限状态机功能的一种比较好的方式。
在小时候玩过一种很简单的游戏机,叫“电子宠物”。
看到这些经典的画面不知道有没勾起大家的回忆呢。 这也是今天的主角,我打算用状态机来实现一个“养鸡”的小游戏。
游戏的数值设计,大致如下:
接下来就上游戏代码!代码是用纯lua写的。
状态基类:
-- 状态基类
local P = class("FSMState")
FSMState = P
-- 构造函数
function P:ctor(owner)
self._owner = owner
self._isPause = false
self._name = "FSMState"
end
-- 暂停状态机
function P:pause()
self._isPause = true
end
-- 继续状态机
function P:resume()
self._isPause = false
end
-- 状态描述
function P:info()
end
-- 设置当前状态是否允许被切换
function P:condition()
return true
end
-- 定时器
function P:update()
end
function P:clear()
self._owner = nil
end
状态子类1【小鸡吃东西】:
-- 状态基类
local P = class("StateEat", FSMState)
-- 构造函数
function P:ctor(owner)
self._owner = owner
self._name = "StateEat"
end
function P:info()
return "小鸡去吃饭啦"
end
function P:update(dt)
if not self._isPause then
self._owner.satiety = self._owner.satiety + math.random(30, 50)
self._owner.energy = self._owner.energy - math.random(10, 20)
self._owner.clean = self._owner.clean - math.random(5, 10)
self._owner.time = self._owner.time + 1
end
end
return P
状态子类2【小鸡睡觉】:
-- 状态基类
local P = class("StateSleep", FSMState)
-- 构造函数
function P:ctor(owner)
self._owner = owner
self._name = "StateSleep"
end
function P:info()
return "小鸡去睡觉啦"
end
function P:update(dt)
if not self._isPause then
self._owner.satiety = self._owner.satiety - math.random(20, 30)
self._owner.energy = self._owner.energy + math.random(50, 80)
self._owner.time = self._owner.time + math.random(8, 10)
self._owner.happy = 70
end
end
return P
小鸡有睡觉、吃饭、洗澡、发呆、玩,5种状态,就不一一列举了……
接下来就是比较重要的machine类了
-- 状态基类
local P = class("FSMMachine")
FSMMachine = P
P.IDLE = function(owner) return require("StateIdle"):create(owner) end
P.EAT = function(owner) return require("StateEat"):create(owner) end
P.PLAY = function(owner) return require("StatePlay"):create(owner) end
P.SLEEP = function(owner) return require("StateSleep"):create(owner) end
P.CLEAN = function(owner) return require("StateClean"):create(owner) end
-- 构造函数
function P:ctor()
self._scheduleId = nil -- 定时器ID
self._interval = 0.03 -- 定时器的时间间隔
self._state = nil -- 状态
self._preState = nil -- 上一个状态[保存上一个状态,实现自由“还原上一个状态”的功能]
end
-- 启动状态机
function P:start(owner)
self._owner = owner
self._state = require("StateIdle"):create(owner)
-- 由此类维护整个状态机的一个定时器(此处使用while循环来强行制作定时器……,通过os.clock()来实现sleep功能,虽然会比较占用CPU,但只是练手小游戏,先这样用着吧)
-- 如果小鸡饱食度为0,则饿死,游戏结束跳出循环
local index = 1
while owner.satiety > 0 do
self:update(index)
index = index + 1
end
print("小鸡去世,享年" .. math.floor(self._owner.time / 24) .. "天" .. self._owner.time % 24 .. "小时 ")
-- self._scheduleId = display.scheduleScriptFunc(function (dt)
-- self:update(dt)
-- end, 5, false)
end
-- 暂停状态机
function P:pause()
if self._state then
self._state:pause()
end
end
-- 继续状态机
function P:resume()
if self._state then
self._state:resume()
end
end
-- 定时器
function P:update(dt)
-- print("zien ", dt)
-- 调用当前状态的update函数执行当前状态的定时器
if self._state then
self._state:update()
self:checkState()
end
self:sleep1(self._interval)
end
-- 检查状态是否需要改变
function P:checkState()
if self._owner.satiety < 20 then -- 非常饿
self:changeState(FSMMachine.EAT)
elseif self._owner.energy < 20 then -- 非常疲惫
self:changeState(FSMMachine.SLEEP)
elseif self._owner.happy < 20 then -- 非常不开心
self:changeState(FSMMachine.PLAY)
elseif self._owner.clean < 20 then -- 非常脏
self:changeState(FSMMachine.CLEAN)
elseif self._owner.satiety < 40 then -- 饿
self:changeState(FSMMachine.EAT)
elseif self._owner.energy < 40 then -- 疲惫
self:changeState(FSMMachine.SLEEP)
elseif self._owner.happy < 40 then -- 不开心
self:changeState(FSMMachine.PLAY)
elseif self._owner.clean < 40 then -- 脏
self:changeState(FSMMachine.CLEAN)
else -- 一切正常则发呆
self:changeState(FSMMachine.IDLE)
end
self:showInfo()
end
-- 输出小鸡状态信息
function P:showInfo()
local str = "鸡龄:" .. math.floor(self._owner.time / 24) .. "天" .. self._owner.time % 24 .. "小时 "
str = str .. " 饱食度:" .. self._owner.satiety
str = str .. " 欢乐度:" .. self._owner.happy
str = str .. " 精力度:" .. self._owner.energy
str = str .. " 清洁度:" .. self._owner.clean
str = str .. " " .. self._state:info()
print(str)
end
-- 状态改变
function P:changeState(state)
if self._state and self._state:condition() then
local newState = state(self._owner)
if newState then
-- 清空上一个状态
if self._preState then
self._preState:clear()
end
self._preState = self._state
self._state = newState
end
end
end
-- 返回上一个状态
function P:revert()
if self._preState and self._state then
local tmpState = self._preState
self._preState = self._state
self._state = tmpState
end
end
function P:sleep(n)
if n > 0 then
os.execute("ping -n " .. tonumber(n + 1) .. " localhost > NUL")
end
end
function P:sleep1(n)
local t = os.clock()
while os.clock() < t + n do
end
end
return P
简单分析一下这个machine的一些功能:
算是一个状态表,通过使用这个表的状态,来对小鸡进行状态切换。
启动状态机后,machine类会维持一个定时器,定时器会执行【当前生效状态】对应的update函数。通过machine类的定时器来驱动着当前状态的运行。
根据不同的条件来切换不同的状态。达成条件后,使用上面提交的条件表的值,就可以进行状态切换。
状态模式大致就是这样子的结构了,一个machine类,一个state基类,再加上N个不同功能的state子类就构成了一个大致的状态模式了。下面就是简单的入口函数。
require "tools.init"
require "init"
math.randomseed(tostring(os.time()):reverse():sub(1, 7)) -- 设置随机种子
function main()
local chick = {satiety = 100, happy = 100, energy = 100, clean = 100, time = 0}
local fsm = FSMMachine:new()
fsm:start(chick)
end
main()
只是设置了小鸡的初始属性,然后启动状态机,你就可以看到小鸡这一生了!
运行效果大致如下:
无聊的我,尝试着养了10次小鸡,结果
只有3只算是勉强养大的,我果然木有养小鸡的天赋=。=!
顺带说说,这个养小鸡的游戏,实在实在是太简单了,真实项目中会出现更多更复杂的情况,如:
1.在状态基类里我预设了暂停状态机的功能;
2. 在machine类里我预设了返回上一个执行的状态的功能;
3.在这游戏中,必然是执行完一个状态才会进入下一个状态的,实际情况有可能一个状态还在执行过程中,就会有需求去改变状态,具体是中断当前状态直接进入下一个状态,还是记录下下一个状态,等待当前状态执行结束再进入下一个状态,这些都需要具体问题具体分析。
4.这个状态机也是全自动的状态机,而且所有子类状态切换的条件完成一致,所以在状态切换那块也写得极其简单,实际应用中,大概率会因为外部的一些变化而需要让状态机切换状态,而且每个子类都可能有自己不同的状态切换条件。
简单来说,这只是个练手的小demo,玩玩而已啦~
源代码在此处:
版权声明
本文为[0zien0]所创,转载请带上原文链接,感谢
https://blog.csdn.net/a42626423/article/details/124347500
边栏推荐
猜你喜欢
The people of Beifeng have been taking action
可视化常见问题解决方案(七)画图刻度设置解决方案
Background management system framework, there is always what you want
How to improve the service efficiency of the hotel without blind spots and long endurance | public and Private Integrated walkie talkie?
保洁阿姨都能看懂的中国剩余定理和扩展中国剩余定理
Mysql 数据库从设计上的优化
学习笔记5-梯度爆炸和梯度消失(K折交叉验证)
vim+ctags+cscpope开发环境搭建指南
自定义时间格式(YYYY-MM-DD HH:mm:ss 星期X)
关于'enum'枚举类型以及结构体的问题。
随机推荐
Django使用mysql数据库报错解决
关于素数的不到100个秘密
5.SQL99标准:内连接和外连接
可视化常见问题解决方案(八)共享绘图区域问题解决方案
Machine vision series (01) -- Overview
【自我激励系列】你永远不会准备好
colab
小程序换行符\n失效问题解决-日常踩坑
npm 安装踩坑
Metro wireless intercom system
数据库查询优化的方式
快速下载vscode的方法
UDP基础学习
Typora操作技巧说明(一)
自定义时间格式(YYYY-MM-DD HH:mm:ss 星期X)
反思 | 事件总线的局限性,组件化开发流程中通信机制的设计与实现
Beifeng communication helps Zhanjiang fire brigade build PDT wireless communication system
对STL容器的理解
获取字符格式的当前时间
The people of Beifeng have been taking action