
简介源自东南大学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.gz2.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-essentialC 编译工具链4.6 ~ 4.8cmake构建配置2.8 ~ 3.xboost 库网络、时间、随机数1.46 ~ 1.60simspark / rcssserver3d仿真服务器本体0.2.xode物理引擎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 生成 makefilecd ~/robocup/seu2013 mkdir -p build cd build cmake .. -DCMAKE_BUILD_TYPERelease make -j$(nproc)-DCMAKE_BUILD_TYPERelease启用-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 标准默认 C98显式set(CMAKE_CXX_STANDARD 11)字符串格式化sprintf(buf, ...)snprintf(buf, sizeof(buf), ...)随机数random_shuffleshuffle 随机引擎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 米左右扑救范围能明显扩大但具体数值要配合本队后卫的回防速度决定。本文还有配套的精品资源点击获取