【游戏业务逻辑】Lua 状态机与行为树

状态机

状态机类

--理论上机器人是个循环状态机,有个初始状态,之后每个时间帧都会刷新状态
--                               |设置初始状态|
--                                      |
--               |------------->|执行该状态running|
--               |                      |
--               |          |无返回值,有返回值|
--               |             |           |
--               |       |设置定时器|   |进入自定义流程|
--               |             |                  |
--      |-->|定时器触发,重置下一帧状态,回归|   |自定义逻辑|
--      |                                          |
--      --------------------------------|逻辑结束调用end_running|

local grobot_logic = require "app.logic.robot.robot_logic"
local robot_test = require "app.conf.game_robot_test"

local math_random = math.random

#状态机 状态列表
local STATE_LIST = {
    empty = {"running_empty", 0},
    -- bag = {"running_bag", 100},
    -- test = {"running_test", 100},
    -- path = {"running_test2", 100},
    -- build_sys = {"running_build_sys", 100},
    -- modify_sys = {"running_science_modify_sys", 100},
    -- modify_sys2 = {"running_science_modify_sys2", 100},
    -- thread_copy = {"running_thread_copy", 100},
    favour = {"running_favour", 100},
    hero = {"running_hero", 100},
    keepsake = {"running_keepsake", 100},
}

local STATE_TOTAL_WEIGH = 0
for _, v in pairs(STATE_LIST) do
    STATE_TOTAL_WEIGH = STATE_TOTAL_WEIGH + v[2]
end

function grobot_logic:set_state(state)
    self.old_state = self.state
    print("robot set state ", state, "old =", self.old_state)
    self.state = state
end

function grobot_logic:reset_state()
    self.old_state = self.state
    self.state = self.robot_type or "empty"
end

function grobot_logic:random_state()
    if self.state ~= "empty" then
        return
    end

    local rand_val = math_random(1, STATE_TOTAL_WEIGH)
    local state = nil
    for k, v in pairs(STATE_LIST) do
        if v[2] >= rand_val then
            state = k
            break
        else
            rand_val = rand_val - v[2]
        end
    end

    print("random_state", state)
    if state then
        self:set_state(state)
    end
end

function grobot_logic:start_running()
    local state = self.state
    if not state then
        return
    end

    local state_info = STATE_LIST[state]
    if state_info and #state_info > 0 then
        if self[state_info[1]] then
            print("--------------------------", os.time(), "-----------------------")
            local ret = self[state_info[1]](self, self.old_state, state)
            if not ret then
                self:end_running()
            end
        end
    else
        logger.error("start_running[%s] not func", state)
        self:end_running()
    end
end

function grobot_logic:running_empty(old_state, new_state)
    print("running_empty", old_state, new_state)
    self:random_state()

    local state_info = STATE_LIST[self.state]
    if state_info and #state_info > 0 then
        if self[state_info[1]] then
            print("--------------------------", os.time(), "-----------------------")
            return self[state_info[1]](self, self.old_state, self.state)
        end
    end
end

function grobot_logic:state_tick_cb(data)
    local state = data and data[1]

    if state then
        self:set_state(state)
    else
        self:reset_state()
    end

    self:start_running()
end

--- 函数功能说明
--@string[opt=nil] 状态(对应STATE_LIST)
--@usage
-- 1.结束running当前流程
-- 2.如果有参数,则下一帧执行该状态类型
-- 3.如果无参数,则下一帧恢复默认状态类型
function grobot_logic:end_running(state)
    self:unregister_tick("state_running_tick")
    if robot_test.robot_interval > 0 then
        self:register_tick("state_running_tick", robot_test.robot_interval, grobot_logic.state_tick_cb, {state})
    end
end

function grobot_logic:get_state()
    return self.state
end

return grobot_logic

外部使用状态机需要调用两个接口

  1. 设置初始状态,即调用grobot_logic:set_state(state)方法设置状态。
  2. 开始状态机的运行,即调用grobot_logic:start_running()方法
function grobot_logic:start_robot()
    #1. 设置初始状态
    self:set_state(self.robot_type or "empty")
    #2. 开始状态机的运行
    self:start_running()
end

状态机开始后的执行流程

  1. 状态机在start_running()方法中 执行状态的方法
  2. 执行完状态的方法后:
  • 如果该状态的方法无返回值

自动执行调用 grobot_logic:end_running()

-> grobot_logic:state_tick_cb() -> self:reset_state() -> self:start_running()。重新随机运行一个状态机。

  • 如果该状态的方法有返回值

不会自动执行调用grobot_logic:end_running()。因此进入自定义流程(下一个状态机、或者执行行为树流程)

-1. 执行下一个状态机。

在本状态机方法逻辑的最后,需要主动调用grobot_logic:end_running(state),其中参数state是希望执行的下一个状态机状态。

-> grobot_logic:state_tick_cb(state) -> self:set_state(state) -> self:start_running()。运行指定的下一个状态机。

例如:如果执行下面代码的start_robot(),状态机将会按照"state1" -> “state2” -> “state3” -> "state1"的顺序无限循环,因为每个状态的处理函数最后都调用了end_running来过渡到下一个状态,并且没有明确的终止条件。

#状态机 状态列表
local STATE_LIST = {
    state1 = {"running_state1", 0},
    state2 = {"running_state2", 100},
    state3 = {"running_state3", 100},
}

#开始运行状态机
function grobot_logic:start_robot()
    #1. 设置初始状态
    self:set_state("state1")
    #2. 开始状态机的运行
    self:start_running()
end

function grobot_logic:running_state1(old_state, new_state)
    print("running running_state1 logic code")
    return self:end_running("state2")
end

function grobot_logic:running_state2(old_state, new_state)
    print("running running_state2 logic code")
    return self:end_running("state3")
end

function grobot_logic:running_state3(old_state, new_state)
    print("running running_state3 logic code")
    return self:end_running("state1")
end

-2. 执行行为树流程。(下面细说)

调用 grobot_logic:path_start(path_id)

行为树

行为树路径表

image.png

行为树类

--按照路径点配置进行测试,理论上可以理解为行为树
-- 1.启动的时候需要传开始路径id
-- 2.只要不调用 path_end 就不会切换到下一状态

local grobot_logic = require "app.logic.robot.robot_logic"
local zn = require "zn"

local math_random = math.random
local PATH_CYCLIC_CNT_MAX = 100

-----------------------------------------------path 配置调用接口 开始---------------------------------------------------
local function think(param)
    local val = param[1]
    local rate = math_random(1, 100)
    return (rate <= val)
end

local PATH_FUNC = {
    think_byval = think,
}
-----------------------------------------------path 配置调用接口 结束---------------------------------------------------

------------------------------------------------- 路径点接口 开始 --------------------------------------------------------

function grobot_logic:path_next(path)
    -- 直接开始下一路径什么也不做
    print("#############--- path_next", path.id)
    assert(path, "path_next not path")
    self:path_start(path.next)
end

function grobot_logic:path_check(path)
    -- 进入2选1路径
    print("#############--- path_check", path.id)
    assert(path, "path_check not path")

    if PATH_FUNC[path.param[2]] then
        if PATH_FUNC[path.param[2]](path.param[3]) then
            self:path_start(path.param[1])
            return
        end
    end

    self:path_start(path.next)
end

function grobot_logic:path_say(path)
    print("#########!!!!!!!!say:", path.param[1])
    assert(path, "path_say not path")

    self:path_start(path.next)
end

function grobot_logic:path_gm(path)
    -- 使用gm指令
    print("#############--- path_gm", path.id)
    assert(path, "path_gm not path")

    self:send_gm(path.param[1], path.param[2])
    self:path_start(path.next)
end

function grobot_logic:path_gm_add_item(path)
    -- gm指令添加道具
    print("#############--- path_gm_add_item", path.id)
    assert(path, "path_gm_add_item not path")

    self:send_gm("add_item_list", path.param[1])
    self:path_start(path.next)
end

function grobot_logic:path_science_modify_random(path)
    -- 随机学习一个科学改造
    print("#############--- path_science_modify_random", path.id)
    assert(path, "path_science_modify_random not path")

    local science_modify_id = self:random_science_modify_id(path.param[1], path.param[2])
    if science_modify_id > 0 then
        local ret, item_id, item_cnt, error_code = self:check_science_modify_levelup(science_modify_id)
        if not ret and item_id == 0 then
            logger.error("path_science_modify_levelup not science_modify[%d] code[%d]", science_modify_id, error_code)
            self:path_end()
            return
        end
        self:set_path_val("path_science_modify_info", {science_modify_id, ret, item_id, item_cnt})
    end

    self:path_start(path.next)
end

function grobot_logic:path_science_modify_levelup(path)
    -- 检测学习一个科学改造
    print("#############--- path_science_modify_levelup", path.id)
    assert(path, "path_science_modify_levelup not path")
    local science_modify_id = path.param[1]
    local path_info = self:get_path_val("path_science_modify_info")
    if not science_modify_id or science_modify_id == 0 then
        if path_info then
            science_modify_id = path_info[1]
        else
            logger.error("path_science_modify_levelup not science_modify")
            self:path_end()
            return
        end
    end

    local ret = path_info[2]
    if not ret then
        -- 使用gm指令获得
        local item_id, item_cnt = path_info[3], path_info[4]
        self:send_gm("add_item_list", {uid = 1, item_list = {{item_id, item_cnt}}})

        self:set_path_val("wait_item_path", "path_science_modify_random")
        self:set_path_val("path_science_modify_random", {item_id, item_cnt, path.next, "science_modify_levelup", science_modify_id})
        return
    else
        self:science_modify_levelup(science_modify_id)
    end

    self:path_start(path.next)
end

function grobot_logic:path_wait_data_clear(path)
    -- 清理等待数据
    print("#############--- path_wait_end", path.id)
    assert(path, "path_wait_end not path")

    local path_key = self:get_path_val("wait_item_path")
    if path_key then
        self:set_path_val("wait_item_path", nil)
        self:set_path_val(path_key, nil)
    end
    self:path_start(path.next)
end

------------------------------------------------- 路径点接口 结束 --------------------------------------------------------

--- 启动路径点
--@number 路径点id
--@usage
-- 1.检测该路径点是否有配置,若无则结束
-- 2.若路径点接口调用出错,则会主动调用 path_end 结束当前流程
function grobot_logic:path_start(path_id)
    -- print("#############--- path_start", path_id)
    if path_id == 0 then
        self:path_end()
        return
    end

    local path_cnt = self:get_path_val("path_start_cyclic_cnt") or 0
    if path_cnt > PATH_CYCLIC_CNT_MAX then
        -- 添加个循环次数限制,避免行为树过深
        logger.error("path_start_cyclic_cnt too big[%d]", path_cnt)
        self:path_end()
        return
    else
        self:set_path_val("path_start_cyclic_cnt", path_cnt + 1)
    end

    local path = zn.sheetdata("Path", path_id)
    if not path or not grobot_logic[path.op] then
        logger.error("path_start[%s] not path cfg", path_id)
        self:path_end()
        return
    end

    local state, err = pcall(grobot_logic[path.op], self, path)
    if not state then
        logger.error("LUA PATH[%s] ERROR:%s", path_id, err)
        self:path_end()
    end
end

function grobot_logic:path_end()
    self:reset_path_val()
    self:end_running()
end

function grobot_logic:get_path_val(key)
    return self.path_val[key]
end

function grobot_logic:set_path_val(key, value)
    self.path_val[key] = value
end

function grobot_logic:reset_path_val()
    self.path_val = {}
end

return grobot_logic

外部使用行为树需要调用一个接口

  1. 开始行为树的运行,即调用grobot_logic:path_start(path_id)方法
function grobot_logic:running_test(old_state, new_state)
    local path_id = math_random(1, 2)
    print("#############--- path_start", path_id)
    #1. 开始行为树的运行
    self:path_start(path_id)
    return true
end

如上,随机了两个初始行为树节点的id,代表两个行为树链条,只需要在行为树路径表里配置好两个行为树链条即可。
比如,

  • 行为树链条1的执行顺序是:1 → 3 → 5 → 0
  • 行为树链条2的执行顺序是:2 → 6 → 0

行为树开始后的执行流程

  1. 如果行为树节点是无效的,或者是该行为树节点是最后一个行为树节点,那么结束行为树的执行。
  2. 如果行为树节点执行过深,那么结束行为树的执行。
  3. 行为树在path_start()方法中 执行行为树节点的方法
  4. 如果 行为树节点的方法 执行失败,那么结束行为树的执行。

行为树节点的注意事项

  1. 每个行为树节点的逻辑最后,一定要写上self:path_start(path.next),代表执行下一个行为树节点的逻辑,否则,行为树执行链条就卡在这个行为树节点了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值