From 9e2a607832cb2a3e648f228f223fb711fbcca60d Mon Sep 17 00:00:00 2001 From: mark Date: Sat, 6 Jul 2024 20:57:07 +0800 Subject: [PATCH] [doc] add 1_3_5/6/7 --- doc/conf.py | 2 +- doc/posts/1_rocos_basic/1_3_0_play_create.md | 5 - doc/posts/1_rocos_basic/1_3_5_play_create.md | 113 +++++++++++++++++++ doc/posts/1_rocos_basic/1_3_6_task.md | 97 ++++++++++++++++ doc/posts/1_rocos_basic/1_3_7_error_msg.md | 1 + 5 files changed, 212 insertions(+), 6 deletions(-) delete mode 100644 doc/posts/1_rocos_basic/1_3_0_play_create.md create mode 100644 doc/posts/1_rocos_basic/1_3_5_play_create.md create mode 100644 doc/posts/1_rocos_basic/1_3_6_task.md create mode 100644 doc/posts/1_rocos_basic/1_3_7_error_msg.md diff --git a/doc/conf.py b/doc/conf.py index d44a358c..55e653bd 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -41,7 +41,7 @@ "attrs_inline", ] myst_footnote_transition = False - +suppress_warnings = ["myst.header"] templates_path = ['_templates'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] diff --git a/doc/posts/1_rocos_basic/1_3_0_play_create.md b/doc/posts/1_rocos_basic/1_3_0_play_create.md deleted file mode 100644 index 3fd74f40..00000000 --- a/doc/posts/1_rocos_basic/1_3_0_play_create.md +++ /dev/null @@ -1,5 +0,0 @@ -# Play - 执行框架与报错[TODO] - -> 本节目标: -> - Play的运行框架 -> - Lua层的报错信息 diff --git a/doc/posts/1_rocos_basic/1_3_5_play_create.md b/doc/posts/1_rocos_basic/1_3_5_play_create.md new file mode 100644 index 00000000..ef156551 --- /dev/null +++ b/doc/posts/1_rocos_basic/1_3_5_play_create.md @@ -0,0 +1,113 @@ +# Play - 框架:创建与选择Play + +> 本节目标:了解Play的运行框架 + +## Play的运行框架 + +让我们尝试梳理一下lua的总体结构,以及Play的运行框架。 +从c++到lua是使用了`LuaModule`中的一个封装函数`RunScript`,该函数使用`luaL_loadfile`这样一个lua的标准库函数来加载lua文件,然后使用`lua_pcall`来执行lua文件。在执行过程中,如果lua文件中有报错,c++层会捕获报错信息并输出到控制台和调试系统中,最终显示在`Client`界面上。 + +在c++的`DecisionModule`中,共出现了两次对`LuaModule`的`RunScript`函数的调用来执行脚本,一次是在`DecisionModule`的构造函数中,调用了`StartZeus.lua`用于初始化整个lua框架,另一次是在`DecisionModule`的`DoDecision`函数中,调用了`SelectPlay.lua`用于每帧的具体策略执行。 + +###### lua框架的初始化 + +:::{card} StartZeus.lua +```{code-block} lua +:linenos: +-- 设置随机数种子,保证后续的随机数是不同的 +math.randomseed(os.time()) +-- 设置模块的索引路径 +package.path = package.path .. ";./lua_scripts/?.lua" + +-- 加载模块 +require("Config") +require("RoleMatch") +require("Zeus") +``` +::: + +以`StartZeus.lua`作为入口,分别加载了`Config/RoleMatch/Zeus`三个模块。在旧版本中,`Config.lua`中使用table完成所有的脚本/cskill的设置工作并在`Zeus.lua`中完成初始化。在新版本中,脚本/cskill的设置工作被自动扫描的方式替代(战术包),`Config.lua`中只保留了一些全局变量的设置。`RoleMatch.lua`中完成了角色匹配的工作,`Zeus.lua`中完成了整个lua框架的初始化工作。 + +###### 每帧的具体策略执行 + +:::{card} SelectPlay.lua 简化版 +```{code-block} lua +:linenos: + +-- 扫描战术包配置,选择裁判指令对应的脚本 +function chooseRefConfigScript(choice) + ... + return playName -- 返回的是脚本的name +end + +-- 选择裁判指令脚本 +function RunRefScript(name) + if USE_CUSTOM_REF_CONFIG then + -- 有战术包配置时的处理 + gCurrentPlay = chooseRefConfigScript(name) + else + -- 无战术包配置(兼容旧版本) + dofile(...) + end +end + +-- 判定是否需要进入裁判指令脚本 +function SelectRefPlay() + ... + RunRefScript(curRefMsg) + ... +end + +-- 主函数 +if SelectRefPlay() then + ... +else + if IS_TEST_MODE then + gCurrentPlay = gTestPlay + else + gCurrentPlay = gNormalPlay + end +end + +RunPlay(gCurrentPlay) +``` +::: +上述代码有部分简化,但足以表达整体逻辑。每帧调用`SelectPlay.lua`核心目的就是选择一个play作为当前执行的脚本,即`gCurrentPlay`。需要考虑的因素有: +- 当前裁判指令 +- 是否使用战术包配置 +- 是否为测试模式 + +--- +在选择完当前play后,调用`RunPlay`函数执行当前play的逻辑,在`RunPlay`函数中,会依次执行以下几个步骤: +- 获取当前脚本的状态 +- 获取当前状态中的`switch`函数/`match`值/所有task的集合 +- 执行`switch`函数维护脚本状态 +- 执行`match`函数匹配task +- 执行task对应的cskill + +下面是`RunPlay`函数的简化版,你可以在`Play.lua`中找到完整代码: + +:::{card} RunPlay函数 简化版 +```{code-block} lua +:linenos: +function RunPlay(name) + -- 获取当前脚本 + local curPlay = gPlayTable(name) + -- 更新当前脚本状态 + local curState = _RunPlaySwitch(curPlay, gCurrentState) + -- 根据状态跳转进行相应的更新和维护(例如bufcnt等) + ... + -- 执行角色匹配 + DoRolePosMatch(curPlay) + -- 执行task + for rolename, task in pairs(curPlay[gCurrentState]) do + ... + -- 执行task对应的cskill + task() + end +end +``` +::: +希望上面的解释能帮助你理解Play的运行框架。如何你阅读了rocos中的相关代码,会发现实际的代码与上述流程保持一致但还有更多的细节。这是由于lua本身作为一个只有部分支持OOP(面向对象编程)特性的语言,想要实现流程控制和状态维护,需要一些额外的工作。 + +我们将在之后章节更细致的解释`RunPlay`中与task解析相关的逻辑,以及lua中的常见报错信息。 diff --git a/doc/posts/1_rocos_basic/1_3_6_task.md b/doc/posts/1_rocos_basic/1_3_6_task.md new file mode 100644 index 00000000..67e3b1cc --- /dev/null +++ b/doc/posts/1_rocos_basic/1_3_6_task.md @@ -0,0 +1,97 @@ +# Play - 框架:解析task + +> 本节目标:了解task的执行流程 + +在上一节,我们简要分析了`SelectPlay.lua`的代码来理解Play的运行流程。在这一节,我们将继续深入,分析task的解析。 +我们之前简化了`RunPlay`中的代码,现在让我们再深入一些: +::::{tab-set} +:::{tab-item} RunPlay in Play.lua +```{code-block} lua +:linenos: +function RunPlay(name) + ... + DoRolePosMatch(curPlay) + ... + for rolename, task in pairs(curPlay[gCurrentState]) do + -- 排除非task的情况(例如switch函数,match匹配规则) + if rolename ~= "switch" and rolename ~= "match" then + -- 如果存在闭包,对task进行解包 + if type(task) == "function" then + task = task(gRoleNum[rolename]) + end + -- 解析当前执行的机器人号码 + local roleNum = gRoleNum[rolename] + -- 执行task对应的cskill + if roleNum ~= -1 then + -- 执行除运动以外的task参数 + if task[3] ~= nil then + -- 包括踢球模式、目标朝向、踢球精度、踢球力度、以及特殊flag配置 + local mkick = task[3](roleNum) + local mdir = task[4](roleNum) + local mpre = task[5](roleNum) + local mkp = task[6](roleNum) + local mcp = task[7](roleNum) + local mflag = task[8] + + ... -- 判断并配置是否吸球 + ... -- 判断并配置是否踢球 + + end + end + + -- 从lua端调用到c++-skill层 + task[1](roleNum) + end + end +end +``` +::: +:::{tab-item} Task.lua +```{code-block} lua +:linenos: +function goSimplePos(p, d, f) + ... + local mexe, mpos = SimpleGoto{pos = p, dir = idir, flag = iflag} + return {mexe, mpos} +end +``` +::: +:::{tab-item} TestScript.lua +```{code-block} lua +:linenos: +... +{ + switch = ..., + Leader = task.goSimplePos(CGeoPoint(0,0)), + -- 结合task.lua,上述代码等效于Leader = {mexe, mpos} + match = "(L)" +}, +... +``` +::: +:::{tab-item} SimpleGoto.lua +```{code-block} lua +function SimpleGoto(task) + ... + execute = function(runner) + mpos = _c(task.pos,runner) + mdir = _c(task.dir,runner) + + task_param = TaskT:new_local() + ... + return skillapi:run("Goto", task_param) + end + + matchPos = function() + return _c(task.pos) + end + + return execute, matchPos +end +:::: + +我们观察`task.lua`中的`goSimplePos`函数,这个函数返回一个table,包含了两个元素,`mexe`和`mpos`。在`TestScript.lua`中,我们调用了`goSimplePos`函数,返回的table被赋值给了`Leader`,这个table中的两个元素被分别赋值给了`mexe`和`mpos`。在`RunPlay`函数中,我们通过`task[1](roleNum)`调用了`mexe`,这个函数会返回一个`execute`函数,这个函数会在`RunPlay`中被调用,执行`SimpleGoto`中的`execute`函数。在`execute`函数中,我们调用了`skillapi:run`函数,这个函数会调用`c++`层的`Goto`技能。 + +除此之外,`SimpleGoto.lua`中的`matchPos`函数即`task[2]`会返回一个目标点,这个目标点会在`RunPlay`的`DoRolePosMatch`中被调用,用于动态匹配。 + +你可以看到,从上述的`Play.lua`的第17行开始,有关于task列表参数的第3-第8项,这些项是用于配置踢球模式、目标朝向、踢球精度、踢球力度、以及特殊flag配置。所以你可以在`task.lua`中的某个函数返回8个参数,这样做的好处是可以直接将非运动的简单指令交给`lua`层处理实现与运动本身的**解耦**,在`c++`层只需要负责移动即可,这可以在类似于动态射门的skill中实现更出色的效果。 \ No newline at end of file diff --git a/doc/posts/1_rocos_basic/1_3_7_error_msg.md b/doc/posts/1_rocos_basic/1_3_7_error_msg.md new file mode 100644 index 00000000..d907a81f --- /dev/null +++ b/doc/posts/1_rocos_basic/1_3_7_error_msg.md @@ -0,0 +1 @@ +# Lua层的常见报错信息[TODO]