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.cppMyTouch.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.hMyTouch.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,并且重载了plantoStream两个函数。

  • 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函数进行注册创建,返回executematchPos两个函数。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测试上层的目标点对任务执行的影响。

参考资料: