Play - 让机器人动态响应¶
在上一节中,我们了解了如何创建一个基本的play让机器人两点运动,你发现了吗?在这个play中,机器人的行为是固定的,很难做出一些高动态的行为,例如绕球转。在这一节中,我们学习如何添加动态参数让task变得更灵活,也会学习到在书写play时至关重要的概念 - 闭包。
闭包¶
在学习闭包之前,我们先来看一个例子:
1function f(num)
2 local i = 0
3 return function()
4 i = i + num
5 return i
6 end
7end
8
9local g,h = f(2),f(3)
10print(g(),h()) -- 2 3
11print(g()) -- 4
12print(g(),h()) -- 6 6
在这个例子中,我们定义了一个函数f,它接受一个参数num,返回一个函数。这个函数内部定义了一个局部变量i,并返回一个匿名函数。这个匿名函数每次调用时,i的值会增加num。我们可以看到,g和h是两个不同的闭包,局部变量之间不会相互影响。
闭包是一种函数,它可以访问其词法范围内的变量。闭包是一种非常强大的工具,可以用来实现许多功能,例如:函数工厂、延迟计算、状态保持等。
动态参数¶
我们截取上节代码中的一行来观察:
Leader = task.goCmuRush(CGeoPoint(0,0)),
在这行代码中,我们调用了task.goCmuRush函数,传入了一个CGeoPoint类型的参数。这个参数是固定的,我们无法在运行时改变它。如果我们想要让机器人在运行时动态地改变目标点,我们可以使用闭包来实现。
我们可以将上面的代码改写为:
local target = function
return CGeoPoint(0,0)
end
Leader = task.goCmuRush(target),
在这个例子中,我们将CGeoPoint类型的参数改为了一个函数,这个函数返回了一个CGeoPoint类型的值。这样,我们就可以在运行时动态地改变目标点。例如:
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函数中,每次调用时,返回一个新的目标点,这个目标点是当前位置加上一个位移向量。这样,我们就可以实现机器人的动态移动。
对上述代码稍作修改,我们就可以实现机器人的绕球转:
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
...
实现的效果如下:
完整的脚本代码如下:
1local rotSpeed = math.pi / 2 -- rad/s
2local rotRadius = 500 -- mm
3local target = function()
4 return ball.pos() + Utils.Polar2Vector(rotRadius, rotSpeed / param.frameRate * vision:getCycle())
5end
6
7return {
8 firstState = "ready",
9
10 ["ready"] = {
11 switch = function()
12 end,
13 Leader = task.goCmuRush(target),
14 match = "[L]"
15 },
16 name = "TestMyRun",
17}
备注
对于task.goCmuRush函数,可以做到传入值或传入闭包,是因为在task.goCmuRush函数内在运行时会根据传入参数类型动态解包,这是一种常见的设计模式。这样的方式在rocos的lua框架中被大量运用以应对更多的动态性需求。在你编写自己的函数时,也可以考虑这种设计模式使其变得更加灵活。