BHuman2019之skills理论介绍

skill 详解

我们把行为分为两层,一层是决策层,这一层决定机器人应该干什么,在这一层中相较于以前的代码风格是很容易添加或者去除决策的;另一层是行为实现层,这一层实现了机器人具体怎么完成一个行为。这就是我们新的行为框架的基础,我们把这种框架叫做 Skills 和 Cards 系统,我们下面详细讲解一下 Skill

一个 skill 是我们在决策层需要使用到的机器人行为,一个 skill 执行了一个具体的任务(比如 GetUpEngine.cpp 实现了站起来这个动作,相应的我们可以在决策层中设置判断条件以决定是否要执行这个动作即是否要调用这个 skill),调用者(即决策层,即 card)可以传递参数给 skill(我们在决策层必然要调用这些已经实现了的行为,然后再加上一些判断条件从而形成决策),skill 也可以调用skill,我们可以通过这样的方式把行为分解成子行为。此外,同一个 skill 还可以有多个实现体,但无论如何 skill 还是只有一个实现体有效,意思就是刷了代码之后,这个有多个实现体的 skill 只会执行一个指定的实现体,不会在这几个实现体中来回切换,这个指定的实现体需要在 skills.cfg 中指出,但是这个基本用不到,具体不再说。

skill 在代码上分为两部分①skill 接口②skill 执行体的接口③skill 的执行体④一个单独的语句

skill 接口给出了类似于函数前向引用声明的东西,通过这个接口,我们就知道其他 skill 或 card 怎么调用这个 skill 了。任何一个 skill 的接口都被定义在skills.h 这一个头文件里的叫 Skills 的命名空间里。skill 接口的定义使用的是SKILL_INTERFACE 这个语句(其实是宏)第一个参数是 skill 的名字,其他更多的参数是可选的,这些可选的参数指定了 skill 需要的参数(其实相当于函数的参数),其他可选的参数的格式是:(数据类型)(初始化)(参数名),如: (float) (10000.f) (length ) ; 当然也可以不要初始化,比如: (KickType) kickType。这个宏实际上用 skill 的名字和这些参数声明了一个结构体,这个结构体的自定义数据类型的名字就是 skill 的名字,后边的参数都是这个 struct 的数据成员。此外会有一个叫做Skill 的类被派生出来,这个类用来将外部的调用命令传送给 skill 的执行体。比如在 skills.h 中写的一个 skill 接口如下:

SKILL_INTERFACE(PassTarget, (int) passTarget, (const Vector2f&) (Vector2f::Zero()) ballTarget); 

它实际上是定义了一个这个东西:

 struct PassTarget   
{   
   int passTarget;   
   const Vector2f& ballTarget=Vector2f::Zero();   
};  

skill 执行体的接口类似于 module 的接口,skill 执行体的接口也基本上创建了一个基类,这个基类的名字是<skill 执行体的名字>Base,skill 执行体也是继承于这个基类的一个派生类,skill 执行体接口SKILL_IMPLEMENTATION 语句,后边的参数是这个执行体的名字,用对应 skill 的名字加上 Impl,(注意这里可没有把这个skill 需要的参数也写在这里),比如 PassTarget 这个skill,我们在skills.h中可以找到这个 skill 的接口是:

SKILL_INTERFACE(PassTarget, (int) passTarget, (const Vector2f&) (Vector2f::Zero()) ballTarget);  

然后在相应的 PassTarget.cpp 中可以看到这个 skill 执行体的接口是:

 SKILL_IMPLEMENTATION(PassTargetImpl,   
 {
   IMPLEMENTS(PassTarget),   
   REQUIRES(LibCheck),   
   MODIFIES(BehaviorStatus),   
 });   

一个 skill 执行体接口中可以有多个 IMPLEMENTS,意思就是说多个不同的 skill可以被一个 skill 执行体实现,相应的在执行体中也会有多个重载 execute 函数,它们的参数不一样,如 KeyFrameArms.cpp

skill 的执行体就是一个继承于刚才所说的基类的一个派生类,它的名字就是skill 执行体的名字,那么对于它继承于基类的五个虚函数和一个纯虚函数来说,如果要在派生类中(也就是执行体中)实现这些函数的话,每一个函数都需要对这个 skill 接口(就是我们上边讲的在 skills.h 中加入的 skills 接口,它就是一个结构体 struct)有一个常引用,比如刚才展示的那个 skill:PassTarget 的执行体如下:

 class PassTargetImpl : public PassTargetImplBase   
 {   
   void execute(const PassTarget& p) override   
   {   
     theBehaviorStatus.passTarget = p.passTarget;   
     theBehaviorStatus.shootingTo = p.ballTarget;   
     theLibCheck.inc(LibCheck::passTarget);   
   }   
 };   

我们可以看到 execute 函数的参数列表是对这个 skill 接口(一个结构体)的常引用,类似的其他的几个函数也必须这样,这样的话我们就可以让这些函数获得调用这个 skill 时给 skill 传递的参数,同时在一个 skill 执行体实现多个 skill 的情况下,不同的重载 execute 函数的参数对应各自要实现的 skill(这个情况可以看HeadControl.cpp)。

具体的方法比如 execute 这个函数:用这个参数名 p+.+调用者传递给 PassTarget 这个 skill 的参数 passTarget(或 ballTarget),就如同代码中的 p.passTarget 和 p.ballTarget,正是因为 skill 接口是一个结构体,我们才会用“.”的方式去引用调用者传递给 skill 的参数,我们写 skill 基本就是只用到两个函数,一个是 execute 另一个是 isdone。skill 执行体不过就是个类罢了,里边当然也可以按照 C++的语法写入其他的成员函数和数据成员。

isDone函数返回值是bool,通常用来给调用者汇报这个skill是否已经完成,可以参照 GetUpEngine.cpp 中的 isdone 函数进行学习,返回值为 true 就是 skill已经完成了,为 false 就是还没有完成,当然这个函数也可以被重载,如果一个skill 执行体实现了好几个 skill,那么这个isDone 函数就得被重载,也对应写多个,如 KeyFrameArms.cpp

isAborted 函数返回值是 bool,通常用来给调用者汇报这个 skill 是否发生异常,这个基本用不到。 execute 函数就是实现相应 skill 的函数,具体 skill 怎么实现,都写在了这个函数中,无返回值,注意参数的常引用。

preprocess、postprocess 函数在开源的 skill 中并没有找到例子,并且也不常用,pdf给的信息只有一句而且很难理解every frame(每帧)的含义,故无法讨论。

reset 函数虽然在开源 skill 中找得到,但仍由于很难理解 every frame(每帧)的含义且我们写代码几乎不用,故无法讨论。

最后就是一句陈述,这个跟 module 中的那个允许 module 可以被实例化的句子类似,也是一个很简单句子,使用 MAKE_SKILL_IMPLEMENTATION(skill 执行体名字);比如:

MAKE_SKILL_IMPLEMENTATION(GoToBallAndKickImpl);   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值