[zz]quick狀態機分析

目錄

目錄目錄javascript

注:寫這篇文章的時候,筆者所用的是quick-cocos2d-x 2.2.1rc版本。java

quick狀態機

狀態機的設計,目的就是爲了不大量狀態的判斷帶來的複雜性,消除龐大的條件分支語句,由於大量的分支判斷會使得程序難以修改和擴展。但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

sample圖示

StateMachineStateMachine設計模式


用法

建立一個狀態機框架

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" },
    }
})

以後咱們就能夠經過異步

  • fsm:doEvent("start")-從"none"狀態轉換到"green"狀態
  • fsm:doEvent("warn")-從"green"狀態轉換到"yellow"狀態
  • fsm:doEvent("panic")-從"green"狀態轉換到"red"狀態
  • fsm:doEvent("calm")-從"red"狀態轉換到"yellow"狀態
  • fsm:doEvent("clear")-從"yellow"狀態轉換到"green"狀態

同時,async

  • fsm:isReady()-返回狀態機是否就緒
  • fsm:getState()-返回當前狀態
  • fsm:isState(state)-判斷當前狀態是不是參數state狀態
  • fsm:canDoEvent(eventName)-當前狀態若是能完成eventName對應的event的狀態轉換,則返回true
  • fsm:cannotDoEvent(eventName)-當前狀態若是不能完成eventName對應的event的狀態轉換,則返回true
  • fsm:isFinishedState()-當前狀態若是是最終狀態,則返回true
  • fsm:doEventForce(name, ...)-強制對當前狀態進行轉換

單一事件的多重from和to狀態

若是一個事件容許咱們從多個狀態(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以後,咱們能夠經過下面兩個方法來完成狀態轉換。

  • fsm:doEvent("eat")
  • fsm:doEvent("rest")

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

因此假如要使用簡寫的話,爲了不onSTATEonEVENT的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爲表結構,包含了

  • name 事件名字
  • from 事件表示的起始狀態
  • to 事件表示的目的狀態
  • args 額外的參數,用來傳遞用戶自定義的一些變量值
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"}舉例,我畫個示意圖來講明
callbackcallback

注意:以前的onbeforeEVENT,這裏EVENT就被具體替換爲clear,因而是onbeforeclear,而onbeforeevent相似的通用型則不用替換。

  • onbeforeclear - clear事件執行前的回調
  • onbeforeevent - 任何事件執行前的回調
  • onleavered - 離開紅色狀態時的回調
  • onleavestate - 離開任何狀態時的回調
  • onentergreen - 進入綠色狀態時的回調
  • onenterstate - 進入任何狀態時的回調
  • onafterclear - clear事件完成以後的回調
  • onafterevent - 任何事件完成以後的回調
3種影響事件響應的方式
  1. onbeforeEVENT方法中返回false來取消事件
  2. onleaveSTATE方法中返回false來取消事件
  3. 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()方法。


初始化選項

  • 狀態機的初始化選項通常根據咱們遊戲需求來決定,quick狀態機提供了幾個簡單的選項。 在默認狀況下,若是你沒指定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事件

本文若是有寫的不對的地方,還請你們指出,交流學習:)

相關文章
相關標籤/搜索