简介:一整套面向机器人学习者和开发者的Webots仿真案例合集,覆盖机器人运动控制与智能行为两大方向,案例包括弹簧腿跳跃与行走的动力学分析、倒立摆的平衡控制、四足狗的多种步态规划、基于键盘控制的小车避障,以及视觉巡线等内容。压缩包共121个文件、约33MB,除可执行程序外,还提供c/h源文件、makefile编译脚本、wbproj/wbt工程文件、mp4演示录像和pdf/md说明文档,便于直接运行、阅读源码和按步骤复现实验。目前已有2010人学习下载,适合高校学生、竞赛选手或自学者在Webots环境中从零搭建仿真场景、验证控制算法。通过学习这些案例,可以掌握PID控制器调参、传感器数据读取与融合、步态周期设计等要点;每个实例都包含可运行的工程和对应视频,既能观察默认效果,也能修改参数理解算法差异。从简单小车到复杂四足机器人,资源提供了一条渐进式的仿真实践路线。
1. Webots 仿真案例包:先别急着解压,这三件事决定你能否跑起来
Webots 仿真案例包这类资源,看起来就是一堆 .wbt 场景文件加控制器源码,但实际拆开后你会发现,真正决定能不能跑起来的不是模型长得像不像,而是版本、路径和传感器 enable 这三件事。这篇笔记把我拆过的案例包里最容易卡人的地方一次性说清:从目录结构看到传感器参数,再到五个高频翻车点。适合刚装好 Webots、想用现成案例做课程实验或算法验证的人;如果你已经跑通过基础 demo,可以直接跳去第 5 章看批量标定。
2. 拆包与版本对齐:目录结构、.wbt 文件头与控制器编译链
2.1 案例包的目录结构:worlds、controllers、protos 各管什么
把 .rar 解压后,第一件事不是双击 .wbt,而是先看目录。标准 Webots 工程由四块构成:worlds 存放场景文件,controllers 存放机器人控制程序,protos 存放可复用的自定义模型,assets 或 textures 存放贴图和网格资源。一个常见案例包的长相如下:
webots_case/ ├── worlds/ │ ├── line_follower.wbt │ └── obstacle_avoid.wbt ├── controllers/ │ ├── line_follower/ │ │ ├── line_follower.c │ │ └── Makefile │ └── obstacle_avoid/ │ └── obstacle_avoid.py ├── protos/ │ └── DemoRobot.proto ├── assets/ └── README.mdcontrollers 目录里每个子目录就是一个独立控制器,.wbt 文件通过 controller 字段指定加载哪个:
Robot { controller "line_follower" }这条配置的意思是:加载场景时,Webots 会去 controllers/line_follower 目录下找同名可执行文件或解释器入口。所以下载的资源如果只给 .wbt 而缺控制器源码,场景能打开但机器人不会动。我拿到案例包的第一件事就是核对 controllers 目录是否完整,再看 README 里写的控制器语言是 C 还是 Python,这决定后面怎么编译。
protos 目录同样不能丢。.wbt 文件通过 Proto 实例化引用自定义模型,比如 DemoRobot { translation 0 0 0.1 }。如果你只把 .wbt 单独拷到别的目录,打开场景时会看到模型整个消失或变成灰色占位,控制台报 Unknown Proto。复制案例时整个工程目录要整体搬,不能只拿单个场景文件。
2.2 版本对齐:先看 .wbt 文件头,再决定装哪个 Webots
Webots 从 R2020a 开始按年份命名,R2021a、R2022b、R2023a……每个版本的场景文件在文件头写明最低要求版本:
head -n 1 worlds/line_follower.wbt # 典型输出: #VRML_SIM R2022b文件头写着 R2022b 的 .wbt,用 R2023a 打开会弹转换提示,一般能自动升级;反过来用 R2021a 打开,大概率直接报“文件版本过新”。最省事的做法是按文件头版本装对应 Webots,其次才是用新版强制转换。我的习惯是先在命令行 head 一眼所有 .wbt 的文件头,如果包里版本混杂(常见于流传了两三年的资源包),就统一用最高版本对应的 Webots 打开并另存一遍。
提示:转换不是无损的,旧的 physics 参数、proto 路径在某些版本转换后会偏离,跑之前看一眼 Console 面板有没有黄色 Warning。
2.3 控制器编译链:C 控制器的 Makefile 与 Python 控制器的解释器选择
C 控制器在 Webots 里的编译方式是进到对应控制器目录执行 make,或直接在 Webots 界面点锤子按钮。案例包自带的 Makefile 通常和 Webots 安装目录联动(通过 WEBOTS_HOME 环境变量),如果系统里没配这个变量,make 会报找不到头文件。常见的解决方式是手动导出:
export WEBOTS_HOME=/opt/webots cd controllers/line_follower make clean && make这里 make 依赖系统里的 gcc 或 Visual Studio Build Tools,Webots 本身不编译 C 代码。如果每次编译都慢,可以先确认 Makefile 里的目标架构对不对,再把优化等级从 -O0 调到 -O2。
Python 控制器没有编译这步,但要确认 Webots 用哪个 Python 解释器。Edit > Preferences 里有 Python 路径设置,案例包如果声明兼容 Python 3.7,就别用 3.12 硬顶。我见过有人用 3.12 跑老案例包,控制器直接闪退,换回 3.8 就正常,这类玄学问题多半是 API 版本差异。检查命令:
python3 --version which python3无论 C 还是 Python,跑之前都建议先确认 world 文件里 controller 字段写的名字和 controllers 目录下的文件夹名一模一样,大小写不同也会导致加载失败。
3. 跑通一个差速巡检案例:从加载 world 到调出传感器数据
3.1 加载场景与启动流程:World 文件、控制台和仿真步长
File > Open World... 打开 worlds/line_follower.wbt。如果一切正常,3D 视图里会出现地板、墙壁和一辆差速小车。窗口底部 Console 面板没有红色报错,再点顶部播放按钮启动仿真。启动后发现机器人原地不动,这不是坏了,而是控制器还没编译或没被加载,回到 2.3 的做法编译一遍再暂停-播放重置。
一个容易忽略的设置是仿真步长 basicTimeStep,它定义在 world 文件里:
WorldInfo { basicTimeStep 32 physics 32 }basicTimeStep 的单位是毫秒,表示仿真调度一次的最小时间片。传感器 enable 的采样周期必须是它的整数倍,控制循环里的 robot.step(timestep) 也拿这个值当参数。把 basicTimeStep 从 32 改成 8,物理仿真更细腻但 CPU 开销显著上升;反之调到 64 能提速但底盘动力学可能不稳。我一般先保持案例包原始值,等确认逻辑正确再动它。
3.2 控制器代码逐段拆解:电机、测距传感器与查表参数
差速巡检是最典型的起步案例,控制器逻辑就三块:读传感器、判断、发速度。一份完整的 Python 控制器大致长这样:
# controllers/obstacle_avoid/obstacle_avoid.py from controller import Robot robot = Robot() # 连接当前场景里的机器人实例 timestep = int(robot.getBasicTimeStep()) # 拿到 world 文件里的步长 front = robot.getDevice("front_distance") # 名字必须和 wbt 里的 DistanceSensor 一致 front.enable(timestep) # 每个控制周期采样一次 left_wheel = robot.getDevice("left_wheel") right_wheel = robot.getDevice("right_wheel") left_wheel.setPosition(float('inf')) # 切到速度控制模式 right_wheel.setPosition(float('inf')) left_wheel.setVelocity(0.0) right_wheel.setVelocity(0.0) while robot.step(timestep) != -1: dist = front.getValue() if dist < 0.4: left_wheel.setVelocity(2.0) # 遇到障碍右转 right_wheel.setVelocity(-2.0) else: left_wheel.setVelocity(3.0) right_wheel.setVelocity(3.0)三处关键参数说明:第一,getDevice 的参数必须和 .wbt 里传感器节点的 name 字段完全一致,写错会拿不到设备;第二,setPosition(float('inf')) 这步不能省,电机默认是位置控制,不切模式的话转速会被内部回路拽回目标位置;第三,getValue() 返回的是查表后的模拟值,不是原始距离。查表在 DistanceSensor 节点的 lookupTable 里配置,格式是“距离、输出值、噪声”三列一组:
DistanceSensor { name "front_distance" lookupTable [0.02 1024 0, 1.0 0 0] }这行的意思是 0.02 米处输出 1024,线性衰减到 1 米处输出 0。想改探测灵敏度,改 lookupTable 比改代码快得多。
3.3 传感器选型与参数表:LiDAR、摄像头、IMU 的配置要点
案例包如果带感知类场景,常见的是这三种传感器的组合,各自的使能和参数注意点完全不同。
| 传感器 | 关键参数 | 参数说明 | 翻车点 |
|---|---|---|---|
| LiDAR | horizontalResolution | 水平一圈扫描点数 | 点数越高越吃 CPU |
| LiDAR | fieldOfView | 水平视场角,单位弧度 | 2π 才是真正的 360 度 |
| LiDAR | numberOfLayers | 垂直层数 | 多线雷达按层数线性增加开销 |
| Camera | width / height | 图像分辨率 | 分辨率翻倍,CPU 开销约翻 4 倍 |
| Camera | fieldOfView / near / far | 视场角和裁剪面 | near 设太大会丢近处目标 |
| IMU | Accelerometer / Gyro | 各自独立使能 | 忘记 enable 会一直读到 0 |
以 LiDAR 为例,.wbt 里的常见写法:
Lidar { name "lidar" horizontalResolution 360 fieldOfView 6.28318 numberOfLayers 1 maxRange 10 noise 0.01 }代码里使能时建议降频采样,比如 lidar.enable(2 * timestep),即每两个控制周期采一次。对避障类逻辑完全够用,CPU 省一半。摄像头同理,挂 VGA 级别分辨率就够做颜色识别,案例包默认如果给到 1280×720,先降下来再谈实时性。以后要做传感器融合,别急着同时开全部设备,先一个一个验证读数再合到一起;十个设备每个都 enable(timestep),32ms 的控制周期里调度会打架,表现就是数据忽快忽慢。
4. 避坑手册:五个常见的“仿真不动、数据不对、越跑越慢”
4.1 现象:控制台干净但机器人纹丝不动
原因:电机没有切到速度控制模式。C 控制器常见漏项是 setPosition 没给 inf,Python 侧则可能是两个电机只设置了一个,差速底盘缺一边驱动就原地打转或干脆不动。解决:两个轮子都执行 setPosition(float('inf')),再 setVelocity 设一个非零初值。如果已经开始仿真才改代码,记得先暂停再重新编译,否则控制器进程不会重启。
4.2 现象:测距传感器一直返回无穷大或 0
原因基本有二:一是没 enable,设备未激活时 getValue 返回 0 或缓存旧值;二是 lookupTable 的量程小于实际距离,或者传感器节点 rotation 朝向反了,射线朝上打天花板自然测不到地面障碍。解决:确认 enable(timestep) 在 while 循环之前执行;再用鼠标选中传感器看姿态,把 rotation 朝前朝里转,然后打印 getValue 验证量程和噪声。
4.3 现象:加了 LiDAR 后仿真越跑越慢
原因:LiDAR 和摄像头都是每采样周期全量计算,horizontalResolution 360 且每步都采,再加 16 层,CPU 直接拉满,real-time factor 掉到 0.3 以下。解决:调低水平分辨率、把 numberOfLayers 降到 1、采样周期放大到 2×timestep。如果场景里多台机器人每人挂一把雷达,减速更明显,优先在控制器里做距离粗判,远处障碍干脆不扫。
4.4 现象:.wbt 打开报版本过新或转换失败
原因:案例包是用比你当前版本更新的 Webots 保存的,旧版本解析不了新字段。解决:看 .wbt 文件头确认最低版本,装一个不低于是它的 Webots,打开后再用 File > Save World As 统一另存一遍,防止后续误保存回旧格式。
4.5 现象:导出的数据带时间戳对不上真实实验
原因:仿真时间由 basicTimeStep 累加,仿真暂停或降速时墙钟仍在走,直接用系统时间打点必然错位。解决:用 Supervisor 的 getTime() 拿仿真时刻,配合 CSV 导出。以下代码把机器人位置按仿真时刻记录:
from controller import Supervisor sup = Supervisor() # Supervisor 控制器才能拿全局数据 timestep = int(sup.getBasicTimeStep()) node = sup.getFromDef("TARGET") # DEF 名字要跟 wbt 里一致 with open("track.csv", "w") as f: f.write("sim_time,x,y\n") while sup.step(timestep) != -1: pos = node.getPosition() f.write(f"{sup.getTime()},{pos[0]},{pos[1]}\n")DEF 名称写不对,getFromDef 返回 None,后面读写直接 AttributeError。这条我踩过不止一次。
5. 把案例包变成参数实验台:用 Supervisor 回传数据,用命令行跑批量
5.1 场景里有名字的节点怎么拿:DEF 命名与全局坐标
把案例包跑通只是第一步,真正工作是改参数。拿差速案例来说,如果想把控制器的速度阈值和转向速度轮流换几组数据,手改再手截图不现实。案例包的 .wbt 里通常在关键机器人节点上写 DEF 属性,控制器侧用 getFromDef 拿节点:
from controller import Supervisor sup = Supervisor() node = sup.getFromDef("ROBOT_MAIN") pos = node.getPosition() rotation = node.getOrientation()这样就能在外部脚本里注入期望速度和目标点,而不是改控制器里的全局变量。注意 Supervisor 控制器和普通 Robot 控制器不能混用,案例包里如果节点已经开了 supervisor 字段,就别再挂第二个 Robot 主控制器。
5.2 无头跑批:一次把十组参数跑完
Webots 支持命令行无窗口运行,配合 --batch 和 --mode=fast 可以一口气跑完参数扫描:
webops --batch --mode=fast --stdout worlds/sweep.wbt上面命令里的数据全由 CSV 写盘,跑完解析比在 GUI 里一个一个调快一个量级。--mode=fast 不渲染画面,适合只看结果的同学;GPU 资源紧张时先用这个模式验证逻辑,再切回实时模式做视觉调试。
我自己的习惯是,每改一个参数先跑一次 fast 模式存一份 CSV,和上一次结果做 diff,确认数值变化符合预期再进 GUI。这份教学案例包给我的最大教训是仿真结果第一次跑出来永远可疑——不差也不信,从那以后我每次批量跑前都强制走一遍“清空日志 → fast 模式 → diff CSV”的流程,希望帮到你。
本文还有配套的精品资源,点击获取