Skip to content

Commit

Permalink
[doc] add 1_2_0/1
Browse files Browse the repository at this point in the history
  • Loading branch information
Mark-tz committed Jul 8, 2024
1 parent 9e2a607 commit 6741c74
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 2 deletions.
Binary file added doc/img/1_2_1_run_circle.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
106 changes: 106 additions & 0 deletions doc/posts/1_rocos_basic/1_2_1_closure_params.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Play - 让机器人动态响应

> 在上一节中,我们了解了如何创建一个基本的play让机器人两点运动,你发现了吗?在这个play中,机器人的行为是固定的,很难做出一些高动态的行为,例如绕球转。在这一节中,我们学习如何添加动态参数让task变得更灵活,也会学习到在书写play时至关重要的概念 - **闭包**
## 闭包

在学习闭包之前,我们先来看一个例子:


```{code-block} lua
:linenos:
function f(num)
local i = 0
return function()
i = i + num
return i
end
end
local g,h = f(2),f(3)
print(g(),h()) -- 2 3
print(g()) -- 4
print(g(),h()) -- 6 6
```

在这个例子中,我们定义了一个函数`f`,它接受一个参数`num`,返回一个函数。这个函数内部定义了一个局部变量`i`,并返回一个匿名函数。这个匿名函数每次调用时,`i`的值会增加`num`。我们可以看到,`g``h`是两个不同的闭包,局部变量之间不会相互影响。

闭包是一种函数,它可以访问其词法范围内的变量。闭包是一种非常强大的工具,可以用来实现许多功能,例如:函数工厂、延迟计算、状态保持等。

## 动态参数
我们截取上节代码中的一行来观察:

```{code-block} lua
Leader = task.goCmuRush(CGeoPoint(0,0)),
```

在这行代码中,我们调用了`task.goCmuRush`函数,传入了一个`CGeoPoint`类型的参数。这个参数是固定的,我们无法在运行时改变它。如果我们想要让机器人在运行时动态地改变目标点,我们可以使用闭包来实现。

我们可以将上面的代码改写为:

```{code-block} lua
local target = function
return CGeoPoint(0,0)
end
Leader = task.goCmuRush(target),
```

在这个例子中,我们将`CGeoPoint`类型的参数改为了一个函数,这个函数返回了一个`CGeoPoint`类型的值。这样,我们就可以在运行时动态地改变目标点。例如:

```{code-block} lua
local targetMoveSpeed = 1000 -- mm/s
local target = function()
return CGeoPoint(0,0) + Utils.Polar2Vector(targetMoveSpeed, 0) / param.frameRate * vision:getCycle()
end
...
{
...
Leader = task.goCmuRush(target),
...
},
```

在这个例子中,我们定义了一个`targetMoveSpeed`变量,表示机器人的移动速度。我们在`target`函数中,每次调用时,返回一个新的目标点,这个目标点是当前位置加上一个位移向量。这样,我们就可以实现机器人的动态移动。

对上述代码稍作修改,我们就可以实现机器人的绕球转:

```{code-block} lua
local rotSpeed = math.pi / 2 -- rad/s
local rotRadius = 500 -- mm
local target = function()
return ball.pos() + Utils.Polar2Vector(rotRadius, rotSpeed / param.frameRate * vision:getCycle())
end
...
```
实现的效果如下:

```{thumbnail} ../../img/1_2_1_run_circle.gif
:width: 70%
:align: center
```

完整的脚本代码如下:

```{code-block} lua
:linenos:
local rotSpeed = math.pi / 2 -- rad/s
local rotRadius = 500 -- mm
local target = function()
return ball.pos() + Utils.Polar2Vector(rotRadius, rotSpeed / param.frameRate * vision:getCycle())
end
return {
firstState = "ready",
["ready"] = {
switch = function()
end,
Leader = task.goCmuRush(target),
match = "[L]"
},
name = "TestMyRun",
}
```

:::{note}
对于`task.goCmuRush`函数,可以做到传入值或传入闭包,是因为在`task.goCmuRush`函数内在运行时会根据传入参数类型动态解包,这是一种常见的设计模式。这样的方式在rocos的lua框架中被大量运用以应对更多的动态性需求。在你编写自己的函数时,也可以考虑这种设计模式使其变得更加灵活。
28 changes: 27 additions & 1 deletion doc/posts/1_rocos_basic/1_3_5_play_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,33 @@ require("Zeus")
```
:::

`StartZeus.lua`作为入口,分别加载了`Config/RoleMatch/Zeus`三个模块。在旧版本中,`Config.lua`中使用table完成所有的脚本/cskill的设置工作并在`Zeus.lua`中完成初始化。在新版本中,脚本/cskill的设置工作被自动扫描的方式替代(战术包),`Config.lua`中只保留了一些全局变量的设置。`RoleMatch.lua`中完成了角色匹配的工作,`Zeus.lua`中完成了整个lua框架的初始化工作。
`StartZeus.lua`作为入口,分别加载了`Config/RoleMatch/Zeus`三个模块。在旧版本中,`Config.lua`中使用table完成所有的脚本/cskill的设置工作并在`Zeus.lua`中完成初始化。在新版本中,脚本/cskill的设置工作被自动扫描的方式替代(战术包),`Config.lua`中只保留了一些全局变量的设置。`RoleMatch.lua`中完成了角色匹配的工作,`Zeus.lua`中完成了整个lua框架的初始化工作,这其中值的一提的是对于所有play脚本的初始化。

针对某个脚本的初始化工作,是通过lua的`dofile`函数完成的。`dofile`函数会加载并执行一个lua文件,这个文件中的代码会被执行。我们来分析一个play脚本的初始化工作:

:::{card} TestScript.lua
```{code-block} lua
:linenos:
local xxx = 1 -- 局部变量
gPlayTable.CreatePlay{
firstState = "...",
-- 多个状态
["stateName"] = {
switch = ...,
..., -- 多个需要执行的task
match = ""
},
...
name = "TestRun",
}
```
:::

在脚本的开始,会定义一些在接下来的脚本中用到的局部变量。然后上述代码的第2行到最末行,是一个`gPlayTable.CreatePlay`函数的调用,这个函数会在`gPlayTable`中创建一个play存储在全局的表中。调用函数时会传入一个table,这个table中包含了play的所有信息,例如`firstState``state``switch``match`等。这个函数会返回一个play的名字,这个名字会被用于后续的调用。

:::{admonition} 提示
调用`dofile`函数是为了将一个play脚本的信息存储在`gPlayTable`中,在后续的运行中,我们不会再直接运行这个脚本文件本身了,这也是为什么在`task.xxx()`中我们需要通过闭包的方式传递动态参数。
:::

###### 每帧的具体策略执行

Expand Down
2 changes: 1 addition & 1 deletion doc/posts/1_rocos_basic/1_3_7_error_msg.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# Lua层的常见报错信息[TODO]
# 调试 - 常见报错信息[TODO]

0 comments on commit 6741c74

Please sign in to comment.