状态机
状态机类
--理论上机器人是个循环状态机,有个初始状态,之后每个时间帧都会刷新状态
-- |设置初始状态|
-- |
-- |------------->|执行该状态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
外部使用状态机需要调用两个接口
- 设置初始状态,即调用
grobot_logic:set_state(state)
方法设置状态。 - 开始状态机的运行,即调用
grobot_logic:start_running()
方法
function grobot_logic:start_robot()
#1. 设置初始状态
self:set_state(self.robot_type or "empty")
#2. 开始状态机的运行
self:start_running()
end
状态机开始后的执行流程
- 状态机在
start_running()
方法中 执行状态的方法 - 执行完状态的方法后:
- 如果该状态的方法无返回值
自动执行调用 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)
行为树
行为树路径表
行为树类
--按照路径点配置进行测试,理论上可以理解为行为树
-- 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
外部使用行为树需要调用一个接口
- 开始行为树的运行,即调用
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
行为树开始后的执行流程
- 如果行为树节点是无效的,或者是该行为树节点是最后一个行为树节点,那么结束行为树的执行。
- 如果行为树节点执行过深,那么结束行为树的执行。
- 行为树在path_start()方法中 执行行为树节点的方法
- 如果 行为树节点的方法 执行失败,那么结束行为树的执行。
行为树节点的注意事项
- 每个行为树节点的逻辑最后,一定要写上
self:path_start(path.next)
,代表执行下一个行为树节点的逻辑,否则,行为树执行链条就卡在这个行为树节点了。