quick-cocos2d-x遊戲開發——StateMachine狀態機

狀態機在quick中是一個亮點,如果我們做一款RPG遊戲,一個角色一般會擁有idle,attack,walk,run,death這些狀態,如果遊戲角色的狀態採用分支條件判斷的話,會造成非常龐大而難以維護,但一旦使用了狀態機這種模式,就會顯得簡單方便。


對於quick中的狀態機是如何實現的咱們先不去了解,首先看看如何去使用它。

總結起來,如果讓一個類擁有狀態機,主要有兩步:

1.創建狀態機對象

2.初始化狀態機,主要包括事件和回調函數


1.創建狀態機組件

[html] view plaincopy
  1. self.fsm = {}  
  2. cc.GameObject.extend(self.fsm):addComponent("components.behavior.StateMachine"):exportMethods()  

這樣就創建了一個狀態機對象,接下來我們要對其初始化,其實也就是設置各個狀態的邏輯。


2.初始化狀態機(設置狀態邏輯)

設置狀態邏輯是重寫setupState方法,這其中有這麼幾個字段參數,

  • initial:狀態機的初始狀態
  • terminal (final):結束狀態
  • events:狀態發生轉變時對應的事件
  • callbacks:發生轉變時的回調函數

一般我們會設置initial,events和callbacks這三個。


先看events,在events中需要分清楚“事件”和“狀態”,events採用table結構,例如我們來寫一個

[html] view plaincopy
  1. events = {  
  2.       {name = "move"from = {"idle", "jump"}, to = "walk"},  
  3. }  

這其中move是事件,就像觸摸事件event.name那樣,name表示事件名稱,而from和to後面跟的idle,jump,walk表示狀態。所以上面的意思就是,當執行move事件時,如果狀態是idle或者jump,那麼都會跳轉到walk狀態上。

from的狀態可以是單一狀態,也可以使集合狀態,就是幾個狀態,但to的狀態只能唯一,不然程序還給你來個隨機狀態?肯定不行的。


所以這裏需要想好我們的主角有哪些狀態,當什麼事件發生時,他會從什麼狀態變到什麼狀態上去。例如我簡單這麼寫,

[html] view plaincopy
  1. events = {  
  2.     {name = "move"from = {"idle", "jump"}, to = "walk"},  
  3.     {name = "attack"from = {"idle", "walk"}, to = "jump"},  
  4.     {name = "normal"from = {"walk", "jump"}, to = "idle"},  
  5. },  

解釋一下,如果是normal事件,不管主角在走路walk還是跳躍jump,都會變成閒置idle狀態。其他同理。


接下來一個重點是callbacks參數,

即所謂回調了,就是事件觸發,會執行一系列的函數。

  • onbeforeEVNET: 在事件EVENT開始前被激活
  • onleaveSTATE: 在離開舊狀態STATE時被激活
  • onenterSTATE 或 onSTATE:在進入新狀態STATE時被激活
  • onafterEVENT 或 onEVENT:在事件EVENT結束後被激活

例如

[html] view plaincopy
  1. callbacks = {  
  2.     onenteridle = function ()  --或者 onidle  
  3.         print("idle")  
  4.     end,  
  5. },  

此外還有5種通用型的回調來捕獲所有事件和狀態的變化:
  • onbeforeevent: 在任何事件開始前被激活
  • onleavestate: 在離開任何狀態時被激活
  • onenterstate:在進入任何狀態時被激活
  • onafterevent :在任何事件結束後被激活
  • onchangestate :當狀態發生改變的時候被激活
這裏面的名稱是不可以修改的,它是針對於任何事件和任何狀態的。
所以大家可以想象一下這其中有多少事件回調和多少狀態回調,它們的先後順序,咱們可以自己分別print一下就知道調用的先後了,這裏就不演示了。

最後,就是調用這些事件了,通過self.fsm:doEvent(event)就可以了,參數event對應events參數名稱。此外還有這些,
  • 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, ...) :強制對當前狀態進行轉換

接下來在實際運用一下,我們創建一個Player類,爲其添加一個狀態機,
[html] view plaincopy
  1. local Player = class("Player", function ()  
  2.     return display.newSprite("icon.png")  
  3. end)  
  4.   
  5. function Player:ctor()  
  6.     self:addStateMachine()  
  7. end  
  8.   
  9. function Player:doEvent(event)  
  10.     self.fsm:doEvent(event)  
  11. end  
  12.   
  13. function Player:addStateMachine()  
  14.     self.fsm = {}  
  15.     cc.GameObject.extend(self.fsm):addComponent("components.behavior.StateMachine"):exportMethods()  
  16.   
  17.     self.fsm:setupState({  
  18.         initial = "idle",  
  19.   
  20.         events = {  
  21.             {name = "move"from = {"idle", "jump"}, to = "walk"},  
  22.             {name = "attack"from = {"idle", "walk"}, to = "jump"},  
  23.             {name = "normal"from = {"walk", "jump"}, to = "idle"},  
  24.         },  
  25.   
  26.         callbacks = {  
  27.             onenteridle = function ()  
  28.                 local scale = CCScaleBy:create(0.2, 1.2)  
  29.                 self:runAction(CCRepeat:create(transition.sequence({scale, scale:reverse()}), 2))  
  30.             end,  
  31.   
  32.             onenterwalk = function ()  
  33.                 local move = CCMoveBy:create(0.2, ccp(100, 0))  
  34.                 self:runAction(CCRepeat:create(transition.sequence({move, move:reverse()}), 2))  
  35.             end,  
  36.   
  37.             onenterjump = function ()  
  38.                 local jump = CCJumpBy:create(0.5, ccp(0, 0), 100, 2)  
  39.                 self:runAction(jump)  
  40.             end,  
  41.         },  
  42.     })  
  43. end  
  44.   
  45. return Player  

比較簡單,回調函數只是寫了進入三個狀態的回調,然後爲Player添加一個doEvent函數,調用狀態機中doEvent。

回到我們的MyScene.lua中,
[html] view plaincopy
  1. local Player = import("..views.Player")  
  2.   
  3. local MyScene = class("MyScene", function ()  
  4.     return display.newScene("MyScene")  
  5. end)  
  6.   
  7. function MyScene:ctor()   
  8.     
  9.     local player = Player.new()  
  10.     player:setPosition(display.cx, display.cy)  
  11.     self:addChild(player)  
  12.   
  13.     local function menuCallback(tag)  
  14.         if tag == 1 then   
  15.             player:doEvent("normal")  
  16.         elseif tag == 2 then  
  17.             player:doEvent("move")  
  18.         elseif tag == 3 then  
  19.             player:doEvent("attack")  
  20.         end  
  21.     end  
  22.   
  23.     local mormalItem = ui.newTTFLabelMenuItem({text = "normal"x = display.width*0.3, y = display.height*0.2, listener = menuCallbacktag = 1})  
  24.     local moveItem =  ui.newTTFLabelMenuItem({text = "move"x = display.width*0.5, y = display.height*0.2, listener = menuCallbacktag = 2})  
  25.     local attackItem =  ui.newTTFLabelMenuItem({text = "attack"x = display.width*0.7, y = display.height*0.2, listener = menuCallbacktag = 3})  
  26.     local menu = ui.newMenu({mormalItem, moveItem, attackItem})  
  27.     self:addChild(menu)  
  28.         
  29. end  
  30.   
  31. return MyScene  

添加我們剛纔的Player,記得import或者require,這裏爲了方便我就通過菜單按鈕的形式來分別doEvent了。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章