1. 概述
在前东家参加了一个robocup2d的比赛,感兴趣的可以搜索一下这个比赛。主要是通过代码控制二维足球员踢球对抗。这篇文章记录一下比赛中的资料和感悟。
2. 详述
2.1. 关键问题
- 无球队员的跑位
- 截球问题
- 射门技术
- 铲球模型
- 动作决策搜索
2.2. 仿真模型
2.2.1. 感知模型
- 听觉模型:接受球员、教练喊话,或裁判发送的信息。
- 视觉模型:视觉范围和视觉质量。
- 身体感知模型:球员自身(位置、速度、身体方向等)。
2.2.2. 运动模型
- 全局坐标系:以球场中心为(0,0),右边为x轴正方向,长度为52.5;下边为y轴正方向,长度为34.0。
- 球员坐标系:以身体方向为y轴正方向。
2.2.3. 动作模型和体能模型
动作包括互斥动作和非互斥动作。
- 互斥动作包括:Kick、Dash、Turn、Tackle、Catch、Move
- 非互斥动作包括:Say、AttentionTo、PointTo、TurnNeck、ChangeView、Score、SenseBody
2.2.4. 球员异构模型
18种异构类型,每场server随机11种
2.2.5. 裁判模型
判断出界、角球、越位等情形的判断
- Before_kick_off 比赛开始之前
- Play_on 比赛进行中
- Time_over 比赛结束
- Kick_off_side 声明比赛开始
- Kick_in_side
- Free_kick
- Corner_kick_side
- CGoal_kick_side
- Drop_ball
- Offside_side
- Goal_side_n
- Foul_side
- Goalie_catch_ball_side
- Back_pass_side
- Free_kick_fault_side
- Time_up_without_a_team
- Half_time
- Time_up
- Time_extented
3. 阵型设计和跑位
3.1. 阵型建模方法
UVA的阵型因子建模、agent2d的Delaunay三角剖分法
3.2. 阵型参数
- Formation_id 阵型编号
- Player_num 球员编号,1~11
- Player_role 球员角色
- Player_x 球员初始状态x坐标
- Player_y 球员初始状态y坐标
- Attr_x 球对球员x坐标的吸引因子
- Attr_y 球对球员y坐标的吸引因子
- Behind_ball 是否限制球员位于球的后方
- X_min 球员x坐标的最小值
- X_max 球员x坐标的最大值
3.3. 跑位点
1 2 3
| x=min(max(Player_x+(Ball_x*Attr_x), X_min), X_max) if Behind_ball=True Then x=min(x,Ball_x) y=Player_y+(Ball_y*Attr_y)
|
其中(Ball_x, Ball_y)表示球的位置
4. 代码解读
4.1. librcsc
1 2 3 4 5 6 7 8 9 10 11 12
| rcsc/action 动作类(重点) rcsc/ann 人工神经网络类 rcsc/coach 在线教练类 rcsc/common 公共的类 rcsc/formation 一些阵型类(agent2d只使用了DT跑位) rcsc/geom 一些几何类 rcsc/net 一些与server交换数据的类 rcsc/param 一些参数类 rcsc/player 一些球员类(重点) rcsc/time 时间类(一般用不到) rcsc/trainer 一些离线教练类 rcsc/util game_mode math version
|
4.2. src
- 以bhv开头的都是在各种情况下的动作执行类(重要)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| bhv_basic_move 球员基本跑位 bhv_basic_offensive_kick 基本进攻踢球 bhv_basic_tackle 阻截对手的球(铲球) bhv_custom_before_kick_off 开球前的习惯(模式) bhv_go_to_static_ball 跑向静态球 bhv_goalie_basic_move 守门员基本跑位 bhv_goalie_chase_ball 守门员追球 bhv_goalie_free_kick 守门员任意球 bhv_penalty_kick 罚球 bhv_prepare_set_play_kick 准备踢比赛 bhv_set_play_free_kick 设置任意球 bhv_set_play_goal_kick 设置踢球门球 bhv_set_play_indirect_free_kick 设置打间接任意球 bhv_set_play_kick_in 设置踢界外球 bhv_set_play_kick_off 设置开球 bhv_their_goal_kick_move 对手踢球跑位
|
- 以role开头的都是角色类
- 以sample开头的都是示例,可以模仿其结构修改代码
- 以intertion开头的是意图类
- 以neck开头的是转脖子动作
保存了一些阵型的数据
4.4. chain_action
agent2d底层的决策核心
4.5. 球员角色分配
具体代码在“ROLE_角色名”
中查看;具体角色划分在strategy.cpp里面;异构球员的顺序在sample_coach.cpp
里面;具体的阵型角色文件通常以Begin Roles开始,以End Roles结束。
1 2 3 4 5 6
| CenterForward 中锋 (11号) SideForward 边锋 (9,10号) SideBack 边后卫 (2,5号) CenterBack 中后卫 (3,4号) DefensiveHalf 防守型中场 (6号) OffensiveHalf 进攻型中场 (7,8号)
|
4.6. 代码执行流程
- 由Main()函数开始
- 首先是一些环境变量设置,启动球员类。
- 进入BasicClient类中,执行Run()函数。
- RunOnline()调用PlayerAgent类的 HandleMessage()函数处理获得的信息。
- HandleMessage()函数调用在PlayerAgent类中的Action()函数进行动作决策和Server参数的解析parse()函数。
- 在Action()函数中依次执行ActionImpl()函数,DoArmAction()函数,DoViewAction()函数,DoNeckAction()函数以及CommunicationImpl()函数。
- 其中ActionImpl()函数是主要的决策函数的框架。基于球员在场上的角色(Role)以及场上位置(Home Position),执行相应的Role策略,这种基于角色的策略增加了球员的灵活性,使不同类型的球员具有不同的策略,对于球场动态环境具有更强的自适应性。
5. 示例
(1)bhv_basic_move.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
|
bool Bhv_BasicMove::execute( PlayerAgent * agent ) {
dlog.addText( Logger::TEAM, __FILE__": Bhv_BasicMove" ); if ( Bhv_BasicTackle( 0.8, 80.0 ).execute( agent ) ) { return true; } const WorldModel & wm = agent->world(); const int self_min = wm.interceptTable()->selfReachCycle(); const int mate_min = wm.interceptTable()->teammateReachCycle(); const int opp_min = wm.interceptTable()->opponentReachCycle(); if ( ! wm.existKickableTeammate() && ( self_min <= 3 || ( self_min <= mate_min && self_min < opp_min + 3 ) ) ) { dlog.addText( Logger::TEAM, __FILE__": intercept" ); Body_Intercept().execute( agent ); agent->setNeckAction( new Neck_OffensiveInterceptNeck() ); return true; } const Vector2D target_point = Strategy::i().getPosition( wm.self().unum() ); const double dash_power = Strategy::get_normal_dash_power( wm ); double dist_thr = wm.ball().distFromSelf() * 0.1; if ( dist_thr < 1.0 ) dist_thr = 1.0; dlog.addText( Logger::TEAM, __FILE__": Bhv_BasicMove target=(%.1f %.1f) dist_thr=%.2f", target_point.x, target_point.y, dist_thr ); agent->debugClient().addMessage( "BasicMove%.0f", dash_power ); agent->debugClient().setTarget( target_point ); agent->debugClient().addCircle( target_point, dist_thr ); if ( ! Body_GoToPoint( target_point, dist_thr, dash_power ).execute( agent ) ) { Body_TurnToBall().execute( agent ); } if ( wm.existKickableOpponent() && wm.ball().distFromSelf() < 18.0 ) { agent->setNeckAction( new Neck_TurnToBall() ); } else { agent->setNeckAction( new Neck_TurnToBallOrScan() ); } return true; }
|
(2)一些类和函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| 类 BallObject Ball; 球类 AgentObject agentObject; agent类 PlayerObject Teammates[MAX_TEAMMATES]; 球员类,三个数组存放不同的球员 PlayerObject Opponents[MAX_OPPONENTS]; PlayerObject UnknownPlayers[MAX_TEAMMATES+MAX_OPPONENTS]; Time 时间类(int类型属性:周期数,循环次数) Circle 圆类(圆心,半径) Line 直线类(包含三个double属性,分别是直线ay + bx + c = 0的三个系数) VecPosition 坐标信息类(包含两个double属性,分别对应x轴和y轴的坐标值) ObjectT 枚举类,包括场上可能出现的对象,例如OBJECT_BALL,OBJECT_GOAL_L,球,左侧球门等等
函数 可通过WM-> 调用函数 (1)球的坐标位置 getGlobalPosition( ObjectT o ) 获取o的坐标(返回类型VecPosition) getBallPos() 获取球的坐标(通过调用getGlobalPosition间接完成) getBallPos().getX() 获取球的x轴坐标 getBallPos().getY() 获取球的y轴坐标(均为double类型) getPosOwnGoal() 获取自己的球门位置(返回VecPosition) getPosOpponentGoal( ) 获取对手球门的位置(返回VecPosition)
(2)球的速度,方向 getGlobalVelocity( ObjectT o ) 获取o的速度(返回类型VecPosition) getBallSpeed() 获取球的速度(返回值为double类型,通过调用getGlobalVelocity()间接完成) getBallDirection()获取球的运动方向(返回AngDeg(弧度角),也就是double)
(3)球员,场上的状态 getPlayerNumber() const 获取球员的号码(返回int) getPlayMode() 获取状态 PlayModeT 枚举类,包含不同的状态,例如PM_BEFORE_KICK_OFF,PM_KICK_OFF_LEFT getClosestInSetTo这是一个重载函数,根据参数的不同功能的实现也不同
(4)距离,角度 getPosClosestOpponentTo( double *dDist, ObjectT o ) (返回VecPosition) getRelAngleOpponentGoal() 获取自己与对方球门之间的相对角(返回double) getRelativeDistance( ObjectT o )获取自己与o之间的相对距离(返回double) getRelDistanceOpponentGoal() 获取自己与对方球门之间的相对距离(返回double)
(5)判断 isBeforeKickOff( PlayModeT pm ) 判断是否在开球之前(返回bool) isKickOffUs( PlayModeT pm ) 判断当前的play模式下是否由我方开球 isOffsideUs( PlayModeT pm ) 判断当前的play模式下我方是否越位(返回bool) isBallKickable() 判断球是否可踢(返回bool) isBallCatchable() 判断球是否可抓(返回bool,该方法只适用于守门员) isBallHeadingToGoal( )判断当前球是否朝向我方球门(返回bool) isBallInOurPossesion( ) 判断球是否在我方手中(返回bool) isBeforeGoal( VecPosition pos )判断位置参数pos是否在对方球门前(返回bool)
(6)周期 getCurrentTime() 获取当前的时间(返回Time对象) getCurrentCycle() 获取当前的周期(返回int)
|
6. 相关开源项目和开发人员
7. 修改方向
- 改阵型,配合fedit2来调整
- 动作链
- 无球跑位策略
- 评估器,难度最大
- 角色
- 其他:原子动作、行为策略、教练
8. 参考文章
9. 总结
比赛给了大家一个月的时间来学习、修改代码,由于种种原因,我们组在前期还取得不错的成绩,但是后期却掉落下来。我们当时大多数的组采用的都是暴力调参,实战对抗,毫无悬念最后也只能在一定范围内提升。动手去修改算法的组还是比较少的。不过有兄弟组采用RL search的方式,使用机器学习模型替代手工调参,最后的确搜索到了很不错的参数组合,最终好像是第一还是第二来着,让人眼前一亮。