写在前面

在足球机器人的场景脚本中,有一个动作是必不可少的,那就是拿球的动作,在比赛开始的时候,拿球的角色需要能够稳定拿到球,然后再传出。

其中有2个难点,其一是图像的误差,因为角色持球的时候图像会有偏差,可能角色在离球一定距离的时候以为自己拿到球了就会停下来了,其实并没有,也有可能会一直拿到球之后推着球走,因为角色认为自己一直都没有拿到球,这样的话是犯规的,因为发球车在球发出前不能带球移动超过特定距离。还有一个难点就是double touch的问题,有时候发球车去拿球的动作没写好,导致发球车只是碰到了一下球之后没有能控制住球导致球滚走了,然后发球车又去抓球了,这样就是double touch,也是属于犯规的,需要交换球权。

从上面可以看出写好角色的拿球动作是多么重要的一件事情,因此我将在这篇博客中分享我是如何设计角色的拿球动作了,为了描述方便,我设置了拿球车是前锋。

思路分析

从上图中我们可以看出,前锋去拿球的时候是会先变动y轴,走到目标点的另一侧,避免去拿球的时候碰到高尔夫球,并且前锋的吸球装置是一直朝着高尔夫球的。当前锋运动到目标点和前锋分别在高尔夫球的两侧的时候,前锋就会走过去吸住球并且在特定的时机将球传出。

程序分析

在这里呢我要介绍一个新的函数,那就是is_ready_pass,这个函数主要是用来判断两条直线之间的角度是否小于某一特定值,具体实现如下:

bool is_ready_pass(const point2f& ball, const point2f& passer, const point2f& receiver){
    float receiver_to_ball = (ball - receiver).angle();
    float ball_to_passer = (passer - ball).angle();
    cout << "拿到球之后的角度大小是  " << (ball - receiver).angle() << "  和 " << (passer - ball).angle() << endl;
    //两个矢量角度之差小于某个值,判断是否可以传球
    bool pass;
    pass = fabs(receiver_to_ball - ball_to_passer) < 0.3;
    return pass;
}

因为前锋走上去拿球的条件就是前锋,高尔夫球和目标点三点共线,所以这个函数就很重要了。介绍完了功能函数之后我们看具体的逻辑实现,在这个运动过程中,首先会根据前锋和高尔夫球的起始相对位置去变动前锋的y轴坐标,从而避免走过去拿球的时候碰到高尔夫球犯规。当前锋运动到高尔夫球的另一侧之后就会走到目标点和高尔夫球的延长线上去准备持球:

namespace{
    float head_len = 7.0;
    //场地实调
#define fast_pass 9
}
if (ball.y>0){//球在y轴正向
    if (runner.y < ball.y){
        task.target_pos.x = runner.x;
        task.target_pos.y = runner.y + get_buff;
    }
    else{
        task.target_pos = ball + Maths::vector2polar(BALL_SIZE / 2 + MAX_ROBOT_SIZE + 15, rece_to_ball);
    }
}
else{//球在y轴负向
    if (runner.y>ball.y){
        task.target_pos.x = runner.x;
        task.target_pos.y = runner.y - get_buff;
    }
    else{
        task.target_pos = ball + Maths::vector2polar(BALL_SIZE / 2 + MAX_ROBOT_SIZE + 15, rece_to_ball);
    }
}
task.orientate = (pos - ball).angle();

前锋,高尔夫球和目标点三点共线之后就需要拿球了,拿球动作设计如下:

if (is_ready_pass(ball, runner, pos)){
    task.target_pos = ball + Maths::vector2polar(fast_pass, rece_to_ball);
    task.orientate = (pos - ball).angle();
    task.needCb = true;
}

总的来说图像对于参数的影响还是比较大的,因此我在这个动作中写了一些需要现场调试的参数,都已经集成到namespace里面了:

namespace{
    float head_len = 7.0;
    //场地实调
#define fast_pass 9
}

fast_pass表示的就是当前锋吸住球的时候,球的中点到前锋中点的距离,这个根据现场吸球效果做微调即可。
还有一个就是前锋拿到球的判断,如何判断前锋是否拿到球了呢?条件有2个,一个是高尔夫球中点到前锋中点小于特定距离(fast_pass ),而且因为球是被前锋吸住的且前锋的吸球装置有一定宽度,因此球中心与前锋中心的连线与前锋朝向所成角度也应该小于特定值,这个也是需要通过现场调试确定的:

float miss = 1.6;
bool get_ball = (ball - runner).length() < get_ball_threshold - miss && (fabs(anglemod(dir - (ball - runner).angle())) < PI / 4);

以上这段程序在我的公众号AIplusX里面的程序资料的robot_soccer文件夹里,具体位置看这个: