zoukankan      html  css  js  c++  java
  • Quick StateMachine状态机

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


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

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

    1.创建状态机对象

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


    1.创建状态机组件

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

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


    2.初始化状态机(设置状态逻辑)

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

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

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


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


    events = {
          {name = "move", from = {"idle", "jump"}, to = "walk"},
    }
    这当中move是事件,就像触摸事件event.name那样,name表示事件名称,而from和to后面跟的idle。jump。walk表示状态。所以上面的意思就是。当运行move事件时,假设状态是idle或者jump。那么都会跳转到walk状态上。

    from的状态能够是单一状态,也能够使集合状态。就是几个状态,但to的状态仅仅能唯一,不然程序还给你来个随机状态?肯定不行的。


    所以这里须要想好我们的主角有哪些状态。当什么事件发生时。他会从什么状态变到什么状态上去。比如我简单这么写。


    events = {
    	{name = "move", from = {"idle", "jump"}, to = "walk"},
    	{name = "attack", from = {"idle", "walk"}, to = "jump"},
    	{name = "normal", from = {"walk", "jump"}, to = "idle"},
    },

    解释一下,假设是normal事件。无论主角在走路walk还是跳跃jump,都会变成闲置idle状态。其它同理。


    接下来一个重点是callbacks參数,

    即所谓回调了,就是事件触发,会运行一系列的函数。

    • onbeforeEVNET: 在事件EVENT開始前被激活
    • onleaveSTATE: 在离开旧状态STATE时被激活
    • onenterSTATE 或 onSTATE:在进入新状态STATE时被激活
    • onafterEVENT 或 onEVENT:在事件EVENT结束后被激活

    比如

    callbacks = {
    	onenteridle = function ()  --或者 onidle
    		print("idle")
    	end,
    },
    此外还有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类,为其加入一个状态机,
    local Player = class("Player", function ()
    	return display.newSprite("icon.png")
    end)
    
    function Player:ctor()
    	self:addStateMachine()
    end
    
    function Player:doEvent(event)
    	self.fsm:doEvent(event)
    end
    
    function Player:addStateMachine()
    	self.fsm = {}
    	cc.GameObject.extend(self.fsm):addComponent("components.behavior.StateMachine"):exportMethods()
    
    	self.fsm:setupState({
    		initial = "idle",
    
    		events = {
    			{name = "move", from = {"idle", "jump"}, to = "walk"},
    			{name = "attack", from = {"idle", "walk"}, to = "jump"},
    			{name = "normal", from = {"walk", "jump"}, to = "idle"},
    		},
    
    		callbacks = {
    			onenteridle = function ()
    				local scale = CCScaleBy:create(0.2, 1.2)
    				self:runAction(CCRepeat:create(transition.sequence({scale, scale:reverse()}), 2))
    			end,
    
    			onenterwalk = function ()
    				local move = CCMoveBy:create(0.2, ccp(100, 0))
    				self:runAction(CCRepeat:create(transition.sequence({move, move:reverse()}), 2))
    			end,
    
    			onenterjump = function ()
    				local jump = CCJumpBy:create(0.5, ccp(0, 0), 100, 2)
    				self:runAction(jump)
    			end,
    		},
    	})
    end
    
    return Player

    比較简单,回调函数仅仅是写了进入三个状态的回调,然后为Player加入一个doEvent函数,调用状态机中doEvent。

    回到我们的MyScene.lua中,

    local Player = import("..views.Player")
    
    local MyScene = class("MyScene", function ()
    	return display.newScene("MyScene")
    end)
    
    function MyScene:ctor()	
      
        local player = Player.new()
        player:setPosition(display.cx, display.cy)
        self:addChild(player)
    
        local function menuCallback(tag)
            if tag == 1 then 
                player:doEvent("normal")
            elseif tag == 2 then
                player:doEvent("move")
            elseif tag == 3 then
                player:doEvent("attack")
            end
        end
    
        local mormalItem = ui.newTTFLabelMenuItem({text = "normal", x = display.width*0.3, y = display.height*0.2, listener = menuCallback, tag = 1})
        local moveItem =  ui.newTTFLabelMenuItem({text = "move", x = display.width*0.5, y = display.height*0.2, listener = menuCallback, tag = 2})
        local attackItem =  ui.newTTFLabelMenuItem({text = "attack", x = display.width*0.7, y = display.height*0.2, listener = menuCallback, tag = 3})
        local menu = ui.newMenu({mormalItem, moveItem, attackItem})
        self:addChild(menu)
          
    end
    
    return MyScene

    加入我们刚才的Player。记得import或者require,这里为了方便我就通过菜单button的形式来分别doEvent了。







    点击打开链接


  • 相关阅读:
    YII 项目部署时, 显示空白内容
    github上传项目(使用git)、删除项目、添加协作者
    Mysql 报错:#1067
    解决:500 Internal Privoxy Error
    解决bcp导出CSV文件没有表头
    asp.net 获取当前,相对,绝对路径
    BCP 运行错误
    cmd 运行bcp 提示:'bcp' 不是内部或外部命令,也不是可运行的程序 或批处理文件。
    Android--数据持久化之SQLite
    Android--JUnit单元测试
  • 原文地址:https://www.cnblogs.com/lcchuguo/p/5043556.html
Copyright © 2011-2022 走看看