兄弟们,如果你和我一样是在 Ubuntu 22.04 上装的 ROS2 Humble,又不想守着老掉牙的 Gazebo Classic,而是想直接上 Gazebo Harmonic 做 ros2_control 仿真,那 gz_ros2_control 这个插件十有八九要把你折磨一遍。我前后折腾了两天,踩遍了“插件加载失败”“controller_manager 起不来”“编译时找不到 gz-sim 版本”这些坑,才把环境彻底跑通。这篇东西就是把我当时的完整排查过程和最终配置方案整理出来,给正要走这条路的人当个参考,省得在同一个坑里反复横跳。
这篇文章适合两类人:一类是 ROS2 Humble 已经装好、想把新版 Gazebo 和 ros2_control 打通做机器人控制仿真的人;另一类是已经在跑 gz_ros2_control 但遇到各种诡异报错、决定静下心从头理一遍环境的人。我会尽量讲清楚每一步背后的原因,不搞那种“照着敲就行”的玄学教程,这样你后面遇到别的幺蛾子也能自己排查。
1. 环境准备:Ubuntu 22.04 下的 ROS2 Humble 与 Gazebo Harmonic 安装
1.1 ROS2 Humble 安装要点
很多教程会让你从配置 locale 开始,实际上对于大部分国内开发者来说更关键的是软件源和 keys 这一关。Ubuntu 22.04 对应 ROS2 Humble,官方推荐的安装方式就是先添加 ROS2 源的 key,再把源写进系统。
sudo apt update && sudo apt install curl gnupg lsb-release sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null sudo apt update装完源之后,桌面版直接装ros-humble-desktop就行,这里面带了 rviz、demo 等一堆常用工具,仿真调试基本够用。如果你打算做纯机器人控制仿真,也可以只装ros-humble-ros-base,加上后续的 ros2_control 相关包,更轻量。
sudo apt install ros-humble-desktop python3-colcon-common-extensions python3-rosdep echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc source ~/.bashrc这里有个新手特别容易漏的步骤:python3-colcon-common-extensions不装的话,后面源码编译 gz_ros2_control 时colcon命令会缺很多东西,我一开始就是没装,结果编译到一半各种 python 模块找不到,白白浪费了半小时。
另外 rosdep 建议顺手初始化一下,后面源码编译时经常要用:
sudo rosdep init rosdep update如果 rosdep init 报错说系统里已经存在配置文件,说明你可能装过别的 ROS 版本,直接跳过 init 只做 update 就行,不影响使用。
1.2 安装 Gazebo Harmonic
Gazebo Harmonic 对应的软件包是gz-harmonic,底层是 gz-sim8。它和 Gazebo Classic(gazebo11)是两套完全不同的东西,命令也不一样:Classic 用gazebo启动,Harmonic 用gz sim启动。这两个包可以在系统里共存,不影响彼此,但要注意别配混了。
安装 Harmonic 需要添加 OSRF 的软件源:
sudo apt-get install curl lsb-release gnupg sudo curl https://packages.osrfoundation.org/gazebo.gpg --output /usr/share/keyrings/pkgs-osrf-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/pkgs-osrf-archive-keyring.gpg] http://packages.osrfoundation.org/gazebo/ubuntu-stable $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/gazebo-stable.list > /dev/null sudo apt-get update sudo apt-get install gz-harmonic装完验证一下:
gz sim --version能输出来类似Gazebo Simulator version 8.x.x这样的信息就说明装成功了。注意,如果你之前装过 Gazebo Fortress 或者 Garden,系统里会有多套 gz-sim 版本共存,后面编译 gz_ros2_control 时极容易因为 CMake 找到了错误的版本而踩坑,我在第 3 节会专门讲。
1.3 ros_gz 桥接包与 gz_ros2_control 的三种安装方式
先说 ros_gz 这个包。它负责 ROS2 和 Gazebo 之间的 topic、service 桥接,以及一些常用 launch 工具。在 Humble 下直接用 apt 装ros-humble-ros-gz是给 Gazebo Fortress(gz-sim6)准备的,如果你要用 Harmonic,这个 apt 版本大概率桥接不上,需要用源码编译新版 ros_gz。
gz_ros2_control 就更麻烦了。它是 ros-controls 官方仓库里的项目,作用是让 ros2_control 的 Controller Manager 能控制新式 Gazebo 里的关节。目前有三种安装方式:
apt 直接装
ros-humble-gz-ros2-control:省事,但默认依赖的 Gazebo 版本和你系统里的 Harmonic 很可能对不上,实际跑起来很容易出问题,我第一轮就是这么翻车的。源码编译 ros-controls/gz_ros2_control 仓库,指定正确分支:灵活,可控,这是我自己最终采用的方式,也是本文下面要详细讲解的方式。
用 Docker 镜像:如果你不想折腾宿主机环境,可以找一个配好 Humble + Harmonic 的镜像,但这样调试时文件挂载、GPU 显示这些也要花时间处理,不如本地环境直接。
我建议有耐心的话直接走源码编译这条路线,虽然前期麻烦一点,但后面遇到问题你至少知道去哪看、怎么改。
2. 理清 ros2_control 与 gz_ros2_control 的配合逻辑
2.1 ros2_control 的四大件
ros2_control 本身不是一个仿真工具,它是一个硬件抽象层框架。官方概念里有四个核心角色:Controller Manager、Controller(控制器)、Hardware Interface(硬件接口)、Robot Hardware(机器人硬件,也就是实际执行机构或仿真器)。
控制链路大概是这样的:Controller Manager 统一管理所有 Controller,比如关节轨迹控制器、状态广播器;Controller 通过 command interface 发指令、通过 state interface 读状态;Hardware Interface 负责把这些指令和状态翻译成真实硬件或仿真器能理解的格式;真正的读写硬件动作则由 Hardware Interface 背后的实现去完成。
这个设计的核心价值在于解耦。你的控制器逻辑可以完全不用关心底层到底是真实电机还是仿真关节,只要遵循同样的接口规范,就能在不同平台上无缝切换。这也是为什么仿真环境对机器人开发这么重要——先用仿真把控制逻辑调通,再迁移到实物上,代价小得多。
2.2 gz_ros2_control 在整个链路里的位置
gz_ros2_control 充当的就是 Robot Hardware 这一层,它向 ros2_control 提供一个名为GazeboSimSystem的硬件接口实现。这个类继承了 ros2_control 的标准 Hardware Interface 接口,并把关节读写操作映射到 Gazebo Sim 里的 joint 对象上。
在 Gazebo Sim 里,它是作为一个 system plugin 被加载到模型中的。插件启动时会帮你把/controller_manager节点拉起来,同时读取配置好的 controllers.yaml,把控制器配置交给 Controller Manager。仿真里每个关节的状态由 gz_ros2_control 周期性地从 Gazebo 中读取并发布,控制器计算出来的指令再由 gz_ros2_control 写回 Gazebo 里的关节。
所以你看到的现象往往是:一旦插件加载成功,ROS2 环境里会自动多出/controller_manager这个节点,ros2 control list_controllers也能看到对应的控制器。如果这个节点没出现,那问题基本都出在插件加载这一环。
2.3 版本兼容矩阵:哪些组合能跑、哪些必然踩坑
版本匹配是 gz_ros2_control 最反直觉的地方。我一开始以为只要 ROS2 和 Gazebo 装好了就能用,实际上不同分支的 gz_ros2_control 和不同 gz-sim 版本之间的匹配关系很讲究。
| ROS2 版本 | Gazebo 版本 | gz_ros2_control 来源 | 稳定性 |
|---|---|---|---|
| Humble | Gazebo Classic 11 | gazebo_ros2_control(apt) | 很稳定,教程最多 |
| Humble | Fortress(gz-sim6) | ros-humble-gz-ros2-control(apt) | 比较稳定 |
| Humble | Harmonic(gz-sim8) | 源码编译,GZ_SIM_VER=8 | 需要踩坑,本文核心 |
| Rolling | Harmonic(gz-sim8) | 源码编译 main 分支 | 推荐新项目使用 |
这里要特别说明一下:Humble 官方发布时主要适配的是 Gazebo Fortress,所以 apt 仓库里的 ros-gz 和 gz_ros2_control 二进制包大多绑定 gz-sim6。如果你想在 Humble 上用 Harmonic,就绕不开源码编译这条路,而且编译时一定要让 CMake 找到 gz-sim8,而不是默认的 gz-sim7 或 gz-sim6。这个“版本指向错误”的问题,就是绝大多数插件兼容性报错的根源。
3. gz_ros2_control 插件兼容性问题深度排查
3.1 错误一:Failed to load plugin libgz_ros2_control.so
这是最经典也最让人崩溃的报错,形式通常是 Gazebo 启动后在日志里打出一行:
[Err] [Plugin.hh:126] Failed to load plugin libgz_ros2_control.so: ... cannot open shared object file或者:
[Err] [SystemManager.cc:183] Failed to load system plugin [gz_ros2_control::GazeboSimSystem]看到这个先别慌。首要是确认系统里到底有没有这个 .so 文件:
find / -name "libgz_ros2_control.so" 2>/dev/null如果找不到,说明你源码编译后忘了sourceinstall 目录,或者压根没编译成功。如果找到了但路径很怪,比如在/workspace/install/...下面,那就需要检查 Gazebo 有没有把那个目录加进插件搜索路径。
可以手动把插件目录添加到环境变量里再启动:
export GZ_SIM_SYSTEM_PLUGIN_PATH=/opt/ros/humble/lib:$HOME/gz_ros2_control_ws/install/lib如果你的 lib 是装在/opt/ros/humble/lib下的,GZ_SIM_SYSTEM_PLUGIN_PATH一定要带这个路径。Gazebo Sim 搜索插件时会优先看这个变量,不设置的话它大概率只找默认安装目录,源码编译产物就找不到了。
如果.so文件确实存在仍然加载失败,那就要用ldd看它依赖的库是否齐全:
ldd /path/to/libgz_ros2_control.so | grep "not found"只要有依赖缺失,基本可以断定是 gazebo 版本不对或者 ros2 环境没 source 完整,补装对应版本的 gz-sim 开发库就行。我在实际调试中还遇到过一种情况:libgz_ros2_control.so是编译好了,但它在运行时依赖的libgz-sim8.so和我系统里装的libgz-sim7.so混在一起,Gazebo 进程加载的时候直接冲突崩溃。这种就需要把旧版本 gz-sim7 的插件路径从GZ_SIM_SYSTEM_PLUGIN_PATH里彻底清掉,别让两个版本同时出现在搜索路径里。
3.2 错误二:编译时找不到 gz-sim7 或 GZ_SIM_VER 指向错误
源码编译 gz_ros2_control 的时候,CMake 会尝试find_package(gz-sim${GZ_SIM_VER})。如果你直接按 README 默认命令编译,它可能去找 gz-sim7 或者你系统里已有的其他版本。而你明明装的是 gz-sim8(Harmonic 对应版本),结果就是:
CMake Error at CMakeLists.txt:...: Could not find a package configuration file provided by "gz-sim7"解决方法有两种。第一种是编译时通过 CMake 参数显式指定版本:
cd ~/gz_ros2_control_ws colcon build --symlink-install --cmake-args -DGZ_SIM_VER=8 -DCMAKE_BUILD_TYPE=Release如果-DGZ_SIM_VER这个变量在你的分支里不被识别,那就只能去改 CMakeLists.txt。找到文件里的 GZ_SIM_VER 相关行,直接改成 8 再编译:
# 在 gz_ros2_control/CMakeLists.txt 里找到类似这样的行 # set(GZ_SIM_VER "7" CACHE STRING "Gazebo Sim version") # 改成 set(GZ_SIM_VER "8" CACHE STRING "Gazebo Sim version")我这里要提醒一句:不同分支的变量名可能有区别,有的是GZ_SIM_VER,有的是USE_GZ_SIM_VERSION,你搜一下 CMakeLists 里的 gz-sim 相关关键词就能看到。还有个小技巧,在colcon build之前先跑一次cmake .. -LA看看有哪些缓存变量,能少走很多弯路。
编译成功后,检查一下生成的动态库链接的是不是 gz-sim8:
ldd install/lib/libgz_ros2_control.so | grep gz-sim如果输出里同时出现 gz-sim7 和 gz-sim8,那说明你的 CMake 路径里混了多个版本,建议清理 build 目录重新配置,只把 gz-sim8 的路径留在 CMAKE_PREFIX_PATH 里。
3.3 错误三:/controller_manager 节点不启动
插件加载没报错,Gazebo 也正常起来了,但你打开另一个终端输入ros2 node list,死活看不到/controller_manager。这种情况通常不是插件本身挂了,而是插件的 ROS2 上下文初始化出了问题。
先确认一下你的 SDF 文件里 plugin 标签到底有没有生效。可以在 Gazebo 启动日志里搜索gz_ros2_control或者GazeboSimSystem相关输出。如果插件确实被加载了,但节点没起来,常见原因有三个:
第一,没有 source ROS2 环境。Gazebo 是在哪个终端启动的,那个终端的 bashrc 里必须已经有/opt/ros/humble/setup.bash的 source,否则插件里的 ROS2 初始化会静默失败。
第二,插件写错了作用域。gz_ros2_control的插件一般要放在<model>标签内部,如果你把<plugin>放在了<world>下面,它虽然也能被加载,但可能找不到对应的模型关节,controller_manager 建不起来。这一点特别坑,因为我见过不少教程把 world 级和 model 级混着写,导致最终行为不一致。
第三,controllers.yaml 的路径没解析对。在 SDF 里写$(find xxx)时,如果插件内部没有做包路径解析,就会拿不到文件。稳妥起见,你先用绝对路径测试:
<plugin filename="libgz_ros2_control.so" name="gz_ros2_control::GazeboSimSystem"> <parameters>/home/yourname/catkin_ws/src/.../config/cartpole_controller.yaml</parameters> </plugin>等确认全链路跑通了,再换成$(find ...)的写法也不迟。这种“先用绝对路径排除变量干扰”的思路,其实适用于所有类似情况,定位问题的时候能少一半工作量。
3.4 错误四:控制器列表为空,JointStateBroadcaster 加载失败
/controller_manager节点起来了,但执行:
ros2 control list_controllers输出显示command not found或者控制器列表是空的。如果是command not found,说明你缺ros2_control相关的命令工具,装一下:
sudo apt install ros-humble-ros2-control ros-humble-ros2-controllers列表为空但节点存在,就要检查 controllers.yaml 是否被正确加载。可以用以下命令看看 controller_manager 的参数里到底有没有 controller 配置:
ros2 param dump /controller_manager如果 yaml 没被加载,你会看到参数前缀下面根本没有joint_state_broadcaster或joint_trajectory_controller这些字段。此时回到 SDF 里的<parameters>标签检查路径和文件名,尤其注意 yaml 的格式必须符合 ros2_control 规范。
还有个常见坑是use_sim_time。如果你的 controller_manager 设置了use_sim_time: true,但 Gazebo 的时间同步没正常建立,controller 会一直等待仿真时间推进,表现就是加载成功了但状态一直是 inactive。
JointStateBroadcaster 加载失败还有一种概率很低但很烦的原因:controller 类型名写错了。ros2_control 里 broadcast 的类型是joint_state_broadcaster/JointStateBroadcaster,你如果漏了前面的命名空间,Controller Manager 会直接拒绝加载。报错信息会提示Class not found,千万别只盯着插件层面排查。
4. 手把手配置一份可用的 SDF 插件与控制文件
4.1 SDF 文件里的 gz_ros2_control 插件怎么贴
我以最经典的 cartpole 示例来说明。一个能跑通的最小 SDF 文件,核心结构大概是这样:
<sdf version="1.9"> <world name="default"> <include> <uri>model://cartpole</uri> <name>cartpole</name> </include> <model name="cartpole"> <plugin filename="libgz_ros2_control.so" name="gz_ros2_control::GazeboSimSystem"> <parameters>/your/absolute/path/to/cartpole_controller.yaml</parameters> </plugin> </model> </world> </sdf>看到没有,插件是挂在<model>下面,不是<world>下面。有些示例会在 world 级别写相同的插件,但根据我自己的测试,在 model 级别写才是最不容易出错的。因为GazeboSimSystem需要绑定到具体的模型实例上,才知道要控制哪些关节。
如果你是用 URDF + xacro 做机器人描述,然后通过 ros_gz 的create功能生成 SDF,那 URDF 里的<gazebo_ros2_control>扩展标签会被转换成 SDF 的<plugin>。这种情况下,你其实可以只改 URDF 里的插件参数,再由工具自动生成 SDF,省去手动维护两个文件的烦恼。
不过如果你是手工写 SDF,这里有个容易被忽略的细节:<plugin>标签里除了<parameters>,有时候还需要写上<ros>子标签来指定命名空间和节点名。比如:
<plugin filename="libgz_ros2_control.so" name="gz_ros2_control::GazeboSimSystem"> <ros> <remapping>/controller_manager:=/my_robot/controller_manager</remapping> </ros> <parameters>/your/path/controllers.yaml</parameters> </plugin>如果你只有一个机器人,不写<ros>也没关系,默认命名空间即可。但如果同一进程里同时有多个机器人模型,就必须给每个插件的 controller_manager 设定不同命名空间,否则它们会抢同一个节点名。这个坑在跑多机仿真时特别容易爆。
4.2 controllers.yaml 怎么写才能被识别
ros2_control 的 yaml 文件其实有非常固定的结构,少了任何一层嵌套都可能导致配置加载失败。一个最简可用的例子:
controller_manager: ros__parameters: update_rate: 100 use_sim_time: true joint_state_broadcaster: type: joint_state_broadcaster/JointStateBroadcaster cartpole_controller: type: joint_trajectory_controller/JointTrajectoryController cartpole_controller: ros__parameters: joints: - slide_to_joint command_interfaces: - position state_interfaces: - position state_publish_rate: 100.0这里有个关键点:controller_manager段落和cartpole_controller段落是平级的,它们分别对应 controller_manager 节点的参数和具体控制器的参数。很多人把cartpole_controller写进了controller_manager下面,结果控制器加载后完全读不到joints配置,报一些莫名其妙的 “joint mismatch” 错误。
关节名slide_to_joint要和你 SDF 模型里的关节名完全一致,大小写敏感。command_interfaces和state_interfaces则决定了这个控制器是发位置指令还是速度、力矩指令。如果你模型里的关节是 revolute 的,接口类型也可以是 effort,这块要按自己模型的实际关节类型来设计。
我建议的第一个测试配置不要搞太复杂,先只跑一个joint_state_broadcaster,确认关节状态能发出来,再往上加控制器。这样能帮你把“插件问题”和“控制器配置问题”分开排查,不然所有问题混在一起,你会疯的。
4.3 完整启动流程与 ros2 control 验证命令
环境配好之后,整个启动顺序我推荐这样来:
先在终端 A 里编译并 source 工作空间:
cd ~/gz_ros2_control_ws colcon build --symlink-install --cmake-args -DGZ_SIM_VER=8 -DCMAKE_BUILD_TYPE=Release source install/setup.bash如果编译报错说你缺少 gz-ros2-control 的依赖,可以先安装:
sudo apt install ros-humble-ros2-control ros-humble-ros2-controllers ros-humble-hardware-interface终端 A 继续启动 Gazebo:
gz sim -s -r your_robot.sdf-s表示 headless 模式,-r表示开始运行,如果你要可视化界面,把-s去掉即可。
打开终端 B,source 同样的环境后,先确认插件节点起来了:
ros2 node list正常情况下你会看到/gz_ros2_control或者/controller_manager这样的节点。如果看到多个类似名字的节点,说明插件被加载了多次,基本都是 SDF 里 plugin 写重了。
然后查看控制器状态:
ros2 control list_controllers如果新加载的 controller 显示unconfigured,手动加载并激活:
ros2 control load_controller joint_state_broadcaster ros2 control set_controller_state joint_state_broadcaster active激活之后在终端 C 里订阅关节状态:
ros2 topic echo /joint_states能刷出来数据,说明 gz_ros2_control 和 ros2_control 整条链路已经打通了。之后再逐个加载你真正要用的轨迹控制器。
这里我强烈安利一个调试命令ros2 control list_hardware_interfaces,它会直接列出当前系统里所有可用的 command 和 state 接口。看到slide_to_joint/position [claimed]就说明插件已经正确把关节接口暴露给了 controller,比瞎猜有效得多。
5. 踩坑实录与快速排查速查表
5.1 速查表:错误现象 + 根因 + 处理方法
我把这轮调试中遇到的高频问题整理成了一张表,以后遇到直接从表里找答案就行。
| 错误现象 | 根本原因 | 处理方法 |
|---|---|---|
| Failed to load plugin libgz_ros2_control.so | 插件路径没被 Gazebo 找到 | 设置 GZ_SIM_SYSTEM_PLUGIN_PATH,指向 install/lib 或 /opt/ros/humble/lib |
| Plugin loaded but no /controller_manager | ROS2 环境未 source,或插件写在 world 级别 | 在启动终端的 bashrc 里 source ROS2;把 plugin 挪到 model 级别 |
| CMake error: Could not find gz-sim7 | CMake 默认找错版本 | 编译时加 -DGZ_SIM_VER=8,或改 CMakeLists 里的 GZ_SIM_VER |
| ros2: command not found | 缺少 ros2_control 命令行工具 | 安装 ros-humble-ros2-control 和 ros-humble-ros2-controllers |
| Controller list empty | controllers.yaml 没被加载 | 检查 SDF 里 parameters 路径,用绝对路径测试 |
| Controller loads but stuck inactive | use_sim_time 不同步或类型名写错 | 确认 use_sim_time: true,检查 controller type 是否带命名空间 |
| join t_states 无数据 | joint 名不匹配或接口类型不正确 | 用 ros2 control list_hardware_interfaces 核对关节接口 |
| 运行中 crash,依赖 gz-sim7 和 gz-sim8 混合 | 系统里多版本 gz-sim 共存 | 清理 GZ_SIM_SYSTEM_PLUGIN_PATH,卸载旧版 gz-sim |
5.2 几条独家经验
第一,不要迷信 apt 版本的 gz_ros2_control。Humble + Harmonic 这个组合天生就不是“apt install 一把梭”能搞定的组合,老老实实源码编译,并且把-DGZ_SIM_VER=8写进自己的编译脚本里,避免每次手动敲。
第二,善用GZ_SIM_RESOURCE_PATH。如果你的自定义模型不在默认资源路径下,Gazebo 会提示找不到模型。设置这个变量指向你的模型目录,能省去绝对路径带来的各种迷之 bug:
export GZ_SIM_RESOURCE_PATH=$HOME/ros2_ws/src/my_robot/models:$HOME/.gazebo/models export GZ_SIM_SYSTEM_PLUGIN_PATH=$HOME/gz_ros2_control_ws/install/lib:/opt/ros/humble/lib第三,善用ros2 param dump /controller_manager。这个命令能看到 controller_manager 实际加载的所有参数,比任何日志都直观。如果joint_state_broadcaster的 type 参数不存在,说明 yaml 压根没读进来,别再去改代码了,回去检查路径。
第四,如果你在同一个模型里放了多个执行器,比如差速轮加机械臂,记得每个执行器对应的 joint 要在 controllers.yaml 里正确分组。一个joint_trajectory_controller默认只会管理它joints列表里定义的关节,没写进去的关节不会动,这是初学者最容易忽略的“不是 bug 的 bug”。
第五,关于话题频率。如果joint_states刷新的频率和你设定的update_rate对不上,先看看有没有开use_sim_time。在仿真环境里,所有节点通常都要以仿真时钟为准,否则会出现控制器在真实时间下运行、但仿真时间停滞不前的诡异现象。我在第一次跑通之后就是因为use_sim_time设置不一致,导致轨迹跟踪误差大得离谱。
最后再分享一个小技巧。当你实在不知道插件有没有被加载时,可以在 SDF 的 plugin 标签里临时加一个日志参数,比如给 plugin 配置一个非法的参数名,然后看启动日志有没有相关输出。Gazebo Sim 对未知参数通常会给 warning,如果 warning 里出现了你加的参数名,说明插件确实在跑,只是 ROS2 侧出了问题;如果连 warning 都没有,那插件可能压根没被加载。这种“故意搞破坏”的定位方法,我试过好几次,还挺管用的。
根据我个人的实际体会,Humble 和 Harmonic 这对组合虽然折腾,但调通之后确实比老版本舒服很多,无论是渲染效果还是物理引擎性能,都比 Gazebo Classic 上了一个台阶。如果后面你的项目要上多传感器仿真或者视觉导航,Harmonic 配合 ros_gz 的桥接优势会更明显。也希望这篇记录能帮你少走点弯路,省下来的时间多喝杯咖啡吧。