安装服务器:
pip install rcsssmj安装示例队伍依赖(BahiaRT-MujOCo-base):
cd BahiaRT-MujOCo-base
# 使用 Poetry
poetry install
# 或使用 Hatch
hatch build# 标准11v11比赛(FIFA场地)
rcssservermj -a 127.0.0.1 -p 60000 -m 60001
# 或者3v3比赛(HL Adult场地)
rcssservermj -a 127.0.0.1 -p 60000 -m 60001 -b hl_adult -f hl_adult_2020服务器参数说明:
-a <ip>: 服务器IP地址(默认:localhost)-p <agent_port>: Agent连接端口(默认:60000)-m <monitor_port>: 监视器端口(默认:60001)-b <body>: 机器人模型(T1, K1, ant等)-f <field>: 场地类型(fifa, hl_adult_2020等)
cd BahiaRT-MujOCo-base
./start.sh localhost 60000或者手动启动每个球员:
for i in {1..11}; do
python3 run_player.py --host localhost --port 60000 -n $i -t TeamLeft &
done打开新终端:
cd BahiaRT-MujOCo-base
./start.sh localhost 60000注意:第二支队伍会自动被分配到右侧(因为左侧已满)
服务器会自动打开MuJoCo可视化窗口,显示实时比赛画面。
# 终端1:启动服务器
rcssservermj -a 127.0.0.1 -p 60000 -m 60001 -b T1 -f hl_adult_2020
# 终端2:启动左队(3名球员)
cd BahiaRT-MujOCo-base
./start3v3.sh localhost 60000
# 终端3:启动右队(3名球员)
cd BahiaRT-MujOCo-base
./start3v3.sh localhost 60000┌─────────────────────────────────────────────────────────────┐
│ RCSSServerMJ 服务器 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ MuJoCo 物理仿真引擎 │ │
│ │ - 机器人动力学模拟 │ │
│ │ - 碰撞检测 │ │
│ │ - 球的运动 │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↕ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Soccer Server 逻辑层 │ │
│ │ - 比赛规则裁判 │ │
│ │ - Play Mode 管理 │ │
│ │ - 计分系统 │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↕ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 通信层(TCP Socket) │ │
│ │ - Agent Port: 60000 │ │
│ │ - Monitor Port: 60001 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↕ ↕ ↕
┌─────────────────┴─┴─┴─────────────────┐
↓ ↓ ↓
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Agent 1 │ │ Agent 2 │ │ Agent N │
│ (球员1) │ │ (球员2) │ │ (球员N) │
└───────────────┘ └───────────────┘ └───────────────┘
所有消息采用长度前缀 + S-expression内容:
[4 bytes: 消息长度 (big-endian)] + [消息内容 (UTF-8)]
Python实现:
# 发送消息
def send_message(sock, msg: str):
msg_bytes = msg.encode('utf-8')
length_prefix = len(msg_bytes).to_bytes(4, byteorder='big')
sock.send(length_prefix + msg_bytes)
# 接收消息
def receive_message(sock) -> str:
length_bytes = sock.recv(4)
msg_length = int.from_bytes(length_bytes, byteorder='big')
msg_bytes = sock.recv(msg_length)
return msg_bytes.decode('utf-8')1. Agent创建TCP连接到服务器端口60000
↓
2. Agent发送初始化消息:(init <model> <team> <player_no>)
例如:(init T1 TeamLeft 1)
↓
3. 服务器分配Agent ID,开始仿真循环
↓
4. 每个仿真周期(20ms):
服务器 → Agent: 发送感知数据(Perception)
Agent → 服务器: 发送动作命令(Action)
↓
5. 循环步骤4直到比赛结束或断开连接
服务器每个周期发送的S-expression格式数据:
(time (now 5.42))
(GS (t 5.0)(pm PlayOn)(tl TeamLeft)(tr TeamRight)(sl 0)(sr 0))
(HJ (n he1)(ax 0.5)(vx 0.1))
(HJ (n he2)(ax -2.3)(vx -0.5))
... (所有23个关节)
(GYR (n torso)(rt 1.2 -0.5 0.3))
(ACC (n torso)(a 0.1 0.2 9.8))
(quat (n torso)(q 0.998 0.01 -0.02 0.05))
(pos (n torso_pos)(p 5.2 -1.3 0.45))
(See
(B (pol 3.5 -15.0 -2.0))
(F1L (pol 10.2 45.0 0.0))
(G1L (pol 8.5 30.0 -5.0))
(P (team TeamLeft)(id 2)
(head (pol 4.2 10.0 15.0))
(lfoot-vismarker (pol 4.3 9.5 -20.0)))
)关键感知类型:
time: 仿真时间GS: 比赛状态(play mode、队名、比分)HJ: 关节状态(角度、角速度)GYR: 陀螺仪(角速度)ACC: 加速度计quat: 姿态四元数pos: 位置(ground-truth)See: 视觉感知(球、地标、其他球员)
Agent发送的动作消息格式:
(关节名 目标角度 目标角速度 kp kd 额外力矩)
(beam x y theta)示例:
(lle1 -20.0 0.0 25.0 0.6 0.0)(lle2 5.0 0.0 25.0 0.6 0.0)(beam 5.0 2.0 0.0)PD控制公式:
applied_torque = kp * (q_target - q_current) + kd * (dq_target - dq_current) + tau
┌─────────────────────────────────────────────────────────────┐
│ Agent │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 1. Server (通信层) │ │
│ │ - TCP连接管理 │ │
│ │ - 消息收发缓冲 │ │
│ │ - send_immediate() / commit() / send() │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 2. WorldParser (感知解析层) │ │
│ │ - S-expression解析 │ │
│ │ - 数据结构化 │ │
│ │ - parse() → 更新World和Robot状态 │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 3. World (世界模型层) │ │
│ │ - 比赛状态(play_mode, game_time, score) │ │
│ │ - 球位置(ball_pos) │ │
│ │ - 自身位置(global_position) │ │
│ │ - 队友/对手位置(our_team_players, their_team_players)│
│ │ - 场地信息(field) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 4. Robot (机器人状态层) │ │
│ │ - 关节状态(motor_positions, motor_speeds) │ │
│ │ - 传感器数据(gyroscope, accelerometer) │ │
│ │ - 姿态(global_orientation_quat/euler) │ │
│ │ - 关节目标(motor_targets) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 5. DecisionMaker (决策层) │ │
│ │ - 行为选择逻辑 │ │
│ │ - carry_ball(): 追球并带球向球门 │ │
│ │ - 根据play_mode决定行为 │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 6. SkillsManager (技能管理层) │ │
│ │ - 技能调度(execute, is_ready) │ │
│ │ - 可用技能: │ │
│ │ * Walk: 行走控制(使用神经网络策略) │ │
│ │ * GetUp: 起身动作(关键帧序列) │ │
│ │ * Neutral: 中立姿态(关键帧) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 7. Robot.commit_motor_targets_pd() │ │
│ │ - 将所有关节目标转换为动作消息 │ │
│ │ - 提交到Server发送缓冲 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
代码位置:BahiaRT-MujOCo-base/mujococodebase/agent.py:42-73
def run(self):
# 1. 连接服务器
self.server.connect()
# 2. 发送初始化消息
self.server.send_immediate(
f"(init {self.robot.name} {self.world.team_name} {self.world.number})"
)
# 3. 主循环
while True:
try:
# 3.1 接收感知数据
self.server.receive()
# ↓ 内部调用 world_parser.parse()
# ↓ 更新 world 和 robot 状态
# 3.2 更新世界模型
self.world.update()
# ↓ 更新 playmode_group
# 3.3 决策
self.decision_maker.update_current_behavior()
# ↓ 根据 play_mode 选择行为
# ↓ 调用 skills_manager.execute()
# ↓ 技能更新 robot.motor_targets
# 3.4 发送动作
self.server.send()
# ↓ 发送缓冲区中的所有动作消息
except Exception:
self.shutdown()
raise代码位置:BahiaRT-MujOCo-base/mujococodebase/decision_maker.py:55-85
def update_current_behavior(self):
# 1. 比赛结束,不执行任何动作
if self.agent.world.playmode is PlayModeEnum.GAME_OVER:
return
# 2. 开球前/重新开球:传送到初始位置
if self.agent.world.playmode_group in (
PlayModeGroupEnum.ACTIVE_BEAM,
PlayModeGroupEnum.PASSIVE_BEAM,
):
self.agent.server.commit_beam(
pos2d=self.BEAM_POSES[...][self.agent.world.number][:2],
rotation=self.BEAM_POSES[...][self.agent.world.number][2],
)
# 3. 检查是否需要起身
if self.is_getting_up or self.agent.skills_manager.is_ready(skill_name="GetUp"):
self.is_getting_up = not self.agent.skills_manager.execute(skill_name="GetUp")
# 4. 正常比赛:追球并带球
elif self.agent.world.playmode is PlayModeEnum.PLAY_ON:
self.carry_ball()
# 5. 其他模式:保持中立姿态
elif self.agent.world.playmode in (PlayModeEnum.BEFORE_KICK_OFF, ...):
self.agent.skills_manager.execute("Neutral")
else:
self.carry_ball()
# 6. 提交所有关节目标到发送缓冲
self.agent.robot.commit_motor_targets_pd()代码位置:BahiaRT-MujOCo-base/mujococodebase/decision_maker.py:87-134
def carry_ball(self):
# 1. 获取关键位置
their_goal_pos = self.agent.world.field.get_their_goal_position()[:2]
ball_pos = self.agent.world.ball_pos[:2]
my_pos = self.agent.world.global_position[:2]
# 2. 计算球到球门的方向
ball_to_goal_dir = (their_goal_pos - ball_pos) / ||their_goal_pos - ball_pos||
# 3. 计算理想带球位置(球后方0.3米)
carry_ball_pos = ball_pos - ball_to_goal_dir * 0.30
# 4. 判断是否对齐
my_to_ball_dir = (ball_pos - my_pos) / ||ball_pos - my_pos||
angle_diff = arccos(dot(my_to_ball_dir, ball_to_goal_dir))
aligned = (angle_diff <= 7.5°)
# 5. 判断是否在球后方
behind_ball = dot(my_pos - ball_pos, ball_to_goal_dir) < 0
# 6. 决策
if not aligned or not behind_ball:
# 移动到球后方的理想位置
self.agent.skills_manager.execute(
"Walk",
target_2d=carry_ball_pos,
is_target_absolute=True,
orientation=None if distance > 2 else desired_orientation
)
else:
# 已对齐,直接带球冲向球门
self.agent.skills_manager.execute(
"Walk",
target_2d=their_goal_pos,
is_target_absolute=True,
orientation=desired_orientation
)服务器发送S-expression
↓
Server.receive() 接收原始字节流
↓
WorldParser.parse() 解析S-expression
↓
WorldParser.__sexpression_to_dict() 转换为字典
↓
更新 World 状态:
- world.playmode
- world.game_time
- world.ball_pos
- world.global_position
↓
更新 Robot 状态:
- robot.motor_positions
- robot.motor_speeds
- robot.gyroscope
- robot.accelerometer
- robot.global_orientation_quat
DecisionMaker.update_current_behavior()
↓
SkillsManager.execute("Walk", target_2d=...)
↓
Walk.execute() 计算关节目标
↓
Robot.set_motor_target_position(motor_name, target, kp, kd)
↓
更新 robot.motor_targets[motor_name]
↓
Robot.commit_motor_targets_pd()
↓
生成动作消息:(motor_name target 0.0 kp kd 0.0)
↓
Server.commit(motor_msg) 添加到发送缓冲
↓
Server.send() 发送所有缓冲消息
↓
服务器接收并应用到MuJoCo仿真
1. Walk(行走技能)
- 使用神经网络策略控制行走
- 输入:目标位置、目标朝向
- 输出:23个关节的目标角度
2. GetUp(起身技能)
- 使用预定义的关键帧序列
- 检测机器人是否倒地(z < 0.3m)
- 执行起身动作序列
3. Neutral(中立姿态)
- 单个关键帧
- 用于开球前、进球后等静止状态
# SkillsManager.execute()
def execute(self, skill_name: str, *args, **kwargs) -> bool:
skill = self.get_skill_object(skill_name)
# 检测技能切换,自动重置
reset = (self.current_skill_name != skill_name)
if reset:
self.current_skill_name = skill_name
# 执行技能
finished = skill.execute(reset, *args, **kwargs)
if finished:
self.current_skill_name = None
return True
return FalseBeforeKickOff (开球前)
↓ [裁判哨声]
KickOff_Left / KickOff_Right (开球)
↓ [球被踢动]
PlayOn (正常比赛)
↓ [球出界]
KickIn_Left / KickIn_Right (界外球)
↓ [球重新进场]
PlayOn
↓ [球进门]
Goal_Left / Goal_Right (进球)
↓ [重置]
BeforeKickOff
↓ [比赛时间结束]
GameOver (比赛结束)
# 终端1:服务器
rcssservermj
# 终端2:左队
cd BahiaRT-MujOCo-base && ./start.sh
# 终端3:右队
cd BahiaRT-MujOCo-base && ./start.shAgent ←→ Server: TCP Socket (端口60000)
消息格式: [4字节长度] + [S-expression内容]
周期: 20ms (50Hz)
接收感知 → 解析数据 → 更新世界模型 → 决策行为 →
选择技能 → 计算关节目标 → 发送动作 → 循环
- 自定义决策:修改
DecisionMaker.update_current_behavior() - 新增技能:继承
Skill类,在SkillsManager.create_skills()中注册 - 训练策略:使用
--sync --no-realtime模式加速训练 - 多Agent协作:通过外部通信(Redis/ROS)实现队内协调
参考资源:
- 官方文档:https://robocup-sim.gitlab.io/rcssservermj/
- Agent开发指南:
AGENT_DEVELOPMENT_GUIDE.md - 视觉感知指南:
VISION_PERCEPTION_GUIDE.md