注:写这篇文章的时候,笔者所用的是quick-cocos2d-x 2.2.1rc版本。java
状态机的设计,目的就是为了不大量状态的判断带来的复杂性,消除庞大的条件分支语句,由于大量的分支判断会使得程序难以修改和扩展。但quick状态机的设计又不一样设计模式的状态模式,TA没有将各个状态单独划分红单独的状态类,相反根据js、lua语言的特色,特别设计了写法,使用起来也比较方便。git
quick框架中的状态机,是根据javascript-state-machine从新设计改写而成,同时sample/statemachine
范例也是根据js版demo改写而来。该js库如今是2.2.0
版本。基于js版的README.md,结合廖大的lua版重构,我针对状态机的使用作了点说明,若是有不对的地方,感谢指出:)。github
推荐你们在理解的时候结合sample/statemachine
范例进行理解,注意player设置成竖屏模式,demo里面的按钮在横屏模式下看不见。web
建立一个状态机框架
local fsm = StateMachine.new()
-- (注:和demo不一样的是,demo采用组件形式完成的初始化)
fsm:setupState({
initial = "green",
events = {
{name = "warn", from = "green", to = "yellow"},
{name = "panic", from = "green", to = "red" },
{name = "calm", from = "red", to = "yellow"},
{name = "clear", from = "yellow", to = "green" },
}
})
以后咱们就能够经过异步
同时,async
若是一个事件容许咱们从多个状态(from)转换到同一个状态(to), 咱们能够经过用一个集合来构建from状态。以下面的"rest"事件。可是,若是一个事件容许咱们从多个状态(from)转换到对应的不一样的状态(to),那么咱们必须将该事件分开写,以下面的"eat"事件。函数
local fsm = StateMachine.new()
fsm:setupState({
initial = "hungry",
events = {
{name = "eat", from = "hungry", to = "satisfied"},
{name = "eat", from = "satisfied", to = "full"},
{name = "eat", from = "full", to = "sick" },
{name = "rest", from = {"hungry", "satisfied", "full", "sick"}, to = "hungry"},
}
})
在设置了事件events
以后,咱们能够经过下面两个方法来完成状态转换。
rest
事件的目的状态永远是hungry
状态,而eat
事件的目的状态取决于当前所处的状态。
注意1:若是事件能够从任何当前状态开始进行转换,那么咱们能够用一个通配符
*
来替代from
状态。如rest
事件,咱们能够写成{name = "rest", from = "*", to = "hungry"}
。注意2:上面例子的
rest
事件能够拆分写成4个,以下:{name = "rest", from = "hungry", to = "hungry"}, {name = "rest", from = "satisfied", to = "hungry"}, {name = "rest", from = "full", to = "hungry"}, {name = "rest", from = "sick", to = "hungry"}
quick的状态机支持4种**特定事件**类型的回调:
onbeforeEVNET
- 在特定事件EVENT开始前被激活onleaveSTATE
- 在离开旧状态STATE时被激活onenterSTATE
- 在进入新状态STATE时被激活onafterEVENT
- 在特定事件EVENT结束后被激活注解:编码时候,EVENT/STATE应该被替换为特定的名字
为了便利起见,
onenterSTATE
能够简写为onSTATE
onafterEVENT
能够简写为onEVENT
因此假如要使用简写的话,为了不onSTATE
和onEVENT
的STATE/EVENT被替换成具体的名字后名字相同引发问题,to
状态和name
名字尽可能不要相同。好比
-- 角色开火
{name = "fire", from = "idle", to = "fire"}
--假如使用简写
--onSTATE --- onfire
--onEVENT --- onfire,回调会引发歧义。
--若是不使用简写
--则onenterSTATE --- onenterfire
--onafterEVENT --- onafterfire
另外,咱们能够使用5种通用型的回调来捕获全部事件和状态的变化:
onbeforeevent
- 在任何事件开始前被激活onleavestate
- 在离开任何状态时被激活onenterstate
- 在进入任何状态时被激活onafterevent
- 在任何事件结束后被激活onchangestate
- 当状态发生改变的时候被激活注解:这里是任何事件、状态, 小写的event、state不能用具体的事件、状态名字替换。
全部的回调都以event
为参数,该event为表结构,包含了
local fsm = StateMachine.new()
fsm = fsm:setupState({
initial = "green",
events = {
{name = "warn", from = "green", to = "yellow"},
{name = "panic", from = "green", to = "red" },
{name = "calm", from = "red", to = "yellow"},
{name = "clear", from = "yellow", to = "green" },
},
callbacks = {
onbeforestart = function(event) print("[FSM] STARTING UP") end,
onstart = function(event) print("[FSM] READY") end,
onbeforewarn = function(event) print("[FSM] START EVENT: warn!") end,
onbeforepanic = function(event) print("[FSM] START EVENT: panic!") end,
onbeforecalm = function(event) print("[FSM] START EVENT: calm!") end,
onbeforeclear = function(event) print("[FSM] START EVENT: clear!") end,
onwarn = function(event) print("[FSM] FINISH EVENT: warn!") end,
})
fsm:doEvent("warn", "some msg")
如上例子,fsm:doEvent("warn", "some msg")
中的some msg
做为额外的参数字段args
结合name
from
to
被添加到event
,此时
event = {
name = "warn",
from = "green",
to = "yellow",
args = "some msg"
}
而event
表正是回调函数的参数。
用{name = "clear", from = "red", to = "green"}举例,我画个示意图来讲明callback
注意:以前的onbeforeEVENT
,这里EVENT
就被具体替换为clear
,因而是onbeforeclear
,而onbeforeevent
相似的通用型则不用替换。
onbeforeEVENT
方法中返回false来取消事件onleaveSTATE
方法中返回false来取消事件onleaveSTATE
方法中返回ASYNC
来执行异步状态转换有时候,咱们须要在状态转换的时候执行一些异步性代码来确保不会进入新状态直到代码执行完毕。
举个例子来讲,假如要从一个menu
状态转换出来,或许咱们想让TA淡出?滑出屏幕以外?总之执行完动画再进入game
状态。
咱们能够在onleavestate
或者onleaveSTATE
方法里返回StateMachine.ASYNC
,这时状态机会被挂起,直到咱们使用了event的transition()
方法。
...
onleavered = function(event)
self:log("[FSM] LEAVE STATE: red")
self:pending(event, 3)
self:performWithDelay(function()
self:pending(event, 2)
self:performWithDelay(function()
self:pending(event, 1)
self:performWithDelay(function()
self.pendingLabel_:setString("")
event.transition()
end, 1)
end, 1)
end, 1)
return "async"
end,
...
提示:若是想取消异步事件,能够使用event的
cancel()
方法。
initial
状态,状态机会指定当前状态为none
状态,因此须要定义一个能将none
状态转换出去的事件。
local fsm = StateMachine.new()
fsm = fsm:setupState({
events = {
{name = "startup", from = "none", to = "green" },
{name = "panic", from = "green", to = "red" },
{name = "calm", from = "red", to = "green"}
}
})
echoInfo(fsm:getState()) -- "none"
fsm:doEvent("start")
echoInfo(fsm:getState()) -- "green"
initial
状态,那么状态机在初始化的时候会自动建立startup
事件,而且被执行。
local fsm = StateMachine.new()
fsm = fsm:setupState({
initial = "green",
events = {
-- 当指定initial状态时,这个startup事件会被自动建立,因此能够不用写这一句 {name = "startup", from = "none", to = "green" },
{name = "panic", from = "green", to = "red" },
{name = "calm", from = "red", to = "green"}
}
})
echoInfo(fsm:getState()) -- "green"
initial
状态:
local fsm = StateMachine.new()
fsm = fsm:setupState({
initial = {state = "green", event = "init"},
events = {
{name = "panic", from = "green", to = "red" },
{name = "calm", from = "red", to = "yellow"}
}
})
echoInfo(fsm:getState()) -- "green"
defer = true
local fsm = StateMachine.new()
fsm = fsm:setupState({
initial = {state = "green", event = "init", defer = true},
events = {
{name = "panic", from = "green", to = "red" },
{name = "calm", from = "red", to = "green"}
}
})
echoInfo(fsm:getState()) -- "none"
fsm:doEvent("init")
echoInfo(fsm:getState()) -- "green"
在默认状况下,若是咱们尝试着执行一个当前状态不容许转换的事件,状态机会抛出异常。若是选择处理这个异常,咱们能够定义一个错误事件处理。在quick中,发生异常的时候StateMachine:onError_(event, error, message)
会被调用。
local fsm = StateMachine.new()
fsm:setupState({
initial = "green",
events = {
{name = "warn", from = "green", to = "yellow"},
{name = "panic", from = "green", to = "red" },
{name = "calm", from = "red", to = "green"},
{name = "clear", from = "yellow", to = "green" },
}
})
fsm:doEvent("calm") -- fsm:onError_会被调用,在当前green状态下不容许执行calm事件
本文若是有写的不对的地方,还请你们指出,交流学习:)