面向零基础读者。读完这篇文档,你将理解仿真服务器的核心概念,并能开始阅读 RCSSServerMJ 的源码。
源代码地址:robocup-sim/rcssservermj
后续阅读:理解服务器之后,可以继续读 Janus 客户端入门指南,看客户端如何接收感知、做决策并发送动作。
快速阅读路线
- 10 分钟速读:先看“第一章:什么是物理仿真”“第二章:Client-Server 架构”和“第八章:完整数据流”,建立服务器整体图景。
- 30 分钟入门:按网络通信、消息协议、仿真主循环、感知与动作这条线读,理解服务器和客户端每 20ms 如何交换信息。
- 深入阅读:再看 PlayMode、Beam、场地规格和“服务端 ↔ 客户端代码对应关系”,适合准备对照源码调试比赛逻辑。
第一章:什么是物理仿真
物理引擎 (Physics Engine) 是什么
想象一个虚拟世界——里面有地面、一个足球、一个机器人。你把球推一下,它会滚动、减速、停下;机器人抬脚,重心会偏移、可能摔倒。
物理引擎就是负责计算这些的程序。它模拟真实世界的:
- 重力:物体会往下掉
- 碰撞:球碰到脚会弹开
- 摩擦:球在草地上会慢慢停下
- 关节约束:机器人的膝盖只能在一定范围内弯曲
你可能在游戏中见过物理引擎(比如 Unity 的 PhysX)。RoboCup 用的 MuJoCo(Multi-Joint dynamics with Contact)是一个专门用于机器人和控制研究的高精度物理引擎,由 DeepMind 维护。
仿真步进 (Simulation Step)
物理引擎不是连续运算的,而是离散地推进时间:
|
|
每一"步"(step),引擎会:
- 读取所有物体当前的位置、速度
- 读取施加在物体上的力(比如电机的扭矩)
- 根据牛顿力学算出 0.020 秒后所有物体的新位置、新速度
这个时间间隔越小,仿真越精确,但计算量越大。RCSSServerMJ 默认每步 20ms,内部还会细分为多个 substep 来提高精度。
MuJoCo 的世界用 XML 描述
MuJoCo 用 XML 文件定义整个物理世界:
|
|
你可以在 resources/robots/T1/robot.xml 看到 T1 机器人的完整定义(23 个关节),在 resources/environments/soccer/world.xml 看到足球场的定义。
对应代码:
sim/simulation.py负责加载 XML 并驱动 MuJoCo 步进。
第二章:Client-Server(客户端-服务器)架构
为什么要把服务器和客户端分开
RCSSServerMJ 的世界里有两种角色:
| 服务器 (Server) | 客户端 (Client) | |
|---|---|---|
| 职责 | 运行物理仿真、执行裁判规则 | 控制一个机器人的决策 |
| 类比 | 足球场 + 裁判 | 球员的大脑 |
| 数量 | 1 个 | 最多 22 个(每队 11 人) |
| 代码 | 这个仓库(rcssservermj) | Janus(你们的项目) |
为什么要分开?因为在 RoboCup 比赛中,每个参赛队伍只提交自己的客户端程序,所有队伍连接到同一个官方服务器比赛。服务器是公平的"裁判 + 物理世界",客户端是你的"算法"。
数据流
|
|
每一轮循环:
- Server 把当前世界状态(感知)发给每个 Client
- Client 根据感知做出决策,把动作发回 Server
- Server 把动作施加到物理世界,推进一步仿真
- 重复
对应代码:
server/server.py管理整个循环,server/remote_agent.py管理每个 Client 的连接。
第三章:网络通信基础
Server 和 Client 是两个独立的进程(甚至可以在不同的电脑上),它们通过网络通信。
IP 地址 + 端口 = 门牌号
- IP 地址:标识一台电脑。
127.0.0.1(也叫localhost)表示"自己这台电脑"。 - 端口 (Port):同一台电脑上可以跑很多程序,端口号区分它们。RCSSServerMJ 默认用 60000(给 Agent)和 60001(给 Monitor)。
类比:IP 地址是小区地址,端口号是门牌号。你寄信需要写清楚两者。
TCP 协议
TCP (Transmission Control Protocol) 是一种网络协议,特点:
- 可靠:数据保证送达,丢了会自动重传
- 有序:发送顺序 = 接收顺序
- 双向:建立连接后,双方都可以发和收
- 字节流:没有"消息"的概念,只是一串连续的字节
类比:TCP 像打电话——先拨号建立连接,然后双方持续对话,挂断才断开。(与之对比,UDP 像发短信——直接发,不保证对方收到。)
Socket:程序里的网络接口
Socket 是操作系统提供给程序使用网络的接口。用 Python 写大概是这样:
|
|
对应代码:
server/communication/tcp_connection_listener.py:服务端监听端口、接受新连接server/communication/tcp_lpm_connection.py:在 TCP 上读写带长度前缀的消息
第四章:消息协议 — 怎么发消息
问题:TCP 是字节流,怎么知道一条消息在哪结束?
TCP 传输的是连续的字节,没有"边界"。假设服务器连续发两条消息:
|
|
客户端收到的可能是:
"helloworld"(粘在一起了)"hel"+"loworld"(断在奇怪的地方)
我们需要一种方法让接收方知道每条消息的边界。
长度前缀协议 (Length-Prefixed Message)
RCSSServerMJ 的解决方案很简单:每条消息前面加 4 个字节,表示消息的长度。
|
|
具体格式:
|
|
- 长度用 大端序 (Big-Endian):高位在前。
00 00 00 0C= 12。 - 接收方先读 4 字节得到长度 N,再读 N 字节得到完整消息。
|
|
对应代码:
server/communication/tcp_lpm_connection.py中的send_message()和receive_message()。
S-expression:消息内容的格式
知道了怎么分割消息,下一个问题是:消息的内容用什么格式组织?
RCSSServerMJ 使用 S-expression(符号表达式),一种用括号嵌套的轻量格式:
|
|
解读规则:
(...)表示一个表达式- 第一个元素通常是"键"(类型),后面是"值"或子表达式
- 可以嵌套:
(GS (unum 1) (team left))表示 GS 里面有 unum=1 和 team=left
和你可能见过的 JSON 对比:
| S-expression | JSON | |
|---|---|---|
| 示例 | (pos (x 1.0) (y 2.0)) |
{"pos": {"x": 1.0, "y": 2.0}} |
| 特点 | 更紧凑,解析简单 | 更通用,可读性更好 |
| 为什么用 | RoboCup 传统,SimSpark 时代传下来的 | — |
对应代码:
utils/sexpression.py实现了 S-expression 的解析器。
第五章:仿真主循环 — 服务器每帧在做什么
Game Loop(游戏主循环)
几乎所有实时仿真程序的核心都是一个 无限循环,每轮执行固定的一组操作:
|
|
RCSSServerMJ 的主循环
打开 server/server.py,找到 run() 方法,它的核心逻辑(简化后)是:
|
|
三种运行模式
| 模式 | 命令行参数 | 说明 |
|---|---|---|
| 实时模式 (Real-time) | 默认开启 | 服务器按真实时间推进,20ms 仿真一步。和现实同步。 |
| 全速模式 (As-fast-as-possible) | --no-realtime |
不等待,算完一步立刻算下一步。训练 AI 时常用。 |
| 同步模式 (Sync) | --sync |
服务器等所有 Agent 都返回动作后才推进下一步。保证不丢帧。 |
对应代码:
server/server.py的run()方法中通过self.real_time、self.sync_mode控制这些行为。
第六章:感知与动作 — 机器人的"眼睛"和"肌肉"
感知 (Perception):服务器告诉 Agent 什么
每一轮循环,服务器给每个 Agent 发送一条感知消息,包含:
|
|
几个关键感知类型:
| 感知 | 含义 | 用途 |
|---|---|---|
pos |
全局坐标 (x, y, z) | 知道自己在球场的位置 |
quat |
四元数 (w, x, y, z) | 知道自己面朝哪个方向(见下面解释) |
HJ |
关节角度 + 角速度 | 知道自己四肢的当前姿态 |
gyro |
三轴角速度 | 知道身体在旋转吗(检测摔倒) |
acc |
三轴加速度 | 知道身体在加速吗 + 判断摔倒方向 |
See |
视觉检测(极坐标) | 看到球、其他球员、场地标志 |
四元数 (Quaternion) 简介
你可能习惯用"朝北"、“朝东"来描述方向。在 3D 空间中,描述朝向常用 四元数 (w, x, y, z),一共 4 个数。
你不需要完全理解四元数的数学原理,只需要知道:
- 它是一种表示 3D 旋转的方式(比欧拉角更稳定,没有万向锁问题)
(1, 0, 0, 0)表示"没有旋转”(原始朝向)- 服务器发的格式是
[w, x, y, z],Janus 客户端会转换成[x, y, z, w](scipy 库的惯例)
对应代码:
sim/perceptions.py定义了所有感知类型。
动作 (Action):Agent 告诉服务器什么
Agent 收到感知后,决定要怎么动,把动作通过 TCP 发回来:
|
|
每个电机的控制指令格式:
|
|
其中 kp 和 kd 是 PD 控制器的参数(下面解释)。
还有一种特殊动作 Beam(传送):
|
|
在开球前(BEFORE_KICK_OFF)等特定状态下,Agent 可以用 beam 把自己传送到指定位置。
对应代码:
server/action_parser.py解析动作消息。
PD 控制器:电机怎么转到目标角度
机器人的关节由电机驱动。你不能直接"设置"关节角度(就像你不能瞬间把胳膊移到任意位置),而是通过电机施加力矩 (torque) 来驱动关节旋转。
PD 控制器 是最常用的控制方法,公式:
|
|
直觉理解:
- kp(比例增益)= 弹簧的刚度。kp 越大,关节越"用力"往目标位置拉。
- kd(微分增益)= 阻尼器。kd 越大,运动越平滑,不会来回震荡。
|
|
T1 机器人有 23 个关节,每个关节都独立做 PD 控制。走路时,客户端需要每帧给出所有 23 个关节的目标角度。
对应代码:服务器在
sim/sim_interfaces.py的ctrl_motor()中实现 PD 控制。 Janus 客户端在robot.py的commit_motor_targets_pd()中打包 PD 参数。
第七章:足球比赛逻辑
PlayMode(比赛模式)
一场足球比赛不只是"跑来跑去踢球"。比赛会经历很多状态:
|
|
常见模式:
| PlayMode | 含义 |
|---|---|
BEFORE_KICK_OFF |
开球前,球员可以 beam 到位 |
KICK_OFF_LEFT |
左队开球 |
PLAY_ON |
正常比赛中 |
THROW_IN_LEFT |
左队掷界外球 |
CORNER_KICK_RIGHT |
右队角球 |
GOAL_LEFT |
左队进球 |
GAME_OVER |
比赛结束 |
每种模式有不同的规则(谁能碰球、能不能 beam、超时自动转换等)。
对应代码:
games/soccer/play_mode.py定义枚举,games/soccer/sim/soccer_referee.py实现裁判逻辑。
Beam(传送定位)
在 BEFORE_KICK_OFF、GOAL_* 等状态下,球员可以使用 (beam x y angle) 把自己传送到指定位置。这模拟了真实足球中开球前球员站位的过程。
Janus 的 decision_maker.py 里硬编码了每个球员号码对应的 beam 位置。
场地规格
| 场地 | 尺寸 | 用途 |
|---|---|---|
| FIFA | 105m x 68m | 标准 11v11 比赛 |
| HL Adult | 14m x 9m | 3v3 比赛(Brazil Open Demo 等) |
对应代码:
games/soccer/soccer_fields.py定义场地几何参数。
第八章:完整数据流 — 从连接到踢球
现在把前面的概念串起来,看一个球员从启动到踢球的完整生命周期:
|
|
服务端 ↔ 客户端代码对应关系
| 服务端 (rcssservermj) | 客户端 (Janus) | 数据方向 |
|---|---|---|
server/perception_encoder.py 编码感知 |
world_parser.py 解析感知 |
Server → Client |
server/action_parser.py 解析动作 |
robot.py 打包电机指令 |
Client → Server |
sim/simulation.py 物理步进 |
— | Server 内部 |
games/soccer/sim/soccer_referee.py 裁判 |
decision_maker.py 根据 PlayMode 决策 |
间接影响 |
server/remote_agent.py 管理连接 |
server.py 管理连接 |
双向 |
术语速查表
| 术语 | 英文 | 含义 |
|---|---|---|
| 仿真步进 | Simulation Step | 物理引擎推进一个时间步长 |
| 感知 | Perception | 服务器发给客户端的传感器数据 |
| 动作 | Action | 客户端发给服务器的控制指令 |
| 长度前缀 | Length-Prefixed Message | 消息前 4 字节表示消息长度 |
| 大端序 | Big-Endian | 高位字节在前 |
| S-expression | S-expression | 括号嵌套的数据格式 |
| PD 控制 | PD Control | 比例-微分控制器,控制电机平滑到达目标角度 |
| 四元数 | Quaternion | 4 个数表示 3D 旋转,无万向锁 |
| Beam | Beam | 将机器人传送到指定位置(开球前使用) |
| PlayMode | Play Mode | 比赛状态(开球前/比赛中/界外球/进球…) |
| DOF | Degrees of Freedom | 自由度,T1 机器人有 23 个可控关节 |
| kp | Proportional Gain | PD 控制的比例增益(弹簧刚度) |
| kd | Derivative Gain | PD 控制的微分增益(阻尼系数) |