Skip to content

Commit

Permalink
[doc] add 1_3_5/6/7
Browse files Browse the repository at this point in the history
  • Loading branch information
Mark-tz committed Jul 6, 2024
1 parent e47126b commit 9e2a607
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 6 deletions.
2 changes: 1 addition & 1 deletion doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"attrs_inline",
]
myst_footnote_transition = False

suppress_warnings = ["myst.header"]
templates_path = ['_templates']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']

Expand Down
5 changes: 0 additions & 5 deletions doc/posts/1_rocos_basic/1_3_0_play_create.md

This file was deleted.

113 changes: 113 additions & 0 deletions doc/posts/1_rocos_basic/1_3_5_play_create.md
Original file line number Diff line number Diff line change
@@ -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中的常见报错信息。
97 changes: 97 additions & 0 deletions doc/posts/1_rocos_basic/1_3_6_task.md
Original file line number Diff line number Diff line change
@@ -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中实现更出色的效果。
1 change: 1 addition & 0 deletions doc/posts/1_rocos_basic/1_3_7_error_msg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Lua层的常见报错信息[TODO]

0 comments on commit 9e2a607

Please sign in to comment.