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
测试上层的目标点对任务执行的影响。