写在前面

在小型足球机器人的比赛中,因为足球机器人并不会像人一样那么智能,所以球出界是非常正常的事情。球出界之后就是进行罚球,因此罚球脚本就显得非常重要了,在所有的发球场景中,角球又是最容易进球的一个场景,因此我将分析旋转角球的task层和skill层实现。

自然语言描述

如上图所示,中场会在上半边的场内绕某点做公转的同时进行自转的操作,前锋会过去拿球,前锋拿到球过一段时间之后会将高尔夫球挑射到对面去,在前锋挑球之后,中场会根据最小二乘法拟合出来的直线去接球,接到球之后会直接射门。

Task层实现

我是基于有限状态机实现的,先放一下我的Lua程序:

gPlayTable.CreatePlay{
firstState = "GetBall",
--该场景已转换为比赛脚本
--转圆圈甩人
["GetBall"] = {
    switch = function()--2.5张/秒
        if Cbuf_cnt(CIsGetBall("Kicker"),450)  then -- 改变这个时间可以控制跑位的时间,一定要在8s之内
            return "delay"
        end
    end,
    Receiver   = task.ReceiverTask("runcircle"),
    Tier = task.TierTask("def"),
    Kicker = task.KickerTask("GetBall_Front"),--拿球加上传
    Goalie   = task.GoalieTask("goalie")
},
 
["delay"] = {
    switch = function()
        if CIsBallKick("Kicker") then -- 延迟时间由测试得出
            return "PassBall"
        end
    end,
    Receiver = task.ReceiverTask("runcircle"),
    Kicker = task.KickerTask("cpkick_c1"),--拿球加上传球
    Tier = task.TierTask("def"),
    Goalie   = task.GoalieTask("goalie")
},
 
["PassBall"] = {
    switch = function()
        if  CIsGetBall("Receiver")  then --CIsGetBall("Kicker")
            return "Shoot"
        end
    end,
    Receiver  = task.ReceiverTask("GoRecPos_Front"),--接球的朝向为射门点
    Kicker = task.GotoPos("Kicker",150,0,0),
    Tier = task.TierTask("def"),
    Goalie   = task.GoalieTask("goalie")
},
 
["Shoot"] = {
    switch = function()
        if CIsBallKick("Receiver") then
            return "finish"
        end
    end,
    Receiver = task.Shoot("Receiver"),--该射门为拿球转向
    Kicker = task.GotoPos("Kicker",150,0,0),
    Tier = task.TierTask("def"),
    Goalie   = task.GoalieTask("goalie")
},
name = "connerkick_3"
}

整个Task层就是按照拿球->延时->传球->射门的顺序进行,在这个场景的动作中,守门员和后卫都在执行防守任务,因此不做特殊分析,主要来看前锋和中场的动作。

在Lua层动作的调用中,采用Kicker = task.KickerTask("GetBall_Front")的格式来调用,等号左边是需要控制的足球机器人的角色名称,右边task.KickerTask()是固定的,括号里面放的是C++底层写的动作的名字,这样一个语句的意思就是让Kicker去执行GetBall_Front动作。

言归正传,中场执行的是转圈和射门的动作,所以他调用的脚本就是runcircle和GoRecPos_Front,前锋执行的是拿球和挑球的动作,因此他调用的脚本就是GetBall_Front和cpkick_c1。

Skill层实现

接下来看中场转圈动作分析,我们确实一个中场转圈的中点,然后让中场绕该点做半径一定的旋转即完成了公转,那么如何实现自转呢?

这个也是很简单啦,开始时以对面球门中点和中场运动起始点连线为初始0度,随着循环调用的进行,每次都给中场一个增加的角度,从而实现了中场的自传,具体程序实现看下面:

#include "utils\maths.h"
 
//用户注意;接口需要如下声明
extern "C"_declspec(dllexport) PlayerTask player_plan(const WorldModel* model, int robot_id);
 
PlayerTask player_plan(const WorldModel* model, int robot_id){
    PlayerTask task;
    const point2f& ball = model->get_ball_pos();      //球的坐标
    const point2f& runner = model->get_our_player_pos(robot_id); //球员的坐标
    const point2f& goal = FieldPoint::Goal_Center_Point;//我方球门的左边
    const point2f& opp_goal = -FieldPoint::Goal_Center_Point;//对方球门的坐标
    const float& dir = model->get_our_player_dir(robot_id);//获取我方球员的朝向
 
    int  convert = ball.y > 0 ? -1 : 1;
 
    /****************************/
    //下面三个参数由测试得出
    point2f pos(210, 105);
    pos.y *= convert;
    float circleR = 18;//中场公转的半径
    float DetAngle = 0.9;//中场公转的变化角度
    float dir_angle = 0.7;//中场自传的变化角度
    /****************************/
 
    task.target_pos = pos + Maths::vector2polar(circleR, (runner - pos).angle() + convert*DetAngle);//实现改变中场的公转位置
 
    task.orientate = dir + convert*dir_angle;//实现中场自转角度的改变
 
    task.flag = 1;
    return task;
}

其中pos(210, 105)就是中场公转的中点,convert参数是为了区别左右半场。