Skill - 创建一个新的技能¶
在之前的内容中,我们尝试过在lua层实现一个简单的Skill。在本结中,我们继续将以截球射门的
Touch技能为例,讲解如何在C++中写单体技能。
创建一个空的Skill¶
在v0.1版本中,我们将之前繁琐的Skill创建流程通过一定的自动化方法进行了简化,并加入了战术包的概念,你可以在Core/目录下创建属于自己的战术包,在无须改进其他编译文件或代码的情况下任意增加,删除,移动等。如果你尝试过在之前自己在rocos的c++层写过Skill,那恭喜你,接下来的内容会让你十分舒适。如果你对如何简化感兴趣,可以参考[1]。
首先,如果你在Core/目录下还没有创建过自己的战术包,可以创建一个文件夹,作为自己的战术包,例如我们创建一个叫做my_tactic_2024的战术包。在my_tactic_2024/目录下创建一个skill文件夹,并在skill下创建MyTouch.h,MyTouch.cpp和MyTouch.lua三个空文件。
mkdir -p Core/my_tactic_2024/skill # 创建战术包文件夹
cd Core/my_tactic_2024/skill # 进入战术包文件夹
touch MyTouch.h MyTouch.cpp MyTouch.lua # 创建空的skill文件
警告
由于所有的战术包都会共用一个环境,所以切记不管是在c++还是在lua层,都不可以出现重名。
备注
如果有战术包不想启用,可以在Core/CMakeLists.txt中在list(REMOVE_ITEM TACTIC_PACKS...这一行将需要忽略的文件夹名字加入在src之后。
在创建之后,你可能会得到如下的目录结构:
Core
└── my_tactic_2024
│ └── skill
│ ├── MyTouch.cpp
│ ├── MyTouch.h
│ └── MyTouch.lua
├── src
└── tactics
└── skill
此时执行cmake,在终端可以查找到如下输出
-- rocos - tactics_package: 'my_tactic_2024'
-- rocos - tactics_package: 'my_tactic_2024' - source: 'MyTouch.cpp'
这表示编译环境识别到了战术包my_tactic_2024,并且识别到了MyTouch.cpp文件。接下来我们就可以在MyTouch.h和MyTouch.cpp中写我们的技能了。
我们在上述三个文件中添加如下内容:
1#pragma once
2#include "skill_registry.h"
3
4class MyTouch : public Skill{
5public:
6 MyTouch() = default;
7 virtual void plan(const CVisionModule* pVision) override;
8 virtual void toStream(std::ostream& os) const override{ os << "MyTouch"; }
9};
10
11REGISTER_SKILL(MyTouch, MyTouch);
1#include "MyTouch.h"
2void MyTouch::plan(const CVisionModule* pVision){
3 setSubTask("SmartGoto",task());
4 Skill::plan(pVision);
5}
1function MyTouch(task)
2 matchPos = function()
3 return ball.pos()
4 end
5
6 execute = function(runner)
7 task_param = TaskT:new_local()
8 task_param.executor = runner
9 task_param.player.pos = CGeoPoint:new_local(0,0)
10 return skillapi:run("MyTouch", task_param)
11 end
12
13 return execute, matchPos
14end
15
16gSkillTable.CreateSkill{
17 name = "MyTouch",
18 execute = function (self)
19 print("This is in skill"..self.name)
20 end
21}
这样我们就完成了一个空的技能,可以编译通过。此时在终端启动Core,就可以看到注册Skill的多了MyTouch这个技能。
Skill的编号可能有所不同,但这不影响我们的使用。
...
Tactic Packages : my_tactic_2024,tactics
Init TPs Skill : ../Core/my_tactic_2024/skill/MyTouch.lua
...
Registry Skill Size : 8
...
2 SkillName : MyTouch
...
让我们来解释一下上面添加的代码:
MyTouch继承自Skill,并且重载了plan和toStream两个函数。REGISTER_SKILL是一个宏定义,用于将MyTouch以第一个参数作为技能名字注册到技能插件系统中,后续无论在lua层或者是其他skill通过subTask调用时,都可以通过技能名字"MyTouch"来调用。在
MyTouch.cpp中,我们实现了plan函数,这个函数是技能的主要逻辑,我们在这里调用了setSubTask函数,这个函数是Skill类的成员函数,用于设置技能的子任务。在刚才的代码中,我们添加了一个SmartGoto的子任务,并给了一个未更新的任务参数,这会让机器人移动到由上层传入的目标点。每个Skill在一个时刻只能由一个子任务,如果设置新的子任务,会将旧的子任务覆盖掉。由于
Skill本身是一个链式调用,所以我们在plan函数中调用了Skill::plan,这个函数会调用setSubTask设置的子任务。阅读代码会发现,除了Goto以外的所有技能,只要在plan函数中调用了setSubTask,都会在plan函数结尾调用Skill::plan确保子任务也会规划执行。在调用setSubTask时,如果传入任务参数TaskT时需要重新构建,切记executor(执行的机器人编号)需要保持一致。
在
MyTouch.lua中,我们定义了一个MyTouch函数并调用了gSkillTable.CreateSkill函数进行注册创建,返回execute和matchPos两个函数。execute函数用于执行技能,matchPos函数返回用于动态匹配的目标点。在execute函数中,我们通过skillapi:run函数调用了c++层的MyTouch技能了。
在进一步修改MyTouch之前,让我们先编写简单的脚本调用当前的MyTouch技能。 由于我们还没有实现对应的lua:task.lua层,所以先用一个简单的状态来代替一下:
1["testSkill"] = {
2 switch = function()
3 end,
4 Leader = {MyTouch{}},
5 match = "[L]"
6},
启动代码可以看到有一个动态匹配的机器人移动到了(0,0),你可以修改MyTouch.lua-line:9测试上层的目标点对任务执行的影响。