注:寫這篇文章的時候,筆者所用的是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事件
本文若是有寫的不對的地方,還請你們指出,交流學習:)