写在前面
之前我已经写过了关于这个足球机器人4V4对抗赛的总思路,具体请看以下链接
其中包括了我程序的开源代码,包括task和skill
那么这篇文章我将分享足球机器人对抗赛中normalplay的作用,以及我在代码中关于normalplay的技巧和前锋的实现原理
normalplay的地位
我用一张流程图来表示normalplay在正常足球机器人比赛中的作用吧
整个足球机器人的比赛规则中如果没有发生犯规的话,SOM软件一直执行的就是normalplay脚本 其实,在正常比赛中,因为小型机器人并没有像人一样这么智能,人会自主最大程度上的避免犯规,但是机器不会,所有犯规还是很常见的,甚至可以说在1个小时的比赛中,有半个小时甚至是半个小时以上SOM软件都是在执行犯规脚本,因此normalplay固然重要,但是也要注重其他场景的脚本哦(尤其是角球脚本,这点我会在我的另外一篇博客中进行讲解),接下来我将从task层和skill层来进行normalplay场景的介绍。
Task层
一个Task脚本就是一个有限状态机,Task层可以形象的比喻成策略游戏中的玩家的对抗方法,放一下我的normalplay的Task里面的内容
gPlayTable.CreatePlay{
--该脚本已经转换为比赛脚本
firstState = "initState",
["initState"] = {
switch = function()
if CIsGetBall("Kicker") then
return "initState"
end
end,
Kicker = task.KickerTask("superforward"),
Receiver = task.ReceiverTask("receiverdef"),--预判球运动的位置
Tier = task.TierTask("tierdef"),--预判球运动的位置
Goalie = task.GoalieTask("goalie")
},
name = "normalplay_1"
}
其实看我的normalplay的脚本可以发现这个场景里面我的策略很简单,在这个有限状态机中可以看出这个场景一直都是在一个状态中循环进行。仔细看就是我的前锋(Kicker),中场(Receiver),后卫(Tier),守门员(Goalie)一直在执行一个单一的动作,那么这些动作究竟是什么呢?我画一张图帮助大家理解一下用语言来表述的话就是守门员在禁区内作为防守的最后一道防线,而中场和后卫一直都贴在禁区线的外侧,作为对抗的第二道防线,守门员,中场和后卫都是会随着球然后前锋一直在外面做抢球->射门这个动作,因为守门员,中场和后卫的动作比较类似,所以我会在我的另外一篇博客作集中讲解,所以接下来我主要讲解前锋的skill思路和实现。
Skill层
前锋主要有2个技术要点,1是接球路线,2是接球速度和射门动作,我们一个个来讲
接球路线
首先是接球路线,我先画个示意图首先我们可以获取场上数帧里“足球”(也就是高尔夫球,接下来就用高尔夫球来称呼它),获取到了这些高尔夫球的位置之后,我们可以根据最小二乘法来拟合出这些足球的运行轨迹,最小二乘法的拟合示意图如下,可以看出,最小二乘法其实就是把一些离散的点经过数学方法进行拟合之后,找到一条离这些点距离误差最小的直线,因为高尔夫球在场上的运动也是直线的,所以这也就是高尔夫球的运行路径那我们得到这个运行路径有什么用呢?再看一下上面那个画了前锋的图,我们已经获得了小球的预期运行轨迹,并且可以根据图像帧与帧之间的时间差来得到小球运动的方向,那么现在我们得到了小球的运行轨迹和方向,接下来我们只需要把前锋的目标位置设定到小球运行的轨迹上而且将持球装置朝向小球来的方向就可以啦~接下来我来做一下skill代码的讲解。
代码讲解
这2个函数是为了求出直线y=kx+b里的k,b参数的
接球速度和射门动作
经过实际测试发现,足球机器人在快要碰到球的时候会有一个减速的动作,经过后来与机器人公司的工作人员沟通了之后确认是下载到板子上的程序限定了这个动作,是我们用户改变不了的。但是我们团队一名同学发现了当把足球机器人的目标点设定到实际目标点前面一段距离的时候,足球机器人的运行速度会得到质的提升! 拿到球了之后是射门动作,这个射门动作也比较简单,前锋在去拿球的过程中会一直判断是否拿到了球,如果拿到了球就执行射门动作,射门动作是这样的,持球之后将足球机器人的朝向变为敌方球门,当足球机器人转到敌方球门中点和持球装置朝向几乎相同时就进行射门,我画一个示意图
代码讲解
这个是我前锋skill的主要函数
PlayerTask player_plan(const WorldModel* model, int robot_id){
PlayerTask task;
const float& dir = model->get_our_player_dir(robot_id);
const point2f& vel = model->get_ball_vel();
const point2f& ball = model->get_ball_pos();
const point2f& last_ball = model->get_ball_pos(1);
const point2f& runner = model->get_our_player_pos(robot_id);
const point2f opp_right(300,25);
const point2f opp_left(300, -25);
point2f opp_goal = -FieldPoint::Goal_Center_Point;
const point2f& goalie_pos = model->get_our_player_pos(model->get_our_goalie());
point2f temp(0, 0);
vector ball_points;
float b_interrupt(0);
float k_slope(0);
float angle (0);//小球运动的角度
bool ismove = (ball - last_ball).length() > ball_movingdist ? true : false;
float r2b_dist = (runner - ball).length();
float change_track = (ball-last_ball).length();
get_k_b(model, b_interrupt, k_slope, ball_points);
get_angle(angle , k_slope, vel);
//get_cross(temp, runner, b_interrupt, k_slope);
if (is_getball(ball,runner, dir) ){
if (dir > (opp_left-runner).angle() && dir < (opp_right-runner).angle()){
task.needKick = true;
task.kickPower = 127;
}
task.target_pos = runner + Maths::vector2polar(runcircle, dir);
task.orientate = (opp_goal-runner).angle();
}
else{
if (ismove){
if (is_ready_pass(ball, runner, opp_goal,angle) && r2b_dist < 60 ){
task.target_pos = runner;
task.orientate = (ball-runner).angle();
}
else{
task.target_pos = ball + Maths::vector2polar(change_track*magnification, angle);
task.orientate = (ball - runner).angle();
}
}
else{
task.orientate = (ball - runner).angle();
task.target_pos = ball;
}
}
if (r2b_dist<50){
task.needCb = true;
}
return task;
}
下面这个函数是一个功能函数,用来判断在这帧图像里面,球是否在足球机器人朝向的延长线上,示意图如下,在接球,追球,守门员等很多地方都可以用到
bool is_getball(const point2f& ball, const point2f& runner, const float& dir){
//miss参数需要在比赛时实地调试
cout << "进入is_getball的判断" << endl;
bool get_ball = (ball - runner).length() < miss && (fabs(anglemod(dir - (ball - runner).angle())) < getangle);
cout << "前锋里的(ball - runner).length() " << (ball - runner).length() << endl;
cout << "前锋里的fabs(anglemod(dir - (ball - runner).angle()) " << fabs(anglemod(dir - (ball - runner).angle())) << endl;
if (get_ball){
return true;
}
else{
return false;
}
}
在该skill主要函数里面,这个条件判断是一直在运行的,所以要离开追球的状态就需要拿到球,所以在这个if判断下面就是拿球之后的操作啦
下面这个就是追球的具体动作设计了(“嘴”就是小车的持球装置)
(づ ̄3 ̄)づ╭❤~一键三连,这次一定(๑•̀ㅂ•́)و✧