• quick-cocos2d-x 游戏开发——StateMachine 状态机


    状态机在quick中是一个亮点,如果我们做一款RPG游戏,一个角色一般会拥有idle,attack,walk,run,death这些状态,如果游戏角色的状态采用分支条件判断的话,会造成非常庞大而难以维护,但一旦使用了状态机这种模式,就会显得简单方便。

    对于quick中的状态机是如何实现的咱们先不去了解,首先看看如何去使用它。

    总结起来,如果让一个类拥有状态机,主要有两步:

    1.创建状态机对象

    2.初始化状态机,主要包括事件和回调函数

    1.创建状态机组件

    [html] view plain copy

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

    这样就创建了一个状态机对象,接下来我们要对其初始化,其实也就是设置各个状态的逻辑。

     

    设置状态逻辑是重写setupState方法,这其中有这么几个字段参数,

    • initial:状态机的初始状态
    • terminal (final):结束状态
    • events:状态发生转变时对应的事件
    • callbacks:发生转变时的回调函数

    一般我们会设置initial,events和callbacks这三个。

    先看events,在events中需要分清楚“事件”和“状态”,events采用table结构,例如我们来写一个

    [html] view plain copy

    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 plain copy

    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 plain copy

    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 plain copy

    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 plain copy

    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  =  menuCallback ,  tag  =  1})  
    24.     local  moveItem  =   ui .newTTFLabelMenuItem({ text  =  "move" ,  x  =  display.width*0.5,  y  =  display .height*0.2,  listener  =  menuCallback ,  tag  =  2 })  
    25.     local  attackItem  =   ui .newTTFLabelMenuItem({ text  =  "attack" ,  x  = display .width*0.7,  y  =  display .height*0.2,  listener  =  menuCallback ,  tag  =  3})  
    26.     local  menu  =  ui .newMenu({mormalItem, moveItem, attackItem})  
    27.     self:addChild(menu)  
    28.         
    29. end  
    30.   
    31. return MyScene  
    添加我们刚才的Player,记得import或者require,这里为了方便我就通过菜单按钮的形式来分别doEvent了。 

  • 相关阅读:
    转:spring-session
    转:SpringBoot项目如何进行打包部署
    事务的隔离级别
    获取打卡记录接口返回数据情况说明
    批注@SuppressWarnings 的作用
    mybatis-generator eclipse插件 使用方法
    几种常见数据库的driverClassName和url
    ssm 配置多个数据源
    常用正则表达式
    tomcat 配置成服务
  • 原文地址:https://www.cnblogs.com/guangyun/p/5249932.html
Copyright © 2020-2023  润新知