FSM Lua

5k 词


Finite State Machine

Description

在讲这个设计模式之前, 我先举一个例子, 假如我们现在开发一款游戏, 我们的任务是实现一个角色, 我们需要根据玩家的输入来控制主角的行为。

那当按下B键时,他应该跳跃但这时有bug,在哪?我们没有阻止主角 “在空中跳跃”,当主角跳起来后持续的按下B键,这样会导致他一直飘在空中,那简单的修复就是添加一个 isJumping 布尔变量来跟踪主角跳跃

接着,我们要实现主角的闪避动作,当主角站在地面上的时候,玩家按下下方键,则躲避,松开此键则站立, 但这时有bug,在哪?
按下, 下方键来闪避,按B键从闪避状态直接跳起来,玩家在空中的时候松开手,玩家还在空中的时候,却显示站立头像,是时候添加另一个布尔标志位来解决问题了

接下来,我们在跳起来的过程,进行一次俯冲攻击,这样才酷炫, 但这时又有bug,在哪?我们发现主角在跳跃状态的时候不能再跳,但在俯冲攻击的时候却可以跳跃。又要添加成员变量了。

很明显,我们这种做法很有问题,每次我们添加一些功能的时候,都会不经意地破坏已有代码的功能。而且我们还有很多行为等动作没有添加,如果我们继续采用类似的做法,那Bug将会可能更多

你拥有一组状态,并且可以在这组状态之间进行切换,状态机同一时刻只能处于一种状态。每个状态有一组转换,每一个转换和每一个输入并指向另一个状态。 状态机由此而来

熟知面向对象方法的人来说,每一个条件分支可以用动态分发来解决。状态模式的目的就是将每个状态相关所有的数据和行为封装到相关的类里面。

Example 1

FSM.lua
local M = {}

function M:new(...)
    self.__index = self
    local ret = setmetatable({},self)
    ret:init(...)
    return ret
end

function M:init()
    self.StateTable = {}        -- 状态表
    self.CurState = nil         -- 当前状态
    self.LastState = nil        -- 当前状态的上一个状态
end

--- 增加一个状态
function M:AddState(state)
    if not self.StateTable[state.State] then
        self.StateTable[state.State] = state
    end
end

--- 删除一个状态
function M:DeleteState(state)
    if self.StateTable[state.State] then
        self.StateTable[state.State] = nil
    end
end

--- 切换状态
function M:TranslateState(state)
    if not self.StateTable[state] then 
        error("<color=orange>"..string.format("Error : State %s not exist state table",state).."</color>")
        return
    end
    if nil == self.CurState then
        self.StateTable[state]:DoEnter()
        self.CurState = state
        return
    end
    if self.StateTable[self.CurState]:Can(state) then
        self.StateTable[self.CurState]:DoLeave()
        self.StateTable[state]:DoEnter() 
        self.LastState = self.CurState
        self.CurState = state
    else
        error("<color=orange>"..string.format("Error : Can't Translate %s  to %s ",self.CurState,state).."</color>")
    end
end

--- 当前状态
function M:CurrentState()
    return self.CurState
end

--- 当前状态的:上一个状态
function M:lastState()
    return self.LastState
end 

return M
FSMState.lua
local M = {}

function M:new(...)
    self.__index = self
    local ret = setmetatable({},self)
    ret:init(...)
    return ret
end

function M:init(state,transitionTable,onEnterCallBack,onLeaveCallBack)
    self.State = state                                      -- 状态值
    self.Transition = transitionTable or {}                 -- 可迁移状态表
    self.onEnter = onEnterCallBack                          -- 进入时调用
    self.onLeave = onLeaveCallBack                          -- 离开时操作
end

--- 进入状态时的操作
function M:DoEnter()
    if self.onEnter then
        self.onEnter(self.State)
    end
end

--- 离开该状态时的操作
function M:DoLeave()
    if self.onLeave then
        self.onLeave(self.State)
    end
end

--- 是否可切换到指定状态
function M:Can(state) 
    return self.Transition[state]
end

return M
How to Use
local Standing_Enter = function()
    --进入站立状态的行为
end 

local Standing_Leave = function()
    --离开站立状态的行为
end 

local Jumping_Enter = function()
    --进入跳跃状态的行为
end 

local Jumping_Leave = function()
    --离开跳跃状态的行为
end 

local Ducking_Enter = function()
    --进入闪避状态的行为
end 

local Ducking_Leave = function()
    --离开闪避状态的行为
end 

function InitFSM()
    FState = {
        Standing = 1,
        Jumping  = 2,
        Ducking  = 3,
    }

    local Fsm = require(".....FSM")
    local State = require(".....FSMState")
    FSM = Fsm:new() 

    FSM:AddState(State:new(FState.Standing,{nil, FState.Jumping, FState.Ducking}, Standing_Enter, Standing_Leave))
    FSM:AddState(State:new(FState.Jumping,{FState.Standing, nil, nil}, Jumping_Enter,Jumping_Leave))
    FSM:AddState(State:new(FState.Ducking,{FState.Standing, nil, nil}, Ducking_Enter,Ducking_Leave))
    FSM:TranslateState(FState.Standing) 
end 

-- 获取当前状态
function current_GameState()
    return FSM:CurrentState()
end 

-- 获取上一个状态
function last_GameState() 
    return FSM:lastState()
end 

-- 改变状态
function ChangeFState(State)
    FSM:TranslateState(State)
end 

Example 2

rFSM 状态机

以下是rFSM 状态机的使用情况, 这是个人业余时间写的一个小框架(还在搬砖中……), 读者有时间的话可以去下面的 Github连接上浏览代码。

require ('Common.FSM.rfsm')

local GameState_Manager = Class()

local fsm

– 加载所有游戏状态对象
local gameStateType = {}
gameStateType.login_state = require("GameState.login_state").new()
gameStateType.user_state = require("GameState.user_state").new()
gameStateType.lobby_state = require("GameState.lobby_state").new()
gameStateType.room_state = require("GameState.room_state").new()
gameStateType.hero_state = require("GameState.hero_state").new()
gameStateType.loading_state = require("GameState.loading_state").new()
gameStateType.play_state = require("GameState.play_state").new()
gameStateType.over_state = require("GameState.over_state").new()

– 建立游戏状态之间的关系
local game_state_fsm = rfsm.state {

login_state = rfsm.state{ 
    entry = gameStateType.login_state.enter,
    exit = gameStateType.login_state.leave,
},



rfsm.transition { src=&#39;initial&#39;, tgt=&#39;login_state&#39; },
--rfsm.transition { src=&#39;hello&#39;, tgt=&#39;world&#39;, events={ &#39;e_done&#39; } },
--rfsm.transition { src=&#39;world&#39;, tgt=&#39;hello&#39;, events={ &#39;e_restart&#39; } },

}

– 默认构造函数
function GameState_Manager:ctor()

end

– 进入登录状态
function GameState_Manager:enter_default_state()
fsm = rfsm.init(game_state_fsm)
rfsm.run(fsm)
end

return GameState_Manager

Reference

pmsl. (2018). luaFSM. Retrieved from: https://github.com/pmsl/luaFSM

kmarkus. (2017). rFSM. Retrieved from: https://github.com/kmarkus/rFSM

preston. (2019). MobileLegend Retrieved from: https://github.com/Preston-Chen/MobileLegend