简介:源自东南大学2014年校内赛的Robocup3D仿真代码包,面向机器人足球仿真研究者和备赛选手,覆盖三维仿真环境下的多智能体决策、物理引擎交互、运动控制与团队协作等核心环节。压缩包共332个文件,主体为C/C++源码,含100个h头文件、87个cpp源文件、22个hpp头文件,实现机器人决策、路径规划与状态控制;另有55个rsg机器人模型文件、19个txt说明、5个sh构建脚本,以及readme、doxyfile、changelog等工程配套文件,方便对照学习工程组织方式。整个资源仅307KB,轻量紧凑;目前已有302人学习下载。通过这套代码,可了解A*/Dijkstra路径规划、基于状态机的行为决策、多机器人通信协作、基于计算机视觉的球与目标识别等具体实现,也可参考其底层内存管理、配置解析与日志调试设计,对深入理解RoboCup3D系统或备战机器人竞赛具有直接借鉴意义。
1. 一个 tar.gz 里装着一整支 RoboCup 3D 球队
拿到seu2013.tar.gz,如果只是当作普通压缩包解压,那它和任何 tar.gz 没有区别。但在 RoboCup 3D 仿真足球的语境里,这个包是一支球队的完整大脑:网络协议解析、世界模型更新、行走与踢球动作控制、场上局势决策,全都压缩在这样一个文件里。SEU 是高校队伍简称,2013 指版本年份,这类代码包在高校 RoboCup 3D 队伍间流传,也常被后来者当作起步代码来读。
对做仿真机器人和行为决策开发的人来说,这份包的价值不在于它能赢多少场比赛,而在于它把感知-决策-行动这条经典自动化链路完整落地了。读懂它、编译它、改造它,等于亲手走了一遍从传感器数据到运动指令的工程全流程。本文按环境的准备、源码结构的解析、编译与运行、调试技巧这个顺序展开,让你能把这个版本包从磁盘变成一支能上场跑动的队伍。
2. 先把运行环境对齐,再解压 seu2013.tar.gz
2.1 RoboCup 3D 仿真链路:SimSpark、agent 与 S-expression 协议
RoboCup 3D 的比赛里,真正的“球员”不是一个实体机器人,而是一个独立运行的 agent 进程。SimSpark 服务器负责物理仿真和视觉计算,每个 agent 通过 TCP 网络连接到服务器的 3100 端口。服务器按 50Hz 的频率计算物理状态,每 0.02 秒向 agent 推送一条感知消息,agent 解析后回传关节指令。整个过程是严格的消息循环:agent 没有主动推送通道,只能在收到消息后回复。
通信协议是 Lisp 风格的 S-expression,看起来是层层括号嵌套的符号表达式。视觉消息样例(已简化):
(See ((F 2 0) (B 3.2 0.5) ...))这条消息表示 agent 看到了 2 号队友,以及球在自己前方 3.2 米、偏右 0.5 弧度。除此之外,感知消息里还有身体姿态数据,比如(Hinge (Name LAnkleRoll) (AX 1.23))表示左踝关节当前角度,(GS (t 120.5) ...)表示比赛时间。所以读 SEU2013 源码前,先把协议层摸清楚,否则看到代码里一大堆 parse 函数会觉得非常乱。2013 年的 agent 代码基本都是这个结构:网络层接收字节流,按协议解析成内部结构体,再更新世界模型。
2.2 准备 Linux 编译环境与 SimSpark 版本匹配
2013 年的代码带有明显时代印记。那时的 SimSpark 主要在 Linux 环境开发,官方推荐 Ubuntu 系列。要把seu2013.tar.gz里的 agent 编译出来,系统里必须先有 C++ 编译工具链和 SimSpark 依赖。下面是那时期很常见的依赖版本范围:
| 依赖 | 作用 | 常见版本 |
|---|---|---|
| g++ / build-essential | C++ 编译工具链 | 4.6 ~ 4.8 |
| cmake | 构建配置 | 2.8 ~ 3.x |
| boost 库 | 网络、时间、随机数 | 1.46 ~ 1.60 |
| simspark / rcssserver3d | 仿真服务器本体 | 0.2.x |
| ode | 物理引擎(SimSpark 依赖) | 0.12 ~ 0.13 |
在 Ubuntu 上安装基本依赖:
sudo apt-get update sudo apt-get install build-essential cmake libboost-all-dev libode-dev装完基础依赖后,SimSpark 本体还需要单独从源码编译。2013 年的 agent 编译时通常要链接 SimSpark 的头文件和库,所以标准顺序是先装 SimSpark 再编 agent。如果系统仓库里的 SimSpark 版本和 agent 期望版本不一致,最常见问题是消息格式或关节名称的差异,表现为 agent 能连上服务器却收不到合法感知数据。另外,热词里常出现的e2fsprogs 1.46.6 tar.gz是 ext 文件系统工具,和 RoboCup 无关,但在较新发行版上排查文件写入异常时可以用它确认文件系统健康,属周边检查,不必深究。
提示:2013 年的代码包里一般自带编译脚本,叫
build.sh或setup.sh。先读脚本里的路径和版本注释,比直接敲cmake更靠谱。脚本里如果写了针对某个具体 SimSpark 版本的依赖,优先按那个版本准备环境。
2.3 在 Linux 上用 tar 解压 seu2013.tar.gz 并核对结构
解压命令在 Linux 上是标准操作:
# 先预览包内容,不改动文件系统 tar -tzvf seu2013.tar.gz | head -40 # 建立独立目录再解压 mkdir -p ~/robocup/seu2013 tar -xzf seu2013.tar.gz -C ~/robocup/seu2013 # 查看解压结果 find ~/robocup/seu2013 -maxdepth 2 | sort | head -50-t是列出包内容,-z是 gzip 格式,-x是解压动作,-C指定目标目录,-v显示明细。先预览的意义在于观察包内顶层路径:如果列出的是seu2013/开头,解压会自动进入这个子目录;如果直接是src/开头,解压到当前目录就会散开,所以要先建独立目录。
解压后先看结构和两个脚本:
cd ~/robocup/seu2013 ls -la cat build.sh 2>/dev/null || ls2013 年代这类包的典型目录结构如下:
seu2013/ ├── src/ │ ├── main.cpp │ ├── worldmodel/ │ ├── skills/ │ └── decision/ ├── config/ │ ├── formations.dat │ └── rsg/nao.rsg ├── build.sh └── start.shbuild.sh是作者期望的构建入口,start.sh是启动入口,两份脚本基本交代了代码怎么编译、怎么运行、默认连接哪个服务器。接下来就可以顺着src目录开始读感知、决策、行动三段核心逻辑。
3. 从 SEU2013 源码中抓住感知、决策、行动三根主线
3.1 main 入口与 sense-think-act 主循环
RoboCup 3D agent 的 main 函数,职责非常固定:解析命令行参数、初始化网络连接、进入主循环。命令行参数通常有--team设置队名、--unum设置球员编号、--host设置 SimSpark 地址。2013 年代的 C++ 实现里,参数解析很多是手写if判断,而不是用现代 CLI 库。
主循环核心如下:
int main(int argc, char** argv) { parse_args(argc, argv); // 读取 --team / --unum / --host connect_to_server(host, 3100); // TCP 连接到 SimSpark while (running) { std::string msg = receive_message(); // 阻塞等待服务器感知数据 WorldModel& wm = world_model; wm.update(msg); // 解析消息并更新世界模型 Action action = decision(wm); // 决策模块选出当前动作 send_commands(action); // 发送关节指令 if (wm.time() > MATCH_LENGTH) running = false; } }这个循环看起来简单,但三个细节决定比赛表现。第一,receive_message()是阻塞的,服务器 50Hz 来一条,它就读一条,处理时间必须控制在 20ms 内,超了就会丢节奏。第二,wm.update()不只是解析字符串,还要做平滑和预测,因为视觉消息噪声很大,直接把原始值存起来会导致决策抖动。第三,send_commands()的发送时机同样关键,SimSpark 只认当前仿真周期内到达的命令,发晚了就丢帧。所以代码里关节指令的时间戳必须跟着感知时间步走,不能自己额外滞后一拍。
3.2 感知:把 S-expression 视觉消息换算成全局坐标
感知模块先把 S-expression 解析成有意义的数值,再把坐标系转换搞对。视觉消息里的球、球门、队友位置,都是相对 agent 颈部坐标系的极坐标。例如(B 3.2 0.5)表示球在 3.2 米外、相位角 0.5 弧度。要变成全局坐标,代码里要做两步变换:极坐标转直角坐标,再按自身位置和朝向做旋转平移。
Vec2 see_to_global(const SeeInfo& see, const Vec2& self_pos, double self_theta) { double r = see.ball_distance; double phi = see.ball_theta; Vec2 local(r * cos(phi), r * sin(phi)); return Vec2( self_pos.x + local.x * cos(self_theta) - local.y * sin(self_theta), self_pos.y + local.x * sin(self_theta) + local.y * cos(self_theta) ); }这段代码里self_theta是 agent 当前朝向弧度,旋转矩阵符号一旦反了,球的位置就会镜像到另一侧,这是拿到包后最先要验证的数学点。2013 年的代码一般已经在worldmodel类里封装了这套转换,但视觉信号丢失时(如球被身体挡住),代码必须处理空数据。常见做法是记录最后一次看到球的时间,超过 1 秒就停止外推,改为用队友通信消息辅助定位,否则追球行为会一直跑向旧位置。
3.3 行动:关节角度命令与动作原语
行动模块是运动控制的出口。NAO 模型每条腿和手臂有多个关节,每个关节通过(HingeEffector (Name LKneePitch) (Target 0.4))命令设置目标角度。走路、踢球、起身这些动作,本质上是目标角度随时间变化的轨迹。动作轨迹写在 skill 代码里,一个 skill 接收目标参数后,根据当前时间步插值出本帧各关节角度。
void WalkSkill::execute(WorldModel& wm, Action& act) { double phase = (wm.time() - start_time_) * freq_; act.set_joint("LThighPitch", sin(phase) * step_height_); act.set_joint("RThighPitch", -sin(phase) * step_height_); // 其他关节按同样相位关系设置 }真实行走控制不会只有一个正弦波,但概念一致:skill 输出关节角度序列。改步行参数(步高、步频、摆宽)要在小范围里试。幅度过大会让机器人倒地,SimSpark 物理仿真下倒地后需要额外起身 skill 才能恢复,白白浪费时间。调整这些参数时,每次只改一个维度,观察步行稳定性和速度曲线,而不是同时动步高和步频。
3.4 决策:角色分工与状态转移的实现方式
决策模块决定当前调用哪个 skill。2013 年的主流实现是分层状态机:场上角色(守门员、后卫、前锋、中场)决定大的战术逻辑,角色内部再根据球的位置切换追球、踢球、回位等子状态。转移关键特征是球到我方球门的距离、球到自身的距离、自身在场上的位置。常见的判断逻辑大体如下:
SkillType decision(const WorldModel& wm, PlayerRole role) { double dist_ball = wm.ball_distance_to_self(); double dist_goal = wm.ball_distance_to_own_goal(); if (role == ROLE_GOALKEEPER && dist_goal < 2.0) { return SKILL_SAVE; } if (dist_ball < 0.8) { return wm.is_kickable_direction_goal() ? SKILL_KICK : SKILL_DRIBBLE; } if (dist_ball < 5.0) return SKILL_CHASE; return SKILL_HOME; }is_kickable_direction_goal()判断球是否在自己可踢范围内且朝向对方球门。决策层判断顺序很重要:守门员特殊行为优先,接着是本方能直接得分的机会,然后才是追球。阈值散落在配置类里,改动后必须用比赛回归验证,只看一两次比赛结果容易误判。另外要注意角色编号与战术阵型的绑定关系,2013 年的代码里,球员号码通常对应固定角色,换角色时要同步调整初始站位,否则开球时会出现多名球员抢同一区域。
4. 编译 SEU2013 并跑起一支球队的最小步骤
4.1 用 CMake 构建 agent 与常见的编译错误
编译动作本身不复杂,难在让老代码在新系统上通过编译。把解压后的代码放进独立 build 目录,用 CMake 生成 makefile:
cd ~/robocup/seu2013 mkdir -p build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc)-DCMAKE_BUILD_TYPE=Release启用-O2优化,仿真比赛要求决策循环在 20ms 内完成,优化等级要开。-j$(nproc)并行编译,但老代码偶尔会有头文件生成顺序问题,如果报“找不到某个自动生成的头文件”,去掉-j重新 make。
编译最常遇到的三个问题:一是-Werror把警告当错误,老代码在新编译器下会爆出大量废弃 API 警告;二是缺 Boost 头文件,报错信息能看到boost/xxx.hpp路径找不到;三是链接阶段undefined reference,多半是链接库顺序或 CMake 依赖没写全。针对第一个问题,临时加-Wno-error先跑通,后续再清理警告;针对后两个,在CMakeLists.txt里补find_package(Boost)和target_link_libraries对应库即可。
4.2 启动 SimSpark 服务器并让 agent 加入比赛
编译完成后,先启动服务器,再启动 agent,两个终端分别执行:
# 终端 A simspark # 终端 B cd ~/robocup/seu2013 ./build/seuagent --team SEU2013 --unum 1 --host 127.0.0.1SimSpark 默认监听 3100 端口。agent 连接成功后,日志里会收到服务器推送的初始世界状态,通常是(GS (t 0.00))开头的文本。如果终端 B 长时间无输出,先确认服务器端口监听状态:
ss -ltnp | grep 3100没有监听记录,说明 SimSpark 没起来或没监听默认端口。有监听但 agent 无反应,则检查--host是否写错,以及 agent 与服务器的 S-expression 版本是否匹配。2013 年代的协议在握手阶段就会交换版本信息,不匹配时 agent 会反复重试,日志里能看到具体错误码。
提示:SimSpark 无图形界面也能跑,但看不到画面不方便调动作。调步行转向这类运动参数时,建议同时开着可视化客户端,观察机器人重心偏移和脚掌着地状态,只看数值日志很难判断摔倒原因。
4.3 用 autotest 跑离线比赛做参数回归
单 agent 能跑起来之后,最终目标是完整球队。RoboCup 3D 社区常用的测试工具是 autotest,它自动启动 SimSpark、按默认阵型加载队伍、跑满固定比赛时长后退出,输出比分和统计数据。用它做参数回归非常直接:
autotest --testdir ~/robocup/seu2013 --length 300 --bin ./build/seuagent--testdir指定工作目录,autotest 在其中寻找球队配置;--bin指向 agent 可执行文件;--length设置比赛时长,单位秒。跑完输出里的Score是两队进球数,GameLog记录每个进球和犯规的时间戳。一次测试几分钟,参数微调前后各跑一轮,对比进球和控球统计,比人眼盯着实时画面可靠得多。
4.4 批量启动 11 名球员并排查站位问题
批量启动 11 个 agent 用循环脚本,是 2013 年版本最常见的做法:
for i in $(seq 1 11); do ./build/seuagent --team SEU2013 --unum $i --host 127.0.0.1 & sleep 0.1 done&把每个 agent 放后台运行,sleep 0.1错开启动时间,避免 11 个进程同时握手导致服务器过载。启动后如果某个球员不动或站在原地抖动,先查两处:一是启动脚本里球员号码和角色的映射关系,二是初始队形文件里的坐标。RoboCup 3D 球场长 30 米、宽 20 米,Beam指令设置的初始坐标如果越界,服务器会拒绝放置,表现为球员一直试图跑到场外。队形配置通常是文本格式,修改坐标不用重新编译,但两支球队如果共用同一份队形文件,双方球员会抢同一片区域,开球混乱。
5. 从 2013 版本代码里抽出三个可复用的经验
5.1 先加日志与坐标校验,再改行为逻辑
拿到这类旧代码包,第一件事不是重写决策模块,而是加调试输出。在感知更新后每 5 秒打印一次自身坐标、球坐标和球速估计,输出到文件,再配合可视化客户端定位一个初值。坐标校验的方法很简单:让 agent 站在 (0, 0) 面向 x 轴正方向,踢一脚球,看日志里球位置的变化方向和速度是否与预期一致。如果 x、y 分量符号有出入,问题基本集中在see_to_global的旋转矩阵上。这一步做好,后面所有决策行为才有可靠的数据基础。
5.2 迁移到现代 toolchain 的最小改动清单
旧代码编译迁移,按下面清单逐项处理,基本能一次通过:
| 改动项 | 原写法 | 现代写法 |
|---|---|---|
| C++ 标准 | 默认 C++98 | 显式set(CMAKE_CXX_STANDARD 11) |
| 字符串格式化 | sprintf(buf, ...) | snprintf(buf, sizeof(buf), ...) |
| 随机数 | random_shuffle | shuffle+ 随机引擎 |
| Boost 头文件 | 老路径boost/tr1/... | 新版本路径或改用标准库 |
这些改动不涉及逻辑层,只让代码在新库下编译通过。过程中保留一份原始源码和一份迁移后源码,方便对比排查编译报错和运行时行为差异。
5.3 最值得调的三个参数
三个参数对比赛表现影响最大。第一是世界模型的平滑系数smoothing_factor,默认值如果低于 0.3,球位置预测对噪声过于敏感;高于 0.6,反应又太迟钝。用 autotest 在 0.3 到 0.5 之间做小步扫描,比较控球时间。第二是踢球动作的kick_power上限,2013 年的物理参数与现代 SimSpark 版本存在差异,按 autotest 里的射门速度数据校准,而不是照搬默认值。第三是守门员站位纵深,默认值往往太贴近门线,调整到门前 1.2 到 1.5 米左右,扑救范围能明显扩大,但具体数值要配合本队后卫的回防速度决定。
本文还有配套的精品资源,点击获取