diff --git a/doc/img/1_2_1_run_circle.gif b/doc/img/1_2_1_run_circle.gif new file mode 100644 index 0000000..957e91b Binary files /dev/null and b/doc/img/1_2_1_run_circle.gif differ diff --git a/doc/posts/1_rocos_basic/1_2_simple_play.md b/doc/posts/1_rocos_basic/1_2_0_simple_play.md similarity index 100% rename from doc/posts/1_rocos_basic/1_2_simple_play.md rename to doc/posts/1_rocos_basic/1_2_0_simple_play.md diff --git a/doc/posts/1_rocos_basic/1_2_1_closure_params.md b/doc/posts/1_rocos_basic/1_2_1_closure_params.md new file mode 100644 index 0000000..6dc0ca1 --- /dev/null +++ b/doc/posts/1_rocos_basic/1_2_1_closure_params.md @@ -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框架中被大量运用以应对更多的动态性需求。在你编写自己的函数时,也可以考虑这种设计模式使其变得更加灵活。 \ No newline at end of file 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 index ef15655..87497e9 100644 --- a/doc/posts/1_rocos_basic/1_3_5_play_create.md +++ b/doc/posts/1_rocos_basic/1_3_5_play_create.md @@ -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()`中我们需要通过闭包的方式传递动态参数。 +::: ###### 每帧的具体策略执行 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 index d907a81..5fe7f40 100644 --- a/doc/posts/1_rocos_basic/1_3_7_error_msg.md +++ b/doc/posts/1_rocos_basic/1_3_7_error_msg.md @@ -1 +1 @@ -# Lua层的常见报错信息[TODO] +# 调试 - 常见报错信息[TODO]