一、MuJoCo 是什么?为什么灵巧手仿真都用它?

MuJoCo 全称 Multi-Joint dynamics with Contact,是 Roboti LLC 开发、后被 DeepMind 收购并开源的物理引擎。它专门为 机器人、多体系统、接触丰富 的场景设计。

1.1 和其他引擎的对比

引擎 特点 适合什么
MuJoCo 快、稳、接触处理优秀 机器人、灵巧手、抓取
PyBullet Python 接口友好,功能全面 教学、入门
Isaac Sim 高保真、GPU 加速、光线追踪 Sim2Real、视觉仿真
Gazebo 传感器模型丰富,ROS 生态好 工业级移动操作平台

灵巧手仿真为什么选 MuJoCo?

  • :单环境 1000+ FPS,RL 训练的基础
  • 接触稳定:灵巧手每天和物体接触几百次,其他引擎容易炸
  • 开源、免费:改代码无限制
  • DeepMind 背书:OpenAI Dactyl 用它训练的 Shadow Hand 策略直接迁移到真机

1.2 版本说明

2022 年 DeepMind 收购后,MuJoCo 进入 2.2+ 时代:

  • 完全开源(Apache 2.0 许可证)
  • pip install mujoco 直接安装
  • 每天全球数以万计的科研/工业用户

二、安装

2.1 最简单的安装方式

1
pip install mujoco

等待 30 秒到 1 分钟即可完成。

2.2 验证安装

打开 Python,运行:

1
2
3
import mujoco
print(mujoco.__version__)
# 应该是 3.x(2025 年最新是 3.x 系列)

如果没报错,说明装好了。

2.3 GPU 与显示需求

MuJoCo 的物理仿真跑在 CPU 上——不需要 GPU 也能跑 1000+ FPS。GPU 只在可视化渲染时用到。如果没有显示器(如 SSH 服务器),可以关闭渲染只跑物理。

2.4 无显示器(SSH 服务器)也能用

MuJoCo 可以纯 Python 运行,无需任何 GUI:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import mujoco

# 用 XML 字符串定义一个简单的掉落实验
xml = """
<mujoco>
<worldbody>
<light name="top" pos="0 0 1"/>
<body name="box" pos="0 0 0.5">
<freejoint/>
<geom type="box" size="0.2 0.2 0.2" rgba="1 0 0 1"/>
</body>
<geom name="floor" type="plane" size="1 1 0.1"/>
</worldbody>
</mujoco>
"""

m = mujoco.MjModel.from_xml_string(xml)
d = mujoco.MjData(m)

for _ in range(1000):
mujoco.mj_step(m, d)

print("最终高度:", d.geom_xpos[1][2]) # 盒子应该掉到地板上了

这段代码不需要显示器,在服务器上也能跑。MuJoCo 的 MjModel.from_xml_string() 是一个极其有用的函数,允许你在不创建文件的情况下直接在内存中构建场景。


三、核心概念:4 个你必须搞清楚的构件

3.1 MjModel(模型)—— 机器人的「骨架和肌肉」

MjModel 是 MuJoCo 的核心数据对象,包含:

  • 运动学:每个关节的类型、限位、父-子关系
  • 动力学:连杆的质量、惯量
  • 碰撞:几何体的形状、尺寸、摩擦系数
  • 控制:电机增益、力矩限幅

MjModel不可变的——加载后你一般不改它。它描述了机器人长什么样,能怎么动。可以把 MjModel 理解为硬件规格书。

3.2 MjData(数据)—— 机器人的「当前状态」

MjData 包含:

  • qpos:所有关节的当前位置(角度/位置)
  • qvel:所有关节的当前速度
  • ctrl:你发给关节的控制信号
  • contact:当前的接触信息(位置、力、法向)
  • sensor:传感器读数(如果有定义)

MjData 每步都在变,代表了机器人现在的姿势和受力。可以把 MjData 理解为仪表盘读数。

二者关系:

1
2
MjModel = 你的灵巧手的「设计图纸」——不变的,加载一次用全程
MjData = 你的灵巧手的「当前状态」——每步在变,reset 就重置

这个分离设计有一个重要优势: 想要多个并行仿真环境(RL 训练用),只需要 1 个 MjModel + N 个 MjData

1
2
3
4
5
6
7
model = mujoco.MjModel.from_xml_path('hand.xml')
datas = [mujoco.MjData(model) for _ in range(1024)]

# 每个 data 独立仿真
for d in datas:
d.ctrl[:] = random_action()
mujoco.mj_step(model, d)

1024 个环境并行跑,这就是 RL 训练的基础。

3.3 mj_step —— 仿真的「心跳」

1
mujoco.mj_step(m, d)

这一行做了 6 件事:

  1. 读取 d.ctrl 里的控制信号
  2. 解动力学方程(牛顿第二定律 + 欧拉方程)
  3. 检测所有碰撞对
  4. 解接触力(摩擦锥约束、法向力)
  5. 更新 d.qposd.qvel
  6. 更新所有传感器

每调一次 mj_step,仿真就前进一个时间步。默认时间步长 m.opt.timestep = 0.002 秒(2ms)。1000 步 = 2 秒的真实时间。

3.4 XML 模型文件 —— 你想要仿真的「一切定义」

XML 文件描述了整个仿真世界:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<mujoco>
<compiler angle="radian"/> <!-- 用弧度制 -->
<option timestep="0.002"/> <!-- 仿真步长 2ms -->

<worldbody>
<!-- 地面 -->
<geom name="floor" type="plane" size="0 0 0.05"/>

<!-- 灵巧手:外部引用 -->
<include file="hand.xml"/>

<!-- 被抓取的物体 -->
<body name="object" pos="0.3 0 0.1">
<freejoint/>
<geom type="cylinder" size="0.02 0.05"/>
</body>
</worldbody>

<actuator>
<!-- 电机定义 -->
<motor name="finger_motor" joint="index_joint1" gear="1"/>
</actuator>
</mujoco>

其中几个关键元素:

  • <body>:刚体(连杆),有质量和惯量
  • <joint>:关节,连接父 body 和子 body
  • <geom>:几何体,用于碰撞检测和可视化
  • <actuator>:驱动器(电机/肌肉)
  • <sensor>:虚拟传感器(力/力矩/触觉)

四、第一个完整示例:用 MuJoCo 跑灵巧手

4.1 加载官方 Shadow Hand

MuJoCo 自带 Shadow Dexterous Hand 的模型文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import mujoco

# 加载 Shadow Hand 模型(官方自带)
xml_path = mujoco.models.get_path('hand.xml')
print("模型路径:", xml_path)

# 创建模型和数据的实例
model = mujoco.MjModel.from_xml_path(xml_path)
data = mujoco.MjData(model)

# 查看模型有多少个自由度
print("关节数量 (nq):", model.nq) # 位置自由度
print("控制数量 (nu):", model.nu) # 控制通道数
print("身体数量:", model.nbody)
print("几何体数量:", model.ngeom)

输出示例:

1
2
3
4
5
模型路径: /usr/lib/python3/dist-packages/mujoco/models/hand.xml
关节数量 (nq): 26
控制数量 (nu): 20
身体数量: 49
几何体数量: 54

26 个自由度、20 个控制通道——这就是灵巧手比普通机械臂复杂的地方。

4.2 查看并控制关节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import mujoco
import numpy as np

model = mujoco.MjModel.from_xml_path(mujoco.models.get_path('hand.xml'))
data = mujoco.MjData(model)

# 查看控制通道的名称
for i in range(model.nu):
name = mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_ACTUATOR, i)
print(f"控制通道 {i}: {name}")

# 让第一个电机工作(通常是拇指)
data.ctrl[0] = 1.0 # 力矩 1.0 Nm

# 仿真 1000 步
for step in range(1000):
mujoco.mj_step(model, data)

if step % 100 == 0:
print(f"步 {step}: 拇指关节角度 = {data.qpos[0]:.3f} rad")

输出示例:

1
2
3
4
5
6
7
控制通道 0: TH
控制通道 1: FF
...
步 0: 拇指关节角度 = 0.000 rad
步 100: 拇指关节角度 = 0.037 rad
步 200: 拇指关节角度 = 0.068 rad
步 300: 拇指关节角度 = 0.094 rad

手指在你的控制下开始弯曲了!

4.3 用仿真频率和控制频率解耦

1
2
仿真频率:1000 Hz(mj_step 每步 1ms)
控制频率:50 Hz(策略每 20ms 发一次指令)

你的策略不需要每步都发指令:

1
2
3
4
for step in range(1000):
if step % 20 == 0: # 每 20 步更新一次控制
data.ctrl[:] = policy(data)
mujoco.mj_step(model, data)

这模拟了真实机器人的控制频率限制——真机上的电机通常只能以 50-100 Hz 更新。


五、核心 API:你需要掌握的完整手册

你不需要记住 MuJoCo 的所有 API,但需要熟练掌握下面这些:

5.1 模型加载

1
2
3
4
5
6
7
8
9
10
11
12
13
# 从文件加载
model = mujoco.MjModel.from_xml_path('path/to/hand.xml')

# 从字符串加载(无需临时文件)
model = mujoco.MjModel.from_xml_string(xml_string)

# 从官方示例加载
model = mujoco.MjModel.from_xml_path(
mujoco.models.get_path('hand.xml')
)

# 从字节加载(网络传输等场景)
model = mujoco.MjModel.from_xml_bytes(xml_bytes)

5.2 仿真步进

1
2
3
4
5
6
7
8
9
10
data = mujoco.MjData(model)

# 单步(一个时间步长)
mujoco.mj_step(model, data)

# 前进一步(指定步数)
mujoco.mj_step(model, data, nstep=10)

# 前向运动学:只更新位置,不更新动力学
mujoco.mj_forward(model, data)

mj_forward 通常用于 reset 后或手动修改位置后,快速重新计算所有几何体的世界坐标。

5.3 控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 发控制指令(力矩控制,默认)
data.ctrl[joint_index] = force_value

# 也可以批量发送
data.ctrl[:] = desired_forces

# 位置控制模式(如果 XML 里用了 position actuator)
# 需要对应修改 actuator type
data.ctrl[:] = desired_positions

# 查看关节名称 -> 索引映射
for i in range(model.nu):
name = mujoco.mj_id2name(
model,
mujoco.mjtObj.mjOBJ_ACTUATOR,
i
)
print(f"{i}: {name}")

5.4 读取状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 关节位置(弧度/米)
data.qpos # 所有关节位置(nq 维向量)
data.qpos[0] # 第一个关节的位置

# 关节速度
data.qvel # 所有关节速度

# 末端位置(世界坐标系)
data.body_xpos[body_id] # 身体的 3D 位置
data.body_xquat[body_id] # 身体的旋转(四元数)
data.body_xmat[body_id] # 身体的旋转(3×3 矩阵)

# 几何体位置
data.geom_xpos[geom_id] # 几何体的 3D 位置

# 关节所受的力/力矩
data.qfrc_actuator # 电机施加在关节上的力矩
data.qfrc_passive # 被动力(重力+摩擦+惯性)
data.qfrc_constraint # 约束力
data.qfrc_bias # 科里奥利力+离心力+重力

获取末端执行器(指尖)位置的完整方法:

1
2
3
4
5
6
7
# 方法一:通过 body name 获取
body_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_BODY, 'fingertip')
fingertip_pos = data.body_xpos[body_id].copy()

# 方法二:通过 site 获取(更精确,site 是定义在 body 上的参考点)
site_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_SITE, 'fingertip_site')
fingertip_pos = data.site_xpos[site_id].copy()

5.5 接触信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 接触数量
data.ncon

# 遍历所有接触
for i in range(data.ncon):
c = data.contact[i]

c.pos # 接触点位置(3D 向量)
c.frame # 接触坐标系(9D 向量:法向 + 切向1 + 切向2)
c.dist # 穿透深度(负数表示穿透)
c.geom1 # 第一个几何体 ID
c.geom2 # 第二个几何体 ID

# 接触法向力(近似)
normal_force = c.frame[0] # frame 的前 3 个元素是法向

# 获取接触力(更精确)
# 需要设置 model.opt.enableflags 包含 contact
# 然后从 data.efc_pos 中读取

5.6 传感器

1
2
3
4
5
6
7
8
9
10
11
# 传感器数据维度
model.nsensordata

# 所有传感器读数
data.sensordata

# 通过名称获取传感器索引和偏移
sensor_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_SENSOR, 'fingertip_force')
sensor_adr = model.sensor_adr[sensor_id] # 在 sensordata 中的偏移
sensor_dim = model.sensor_dim[sensor_id] # 数据维度
force_reading = data.sensordata[sensor_adr:sensor_adr + sensor_dim]

六、进阶:灵巧手仿真特有的配置

6.1 域随机化(Domain Randomization)

域随机化是 Sim-to-Real 迁移最核心的技术之一。通过在训练时随机化物理参数,策略学会适应各种条件,从而在真机上也能工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import numpy as np

def randomize_physics(model):
"""随机化物理参数,提高策略对真实世界差异的鲁棒性"""

# 随机化摩擦系数(默认 [0.5, 0.005, 0.0001])
# 分别对应:切向摩擦 / 扭转摩擦 / 滚动摩擦
for i in range(model.ngeom):
factor = np.random.uniform(0.7, 1.3)
model.geom_friction[i] = model.geom_friction[i] * factor

# 随机化关节阻尼
for i in range(model.njnt):
model.damping[i] = model.damping[i] * np.random.uniform(0.5, 1.5)

# 随机化物体质量(通过修改密度间接实现)
for i in range(model.ngeom):
model.geom_density[i] = model.geom_density[i] * np.random.uniform(0.5, 2.0)

# 随机化重力
gravity_scale = np.random.uniform(0.8, 1.2)
model.opt.gravity[2] = -9.81 * gravity_scale

需要注意的是MjModel 在文档中被描述为「不可变」,但实际上加载后可以修改部分数值字段(如摩擦系数、阻尼、重力等)。这是因为 MuJoCo 将这些参数暴露为可写的 numpy 数组,以便域随机化等训练场景使用。

6.2 添加力传感器(近似触觉)

MuJoCo 原生支持力/力矩传感器,可以用来近似灵巧手的触觉反馈:

1
2
3
4
5
6
7
8
9
10
11
<!-- 在 XML 中定义传感器 -->
<sensor>
<!-- 指尖力传感器 -->
<force name="fingertip_force" site="fingertip_site"/>
<torque name="fingertip_torque" site="fingertip_site"/>

<!-- 触觉皮肤近似:多个接触点 -->
<touch name="tactile_taxel_1" site="taxel_1"/>
<touch name="tactile_taxel_2" site="taxel_2"/>
<!-- 可以扩展到 16×16 阵列 -->
</sensor>

6.3 软指尖模型(参考 FineManip 论文方法)

在灵巧手仿真中,指尖通常是刚体——但真实的灵巧手有硅胶指套。可以通过调整接触参数来近似软接触:

1
2
3
4
5
<!-- 软指尖接触参数 -->
<geom type="sphere" size="0.01" pos="0 0 -0.02"
solref="0.01 1" <!-- 接触硬度时间常数 0.01s阻尼 1 -->
solimp="0.9 0.95 0.001" <!-- 接触力-穿透曲线 -->
condim="4"/> <!-- 接触维度:3=法向+剪切, 4=+扭转 -->

参数含义:

  • solref:参考时间常数和阻尼比,控制接触的「软硬程度」
  • solimp:接触力随穿透深度的增长曲线,控制「渐进接触」
  • condim:接触维度,影响摩擦力是否建模

调整这些参数可以让指尖接触更像真实硅胶材料。这正是 FineManip(精细操作触觉皮肤)论文实现亚像元精度的物理基础。

6.4 设置腱绳约束

MuJoCo 原生支持 tendon(腱绳)约束,这正是腱绳驱动灵巧手(如帕西尼感知科技的产品)需要的:

1
2
3
4
5
6
7
8
<tendon>
<!-- 定义一条腱绳:从关节 A 到关节 B -->
<spatial name="tendon_1" limited="true" range="0 0.05">
<geom site="joint_A_pos" site="joint_B_pos"
width="0.002"/>
<pulley divisor="1" multiplier="1"/>
</spatial>
</tendon>

在仿真中,腱绳可以配合 TendonForce 论文的方法,用 Transformer 模型预测真实腱绳力,替代理想力的假设。具体做法:

  1. 在 MuJoCo 的 mjData.ctrl 中设置电机位置
  2. 从 Transformer 模型读取预测的腱绳力
  3. 将该力应用到对应的 qfrc_actuator
  4. 执行 mj_step 时,运动学受腱绳约束影响

七、一个典型的灵巧手仿真工作流

你在灵巧手公司(如帕西尼感知科技)的日常仿真工作流大致如下:

1
2
3
4
5
6
7
8
① 加载灵巧手 XML
② 创建待抓取物体(球/圆柱/不规则形状)
③ 设置初始位姿(手张开、物体在掌心)
④ 运行控制策略(随机/PPO/示教)
⑤ 采集数据(接触力、关节角度、触觉读数)
⑥ 训练或验证策略

①-⑥ 循环,每天跑几千次抓取验证

下面是一个完整的抓取仿真器骨架:

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
import mujoco
import numpy as np

class DexterousGraspSimulator:
"""灵巧手抓取仿真器骨架"""

def __init__(self, xml_path):
self.model = mujoco.MjModel.from_xml_path(xml_path)
self.data = mujoco.MjData(self.model)

# 记录数据
self.contact_history = []
self.reward_history = []

# 获取一些关键的 ID
self._cache_ids()

def _cache_ids(self):
"""预缓存常用的 ID,避免重复查询"""
self.object_geom_id = mujoco.mj_name2id(
self.model, mujoco.mjtObj.mjOBJ_GEOM, 'object'
)
self.fingertip_site_ids = [
mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_SITE, f'fingertip_{i}')
for i in range(4) # 四指灵巧手
]

def reset(self, randomize=True):
"""重置到初始状态"""
mujoco.mj_resetData(self.model, self.data)

if randomize:
# 域随机化:物体初始位置小幅度随机
self.data.qpos[-7:] += np.random.uniform(-0.02, 0.02, size=7)

mujoco.mj_forward(self.model, self.data)

def step(self, action):
"""执行一步控制"""
self.data.ctrl[:] = np.clip(action, -1.0, 1.0)
mujoco.mj_step(self.model, self.data)

# 计算奖励
reward = self._compute_reward()
self.reward_history.append(reward)

# 记录接触信息
self.contact_history.append(self.data.ncon)

def _compute_reward(self):
"""计算抓取质量"""
# 物体是否被抬离桌面?
object_height = self.data.geom_xpos[self.object_geom_id][2]
lift_reward = 1.0 if object_height > 0.1 else 0.0

# 指尖接触是否稳定?(避免握力过大或过小)
contact_force = self._get_total_fingertip_force()
optimal_force = 5.0 # N,合适的抓握力
force_penalty = -0.01 * abs(contact_force - optimal_force)

# 物体是否偏离中心(防止滑落)
xy_dist = np.linalg.norm(self.data.geom_xpos[self.object_geom_id][:2])
drift_penalty = -0.1 * xy_dist

return lift_reward + force_penalty + drift_penalty

def _get_total_fingertip_force(self):
"""获取指尖总接触力"""
total_force = 0.0
for i in range(self.data.ncon):
c = self.data.contact[i]
# 只考虑指尖-物体的接触
geom1_name = mujoco.mj_id2name(
self.model, mujoco.mjtObj.mjOBJ_GEOM, c.geom1
)
geom2_name = mujoco.mj_id2name(
self.model, mujoco.mjtObj.mjOBJ_GEOM, c.geom2
)
if 'fingertip' in geom1_name or 'fingertip' in geom2_name:
# 接触法向力近似
normal_force = np.linalg.norm(c.frame[:3])
total_force += normal_force
return total_force

# 使用示例
sim = DexterousGraspSimulator('hand_with_object.xml')

for episode in range(100):
sim.reset()
done = False

while not done:
# 用随机策略(实际工作中会换成 PPO 等)
action = np.random.uniform(-1.0, 1.0, size=sim.model.nu)
sim.step(action)

# 检查是否超时或物体掉落
object_pos = sim.data.geom_xpos[sim.object_geom_id][2]
done = object_pos < 0.01 # 掉到地上了

print(f"Episode {episode}: 平均奖励 = {np.mean(sim.reward_history[-50:]):.3f}")

八、常见问题与调试技巧

8.1 手指穿透物体

1
2
3
4
5
6
7
现象:手指穿过物体而不是推开它
原因:碰撞检测开启太少,或时间步长太大

解决:
• 减小 timestep(0.001 而不是 0.002)
• 增大接触检测范围(model.opt.tolerance)
• 检查 geom 类型是否都设了 type

8.2 物体抖动/爆炸

1
2
3
4
5
6
7
现象:物体在接触后剧烈抖动或飞走
原因:接触刚度太高,或质量设置不合理

解决:
• 减小 solref 中的刚度值
• 确保物体的质量和惯性矩是合理的
• 检查是否有两个 geom 重叠初始位置

8.3 控制信号没反应

1
2
3
4
5
6
7
现象:设置了 data.ctrl 但关节不动
原因:actuator 没连到正确的 joint 上

解决:
• 检查 actuator 中的 joint/transmission 名称是否正确
• 检查 actuator 的 gear 值是否合理
• 使用 mujoco.mj_id2name 验证名称映射

8.4 仿真速度太慢

1
2
3
4
5
6
7
8
现象:FPS 低于预期

解决:
• 减少 geom 的数量(尤其是复杂 mesh)
• 增大 timestep(但不要超过接触稳定性极限)
• 关闭不必要的传感器
• 使用 model.opt.cone = mujoco.mjtCone.mjCONE_PYRAMIDAL
(金字塔摩擦锥比椭球锥更快)

九、我该去哪里进一步学习?

📖 中文文档(推荐优先阅读):

1
2
3
4
5
6
7
0. MuJoCo 中文文档翻译站(强烈推荐 ⭐)
https://docs.mujoco.cn
由社区维护的完整中文翻译,与官方英文版同步
内容覆盖:概述、计算、建模、XML 参考、编程、
API 参考、Python、MuJoCo XLA/Warp、
Unity 插件、OpenUSD、模型库、更新日志
推荐作为日常查阅的首选文档

官方资源(英文原版):

1
2
3
4
5
6
7
8
9
10
11
1. MuJoCo 官方文档
https://mujoco.readthedocs.io/
必读章节:XML 参考、Python 接口

2. MuJoCo 官方 Python 示例
https://github.com/google-deepmind/mujoco/tree/main/python/examples
推荐文件:body_interactions.py, marker.py, actuator.py

3. MuJoCo Menagerie(官方模型库)
https://github.com/google-deepmind/mujoco_menagerie
包含 Shadow Hand、Allegro Hand、Franka 等多种模型

社区资源:

1
2
3
4
5
6
7
8
4. Roboti 论坛
https://roboti.us/forum/
搜索 "dexterous hand" 有大量讨论

5. 论文源码中的 MuJoCo 使用
OpenAI Dactyl(Shadow Hand + MuJoCo 的奠基工作)
T-Dex(触觉预训练 + MuJoCo)
TendonForce(腱绳力建模 + MuJoCo)

十、速查表

你想做什么 代码
加载模型 m = mujoco.MjModel.from_xml_path('file.xml')
从字符串加载 m = mujoco.MjModel.from_xml_string(xml_str)
初始化数据 d = mujoco.MjData(m)
前进一步 mujoco.mj_step(m, d)
给关节发指令 d.ctrl[i] = value
读关节角度 d.qpos[i]
读接触数量 d.ncon
读接触力 d.contact[i].frame[:3]
重置 mujoco.mj_resetData(m, d)
前向运动学 mujoco.mj_forward(m, d)
获取 body 位置 d.body_xpos[body_id]
通过名称找 ID mujoco.mj_name2id(m, mujoco.mjtObj.mjOBJ_BODY, 'name')
通过 ID 找名称 mujoco.mj_id2name(m, mujoco.mjtObj.mjOBJ_BODY, id)
获取传感器值 d.sensordata[adr:adr+dim]

本文为 MuJoCo 零基础入门指南,结合了官方文档(mujoco.readthedocs.io)和学术界常用的最佳实践编写而成。 核心建议:不要试图学完所有 MuJoCo 知识再开始。安装→跑通示例→让手指动一下,三步就够了,遇到具体问题再查文档。