-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
212 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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中的常见报错信息。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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中实现更出色的效果。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Lua层的常见报错信息[TODO] |