跳到主要内容

5. PyBullet 快速上手

开始之前,先说明本节需要的基础:

  • 具备 Python 基础,能够创建虚拟环境并运行脚本。
  • 知道 URDF 是机器人/物体模型的一种常见描述格式;即使还没系统学过,也不影响先跑通本节示例。
  • 如果当前机器没有图形界面,接受先用无头模式 DIRECT 跑最小实验。

本节目标:

  1. 理解 PyBullet 在机器人仿真栈中的定位。
  2. 建立 connect -> loadURDF -> stepSimulation -> read state -> disconnect 这条最核心的工作流。
  3. 跑通一个最小示例,把现成的 URDF 模型真正放进物理世界里。
  4. 分清 GUIDIRECT 两种模式分别适合什么场景。

5.1 PyBullet 是什么

PyBullet 是 Bullet Physics SDK 的 Python 接口,常被用于机器人仿真、强化学习实验和快速原型验证。

如果把它放到常见仿真工具栈里理解,可以把它看成一种“轻量、直接、脚本友好”的物理仿真入口:

  • Isaac Sim 更偏高保真场景、渲染和传感器工作流。
  • MuJoCo 更偏动力学、接触和控制实验底座。
  • Gymnasium 更偏强化学习环境接口,而不是物理引擎本身。
  • PyBullet 则更适合快速加载 URDF、写几行 Python 就把场景跑起来。

所以如果目标是下面这些方向,PyBullet 往往会是很顺手的起点:

  • 想快速验证一个机械臂或移动机器人模型能否跑起来
  • 想用 Python 先把“加载模型 -> 设置重力 -> 推进仿真 -> 读状态”这条闭环打通
  • 想做教学、小实验、课程 demo 或研究原型
  • 想先用较低成本建立机器人仿真的基本直觉

5.2 安装

5.2.1 建议的起步方式

第一次上手时,建议优先使用独立虚拟环境,把依赖和已有项目隔开。这里用 Conda 做一个最小环境:

conda create -n pybullet python=3.10 -y
conda activate pybullet
python -m pip install --upgrade pip
pip install pybullet

如果本地已经有可用的 Python 环境,也可以直接安装:

pip install pybullet

5.2.2 为什么先不急着追求 GUI

很多第一次上手时遇到的问题,其实不是物理没跑起来,而是图形窗口没有正常打开。

因此更稳妥的顺序通常是:

  1. 先用 DIRECT 跑通无头仿真。
  2. 再切到 GUI 观察画面。
  3. 最后再进入机器人控制、相机渲染或强化学习环境封装。

这样做的好处是,能把“仿真逻辑是否正确”和“图形环境是否正常”拆开排查。

5.3 PyBullet 的核心心智模型

PyBullet 上手最快的方法,不是先记所有 API,而是先记住下面几个概念。

1. connect

PyBullet 的 API 是围绕“客户端连接到物理服务器”组织的。最常见的两种连接方式是:

  • p.GUI:打开图形界面,适合观察和交互。
  • p.DIRECT:无头模式,不开窗口,适合服务器环境和最小实验。

2. pybullet_data

很多最小示例都会用到 pybullet_data 里的现成资源,例如 plane.urdfr2d2.urdf。常见写法是:

p.setAdditionalSearchPath(pybullet_data.getDataPath())

这一步的作用,是告诉 PyBullet 去哪里找这些内置模型文件。

3. loadURDF

loadURDF 会把一个 URDF 模型加载进当前仿真世界。机器人、地面、简单刚体,都可以通过这一步进入场景。

4. stepSimulation

这一步负责真正推进物理。很多刚开始写脚本时“模型不动”的原因,不是重力没设,而是忘了循环调用 stepSimulation()

5. 状态读取

推进若干步之后,可以通过 getBasePositionAndOrientationgetNumJointsgetJointInfo 等 API 读取机器人和物体状态。

真正最值得记住的一条主线是:

connect -> setAdditionalSearchPath -> loadURDF -> setGravity -> stepSimulation -> read state -> disconnect

5.4 第一个最小示例:先用 DIRECT 跑通

第一次上手时,推荐先不打开 GUI,直接跑一个无头脚本。这能最快确认:

  • pybullet 是否安装成功
  • 模型文件能否被找到
  • 重力和步进是否正常工作

新建一个 hello_pybullet.py

import pybullet as p
import pybullet_data

physics_client = p.connect(p.DIRECT)

p.setAdditionalSearchPath(pybullet_data.getDataPath())
p.setGravity(0, 0, -9.8)

plane_id = p.loadURDF("plane.urdf")
start_pos = [0, 0, 1]
start_orn = p.getQuaternionFromEuler([0, 0, 0])
robot_id = p.loadURDF("r2d2.urdf", start_pos, start_orn)

print("start position:", p.getBasePositionAndOrientation(robot_id)[0])

for _ in range(480):
p.stepSimulation()

end_pos, end_orn = p.getBasePositionAndOrientation(robot_id)
print("plane id:", plane_id)
print("robot id:", robot_id)
print("end position:", end_pos)
print("end orientation:", end_orn)

p.disconnect()

执行:

python hello_pybullet.py

这个例子虽然很小,但已经把 PyBullet 最核心的工作流都走了一遍:

  • p.connect(p.DIRECT):连接到无头物理服务器
  • p.setAdditionalSearchPath(...):设置内置模型搜索路径
  • p.loadURDF("plane.urdf"):加载地面
  • p.loadURDF("r2d2.urdf", ...):加载一个现成机器人模型
  • p.stepSimulation():推进物理
  • p.getBasePositionAndOrientation(...):读取机器人底座姿态
  • p.disconnect():关闭连接

如果输出里的 end position 相比初始位置发生了变化,尤其是 z 值明显降低,通常就说明重力和步进已经正常生效。

5.5 再切到 GUI 看画面

当无头模式已经跑通之后,再切到 GUI 会更稳。可以把上面的脚本改成下面这样:

import time

import pybullet as p
import pybullet_data

physics_client = p.connect(p.GUI)

p.setAdditionalSearchPath(pybullet_data.getDataPath())
p.setGravity(0, 0, -9.8)

plane_id = p.loadURDF("plane.urdf")
robot_id = p.loadURDF("r2d2.urdf", [0, 0, 1])

for _ in range(2400):
p.stepSimulation()
time.sleep(1.0 / 240.0)

p.disconnect()

这里最关键的变化只有两个:

  • DIRECT 改成了 GUI
  • 每步之后加了 time.sleep(1.0 / 240.0),让画面按接近实时的速度播放

这一步完成后,通常会看到机器人在重力作用下落到平面上。和 DIRECT 相比,GUI 更适合:

  • 观察碰撞和姿态变化
  • 手动检查模型是否加载正确
  • 后续做调试线、相机、键鼠交互等可视化操作

如果当前机器是远程服务器、容器或没有桌面环境,优先坚持用 DIRECT 往往更实际。

5.6 从“能跑”到“会看机器人”

PyBullet 的一个强项,是很适合把 URDF 机器人快速加载进来,然后继续往关节和控制方向走。

下面这段代码可以在加载完 r2d2.urdf 之后,查看模型包含多少关节,以及前几个关节的名字:

num_joints = p.getNumJoints(robot_id)
print("num_joints:", num_joints)

for joint_index in range(min(num_joints, 5)):
joint_info = p.getJointInfo(robot_id, joint_index)
joint_name = joint_info[1].decode("utf-8")
joint_type = joint_info[2]
print(joint_index, joint_name, joint_type)

这一步很重要,因为后续无论是做:

  • setJointMotorControl2 控制关节
  • calculateInverseKinematics 求逆运动学
  • 读取 link 状态和接触信息

都需要先建立“这个机器人有哪些 joint / link”这层认知。

还有一个很容易忽略的点是:PyBullet 中很多关节默认会带着电机控制效果,不一定会完全自由下垂。如果观察到关节行为和预期不同,通常要先回头检查关节控制模式,而不是先怀疑物理引擎。

5.7 常见坑

第一次上手 PyBullet 时,最常见的问题通常集中在下面几类:

  • loadURDF 找不到文件:通常是忘了设置 pybullet_data 搜索路径,或者传入的相对路径不对。
  • 已经设置了重力,但模型完全不动:通常是忘了调用 stepSimulation()
  • 无头脚本能跑,GUI 打不开:通常是图形环境、远程桌面或驱动问题,先退回 DIRECT 更稳。
  • 机器人关节表现很僵硬:通常要检查默认 motor control,而不是只盯着重力或摩擦参数。

把这些问题拆开处理,排障速度会快很多。

5.8 本节小结

这一节最重要的结论有四个:

  • PyBullet 是一个很适合教学、快速验证和 URDF 机器人原型的 Python 仿真入口。
  • 第一次上手时,先用 DIRECT 跑通,再切 GUI,通常最稳妥。
  • 真正需要建立的核心心智模型是 connect -> loadURDF -> stepSimulation -> read state -> disconnect
  • 当最小示例已经能加载地面和机器人、推进仿真并读出状态时,就已经跨过 PyBullet 入门里最关键的一步了。

接下来如果要继续深入,通常有三条自然路径:

  1. 继续往机器人控制走,学习 setJointMotorControl2、逆运动学和关节状态读取。
  2. 继续往感知和可视化走,学习摄像头、调试线和图像渲染。
  3. 继续往强化学习走,把任务环境再封装到 Gymnasium 一类接口上。

参考资料