FSM有限状态机

        当你要制作的功能需要通过不同的情况(条件),用不同的方式去处理问题时,一般情况下,会写大量的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,玩玩而已啦~

源代码在此处:

有限状态机、状态模式的实现源代码-cocos2D文档类资源-CSDN下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值