Ignition Gazebo(Gazebo Fortress)
Ignition Gazebo(Gazebo Fortress,基於ROS2 Humble)
** 建議使用ROS2 Jazzy的,這個ROS2 Humble的Gazebo像過渡版本,代碼可能和後續版本又衝突! ** (該版本教程已經停更,以後會專注於更新ROS2 Jazzy及之後的版本,也就是更新Gazebo Harmonic及之後的版本。)
詳見Gz Sim教程
Ign Gazebo安裝與運行
Gazebo每個版本的變化都很大。
特別是ROS1用的老版Gazebo(黑色界面)和ROS2用的新版Gazebo(白色界面)。
ROS2的不同版本的Gazebo跨度也很大,比如Humble和Jazzy及Jazzy之後的版本之間很多標籤區別很大。
本文使用humble版本(即Ignition Gazebo)當做教程。
當然,為了兼容以後的Gazebo,在下方也會有教程教你如何從ign gazebo遷移到gazebo sim(最最最新版gazebo)。
Ignition Gazebo 是 ROS2 中使用的全新機器人仿真工具,它是 Gazebo 的升級版本。在Humble他還叫Ignition Gazebo(也叫Gazebo Fortress),在Jazzy中叫Gazebo Harmonic(去掉了Ignition的名字)(https://community.gazebosim.org/t/a-new-era-for-gazebo/1356)。它具備更好的性能和可用性,並通過緊密集成 ROS2 來提供強大的仿真環境。Ignition Gazebo 支持各種機器人平台和傳感器,並提供靈活的配置選項和易於使用的界面。它的物理引擎和傳感器模型可以幫助開發人員進行機器人系統的開發、測試和驗證。無論是研究還是教育,Ignition Gazebo 都是一個強大的工具。
如果想從Ignition Gazebo(ROS2 Humble)遷移到Gazebo(ROS2 Jazzy),請往下翻翻,下面有一節是講如何遷移的。
https://docs.ros.org/en/humble/Tutorials/Advanced/Simulators/Gazebo/Gazebo.html
下面這個網站是官方教程(ROS2 Humble的Ignition Gazebo Fortress):
https://gazebosim.org/docs/fortress/getstarted/
https://gazebosim.org/docs/fortress/library_reference_nav/
源碼:https://github.com/gazebosim/docs/blob/master/fortress/tutorials
安裝
Ignition Gazebo 是不依賴於ROS2的一個獨立的項目,可以獨自安裝。但是如果安裝了ROS2,在ROS2存儲庫中已經集成了對應版本的 Ignition Gazebo,可以調用如下指令直接安裝:
# 通用命令
sudo apt install ros-${ROS_DISTRO}-ros-gz
# Humble版本
sudo apt install ros-humble-ros-gz
# Jazzy版本
sudo apt install ros-jazzy-ros-gz

運行
Ignition Gazebo 安裝完畢之後,可以通過兩種方式啓動。
方式1,以Ignition Gazebo 的方式啓動,指令如下:
# Humble版本
ign gazebo
# Jazzy版本
gz sim
方式2,以ROS2的方式 啓動,指令如下 :
ros2 launch ros_gz_sim gz_sim.launch.py
二者運行結果一致,如下圖所示:在彈出窗口中,選擇仿真環境然後點擊run按鈕即可運行。

界面介紹
接下來以Empty仿真環境為例,介紹一下Ignition Gazebo的界面組成。

注意:如果你的Gazebo不卡,但是Ignition Gazebo巨卡的話,請確認Ignition Gazebo是以獨顯打開的,而不是核顯。
如果不會切換應用顯卡,可以直接把核顯關閉掉,從混合輸出切換為獨立顯卡輸出。
工具欄
- 頂部的工具欄包含兩個按鈕,左側的文件菜單按鈕(水平條紋)和右側的插件按鈕(垂直省略號)。
- 文件菜單按鈕(水平條紋)

- 文件菜單按鈕包含將仿真環境保存到文件、保存和加載界面配置以及自定義界面樣式等設置。
- 右側的插件按鈕(垂直省略號)

- 插件按鈕列出了所有可用的插件。點擊後會彈出插件列表,向下滾動此列表以查看所有插件。 當選擇一個時,其界面將出現在右側面板中。
3D視窗
- 左上方工具欄包含多種幾何體(球體、框體、圓柱體)按鈕和變換控件。通過集合體按鈕可以直接將盒子、球體或圓柱體模型插入仿真環境。只需單擊要插入的形狀,然後將其放入環境中。該形狀將自動捕捉到地平面上。


- 主視圖會顯示仿真環境,我們可以通過鼠標以不同方式來導航場景,相關操作如下:
左键单击:选择实体
右键单击:打开带有选项的菜单:
Move to:移动到以实体为中心的场景
Follow:选择一个实体让视图保持居中,无论是移动还是平移
Remove:从模拟中删除实体
Copy:复制实体
Past: 粘贴实体
View:显示实体的重心(Center of Mass)、碰撞边界(Collisions)、惯性(Inertia)、
关节(Joints)、坐标系(Frames)、透明度(Transparent)、线框(Wireframe)等属性
左键单击并拖动:在场景中平移
右键单击并拖动:放大和缩小
滚轮向前/向后:放大和缩小
滚轮单击并拖动:旋转场景
- 想移動這個球,需要點左上角的移動模式,再左鍵單擊選中物體


- 在視窗的底部,從左到右分別是是播放、步長按鈕和實時因子(Real-Time Factor,RTF)。點擊播放按鈕將開始運行仿真環境, 再次點擊可以暫停運行。步長按鈕用於設置仿真時間的離散單位,可以通過將鼠標懸停在按鈕上來自定義步長。實時因子表示仿真運行速度相對於真實時間的比例。

右側面板
右側面板用於顯示插件,當前仿真環境默認包含兩個插件:Model和Entity Tree。

- Entity Tree 中會顯示仿真環境中的實體列表;
- 點擊Entity Tree中的實體後,可以在Model中顯示該實體的相關信息。
- 也可以按住 Ctrl 並單擊以選擇多個實體;
- 還可以右鍵單擊任何插件以打開基本設置或關閉。

在Ignition Gazebo中內置了許多插件,可以點擊工具欄的右側按鈕自行添加,比如:可以選擇 Grid Config 插件調整世界網格的特徵,包括單元格大小、網格位置、單元格計數、或顏色等。
後期隨着應用的深入,也會陸續介紹其他一些插件。


與ROS2集成
本節將介紹如何實現Ignition Gazebo與ROS2的集成,以實現二者之間的交互,比如,可以通過ROS2的鍵盤控制節點控制機器人運動,並且在rviz2中顯示機器人的里程計(odom)數據。其流程大致如下:
- 啓動 Ignition Gazebo 仿真環境;
- 通過 ros_gz_bridge 建立 ROS2 與 Ignition Gazebo 的連接;
- 啓動 ROS2 相關節點實現與 Ignition Gazebo 的數據收發。
Ignition Gazebo與ROS2的的所有集成實現,基本都遵循上述流程。
啓動仿真環境
在 Ignition Gazebo 安裝時,已經內置了一些仿真環境,直接啓動即可。在此我們可以使用名為visualize_lidar.sdf的仿真文件,該文件對應的仿真環境中包括了差速機器人以及激光雷達的仿真。啓動指令如下:
ign gazebo -v 4 -r visualize_lidar.sdf
#或者
gz sim -v 4 -r visualize_lidar.sdf
或者也可以以ROS2 launch的方式啓動,指令如下:
ros2 launch ros_gz_sim gz_sim.launch.py gz_args:="-v 4 -r visualize_lidar.sdf" # 启动状态
兩種方式本質相同,都是啓動了Ignition Gazebo並且加載了visualize_lidar.sdf文件。

建立連接
雖然仿真環境中的機器人已經配置了運動控制插件,可以通過/model/vehicle_blue/cmd_vel話題訂閲速度指令並運動,但是Ignition Gazebo與ROS2中的消息格式並不一致,所以還需要通過ros_gz_bridge這一橋接功能包,實現二者之間消息的轉換,調用指令如下:
ros2 run ros_gz_bridge parameter_bridge /model/vehicle_blue/cmd_vel@geometry_msgs/msg/Twist]gz.msgs.Twist
通過該指令可以將發佈在/model/vehicle_blue/cmd_vel話題上的geometry_msgs/msg/Twist類型的ROS2消息轉換成可以被Ignition Gzebo識別的gz.msgs.Twist類型的消息。
啓動ROS2節點
啓動ROS2的鍵盤控制節點,並將話題重映射為/model/vehicle_blue/cmd_vel,指令如下:
ros2 run teleop_twist_keyboard teleop_twist_keyboard --ros-args -r /cmd_vel:=/model/vehicle_blue/cmd_vel
接下來就可以使用鍵盤控制機器人運動了。

ros_gz_bridge
ros_gz_bridge是連接ROS2與Ignition Gazebo的橋樑,ROS2與Ignition Gazebo使用的消息並不兼容,必須通過ros_gz_bridge進行轉換。
ros_gz_bridge使用語法
ROS2與Ignition Gazebo的橋接是通過ros_gz_bridge包中的parameter_bridge節點實現,其使用語法如下:
parameter_bridge [<topic@ROS2_type@Gz_type> ..] [<service@ROS2_srv_type[@Gz_req_type@Gz_rep_type]> ..]
在話題Topic中, 第一個@ 符號是話題名稱和消息類型的 分隔符 。
第一個@符號後面是ROS消息類型。
ROS消息類型後面是@、[或]符號:
- @ 表示雙向橋接;
- [ 表示從Ignition Gazebo到ROS的橋接;
- ] 表示從ROS到Ignition Gazebo的橋接。
方向符號後是Gazebo Transport消息類型。
(兩個@不是同一個含義)
在服務Service中, 第一個@ 符號是服務名稱和類型的 分隔符 。
第一個@符號後面是ROS服務類型。可以選擇地包括Gazebo請求和響應類型,在它們之間用@符號分隔。
僅 支持將Gazebo服務公開為ROS服務,即ROS服務將請求轉發到Gazebo服務,然後將響應轉發回ROS客户端。
雙向橋接示例:
parameter_bridge /chatter@std_msgs/String@gz.msgs.StringMsg
從Gazebo到ROS的橋接示例:
parameter_bridge /chatter@std_msgs/String[gz.msgs.StringMsg
從ROS到Gazebo的橋接示例:
parameter_bridge /chatter@std_msgs/String]gz.msgs.StringMsg
服務橋接示例:
parameter_bridge /world/default/control@ros_gz_interfaces/srv/ControlWorld
或者:
parameter_bridge /world/default/control@ros_gz_interfaces/srv/ControlWorld@gz.msgs.WorldControl@gz.msgs.Boolean
也可以運行ros2 run ros_gz_bridge parameter_bridge -h指令查看官方説明文檔。
ros_gz_bridge支持的消息類型
以下是ROS2與Ignition Gazebo中話題消息類型對應表:
| ROS2消息類型 | Gazebo Transport 類型 |
|---|---|
| builtin_interfaces/msg/Time | gz.msgs.Time |
| geometry_msgs/msg/Point | gz.msgs.Vector3d |
| geometry_msgs/msg/Pose | gz.msgs.Pose |
| geometry_msgs/msg/PoseArray | gz.msgs.Pose_V |
| geometry_msgs/msg/PoseStamped | gz.msgs.Pose |
| geometry_msgs/msg/PoseWithCovariance | gz.msgs.PoseWithCovariance |
| geometry_msgs/msg/Quaternion | gz.msgs.Quaternion |
| geometry_msgs/msg/Transform | gz.msgs.Pose |
| geometry_msgs/msg/TransformStamped | gz.msgs.Pose |
| geometry_msgs/msg/Twist | gz.msgs.Twist |
| geometry_msgs/msg/TwistWithCovariance | gz.msgs.TwistWithCovariance |
| geometry_msgs/msg/TwistWithCovarianceStamped | gz.msgs.TwistWithCovariance |
| geometry_msgs/msg/Vector3 | gz.msgs.Vector3d |
| geometry_msgs/msg/Wrench | gz.msgs.Wrench |
| geometry_msgs/msg/WrenchStamped | gz.msgs.Wrench |
| nav_msgs/msg/Odometry | gz.msgs.Odometry |
| nav_msgs/msg/Odometry | gz.msgs.OdometryWithCovariance |
| rcl_interfaces/msg/ParameterValue | gz.msgs.Any |
| ros_gz_interfaces/msg/Altimeter | gz.msgs.Altimeter |
| ros_gz_interfaces/msg/Contact | gz.msgs.Contact |
| ros_gz_interfaces/msg/Contacts | gz.msgs.Contacts |
| ros_gz_interfaces/msg/Dataframe | gz.msgs.Dataframe |
| ros_gz_interfaces/msg/Entity | gz.msgs.Entity |
| ros_gz_interfaces/msg/Float32Array | gz.msgs.Float_V |
| ros_gz_interfaces/msg/GuiCamera | gz.msgs.GUICamera |
| ros_gz_interfaces/msg/JointWrench | gz.msgs.JointWrench |
| ros_gz_interfaces/msg/Light | gz.msgs.Light |
| ros_gz_interfaces/msg/SensorNoise | gz.msgs.SensorNoise |
| ros_gz_interfaces/msg/StringVec | gz.msgs.StringMsg_V |
| ros_gz_interfaces/msg/TrackVisual | gz.msgs.TrackVisual |
| ros_gz_interfaces/msg/VideoRecord | gz.msgs.VideoRecord |
| ros_gz_interfaces/msg/WorldControl | gz.msgs.WorldControl |
| rosgraph_msgs/msg/Clock* | gz.msgs.Clock* |
| sensor_msgs/msg/BatteryState | gz.msgs.BatteryState |
| sensor_msgs/msg/CameraInfo | gz.msgs.CameraInfo |
| sensor_msgs/msg/FluidPressure | gz.msgs.FluidPressure |
| sensor_msgs/msg/Image | gz.msgs.Image |
| sensor_msgs/msg/Imu | gz.msgs.IMU |
| sensor_msgs/msg/JointState | gz.msgs.Model |
| sensor_msgs/msg/Joy | gz.msgs.Joy |
| sensor_msgs/msg/LaserScan | gz.msgs.LaserScan |
| sensor_msgs/msg/MagneticField | gz.msgs.Magnetometer |
| sensor_msgs/msg/NavSatFix | gz.msgs.NavSat |
| sensor_msgs/msg/PointCloud2 | gz.msgs.PointCloudPacked |
| std_msgs/msg/Bool | gz.msgs.Boolean |
| std_msgs/msg/ColorRGBA | gz.msgs.Color |
| std_msgs/msg/Empty | gz.msgs.Empty |
| std_msgs/msg/Float32 | gz.msgs.Float |
| std_msgs/msg/Float64 | gz.msgs.Double |
| std_msgs/msg/Header | gz.msgs.Header |
| std_msgs/msg/Int32 | gz.msgs.Int32 |
| std_msgs/msg/String | gz.msgs.StringMsg |
| std_msgs/msg/UInt32 | gz.msgs.UInt32 |
| tf2_msgs/msg/TFMessage | gz.msgs.Pose_V |
| trajectory_msgs/msg/JointTrajectory | gz.msgs.JointTrajectory |
以及服務消息類型對應表:
| ROS2消息類型 | Gazebo 請求 | Gazebo 響應 |
|---|---|---|
| ros_gz_interfaces/srv/ControlWorld | gz.msgs.WorldControl | gz.msgs.Boolean |
與ROS2集成優化
在 Ignition Gazebo與ROS2集成 實現中需要在終端中使用不同的指令啓動不同模塊,該流程實現稍顯複雜,本節將介紹如何以launch文件的方式進行優化。
新建功能包
請首先調用如下指令創建一個功能包:
ros2 pkg create demo_gazebo_sim

添加目錄
在新建的功能包下添加目錄: launch、rviz、world。並在CmakeLists.txt中添加如下代碼:
install(DIRECTORY rviz world launch DESTINATION share/${PROJECT_NAME})
launch目錄用於存儲launch文件,rviz目錄由於存儲rviz2的配置文件,而world目錄則用於存儲Ignition Gazebo仿真環境的相關文件。

rviz目錄中生成rviz2的配置文件
啓動 rviz2,直接將默認配置保存至當前功能包的rviz目錄,保存文件命名為sim.rviz。

複製world文件
在ignition安裝路徑下的worlds目錄(/usr/share/ignition/ignition-gazebo6/worlds)中複製visualize_lidar.sdf文件至world目錄。
如果該路徑下沒有,那可能在ROS的安裝路徑下:
/opt/ros/jazzy/opt/gz_sim_vendor/share/gz/gz-sim8/worlds/
如果還沒有的話,手動查找一下:
sudo find / -name "visualize_lidar.sdf"

編寫launch文件
launch目錄下新建launch文件gazebo_sim_demo.launch.py,並輸入如下內容:
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.actions import IncludeLaunchDescription
from launch.conditions import IfCondition
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
this_pkg = get_package_share_directory('demo_gazebo_sim')
pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim')
world_file = os.path.join(this_pkg,'world','visualize_lidar.sdf')
gz_sim = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py')),
launch_arguments={
'gz_args': '-r ' + world_file
}.items(),
)
# RViz
rviz = Node(
package='rviz2',
executable='rviz2',
arguments=['-d', os.path.join(this_pkg, 'rviz', 'sim.rviz')],
condition=IfCondition(LaunchConfiguration('rviz'))
)
# Bridge
bridge = Node(
package='ros_gz_bridge',
executable='parameter_bridge',
arguments=['/model/vehicle_blue/cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist',
'/model/vehicle_blue/odometry@nav_msgs/msg/Odometry@gz.msgs.Odometry',
'/model/vehicle_blue/tf@tf2_msgs/msg/TFMessage[gz.msgs.Pose_V',
],
parameters=[{'qos_overrides./model/vehicle_blue.subscriber.reliability': 'reliable'}],
remappings=[
('/model/vehicle_blue/tf', '/tf'),
('/model/vehicle_blue/cmd_vel','cmd_vel')
],
output='screen'
)
return LaunchDescription([
gz_sim,
DeclareLaunchArgument('rviz', default_value='true',
description='Open RViz.'),
bridge,
rviz
])
該launch文件中,啓動了Ignition Gazebo仿真環境、通過ros_gz_bridge建立了仿真與ROS2的連接,並且啓動了rviz2節點。其中建立連接時,實現了速度指令、里程計以及座標變換等消息的轉換。

構建
終端中進入當前工作空間,編譯功能包:
colcon build --packages-select demo_gazebo_sim

執行
終端中進入當前工作空間,調用如下指令執行launch文件:
. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_demo.launch.py
新開終端,啓動鍵盤控制節點:
ros2 run teleop_twist_keyboard teleop_twist_keyboard
再配置rviz2,將Fixed Frame設置為vehicle_blue/odom,添加TF插件,添加Odometry插件並將話題設置為/model/vehicle_blue/odometry,當通過鍵盤控制發送速度指令時,仿真環境的機器人開始運動,並且在rviz2中可以回顯座標變換以及里程計等消息。

仿真環境創建 SDF文件
前面幾節內容我們使用的是Ignition Gazebo內置的仿真環境,本節開始將介紹如何自行搭建仿真環境。本節案例將仿真一個長10m寬5m的矩形房間。該案例可以先啓動Ignition Gazebo以拖拽的方式搭建仿真環境,然後再修改仿真環境對應的文件以調整細節。
SDF、URDF 和 Xacro 的關係:
- URDF 和 SDF 的區別:
- 複雜性: SDF 支持的功能更強大,能夠描述完整的仿真環境;URDF 更適合定義機器人模型。
- 用途: URDF 是 ROS 的標準;SDF 是 Gazebo 的標準。
- 物理引擎支持: URDF 通過插件支持 Gazebo;SDF 原生支持 Gazebo。
- 格式轉換: URDF 可以轉換為 SDF(通過 ROS 提供的工具
gz sdf -p)。
- Xacro 的作用:
- Xacro 是 URDF 的生成工具,幫助用户高效編寫 URDF 文件,但它與 SDF 無直接關係。
實踐建議
- 在 Gazebo 仿真中: 如果你用的是 ROS 2 和 Gazebo,可以直接使用 SDF 文件,功能更強大。
- 在 ROS 中: 如果主要用於機器人控制和規劃,推薦使用 URDF 或由 Xacro 生成的 URDF。
- 兩者結合: 使用 URDF 進行控制,使用 SDF 進行仿真。例如,使用 URDF 定義機器人結構後,藉助 Gazebo 插件將其轉換為 SDF。
示例對比
URDF 示例:
<robot name="example_robot">
<link name="base_link">
<inertial>
<mass value="1.0" />
<inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0" />
</inertial>
</link>
</robot>
Xacro 示例(生成 URDF):
<xacro:robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="example_robot">
<xacro:macro name="base_link" params="mass">
<link name="base_link">
<inertial>
<mass value="${mass}" />
<inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0" />
</inertial>
</link>
</xacro:macro>
<xacro:base_link mass="1.0" />
</xacro:robot>
SDF 示例:
<sdf version="1.6">
<model name="example_robot">
<link name="base_link">
<inertial>
<mass>1.0</mass>
<inertia>
<ixx>1.0</ixx>
<iyy>1.0</iyy>
<izz>1.0</izz>
</inertia>
</inertial>
</link>
</model>
</sdf>
1.創建sdf文件
首先請調用指令ign gazebo啓動Gazebo,選擇Empty仿真環境,然後添加立方體,每一個立方體都對應一堵牆。
上下左右立方體box、box_1、box_2、box_3對應的座標分別為(5.0,0.0,0.5)、(-5.0,0.0,0.5)、(0.0,2.5,0.5)、(0.0,-2.5,0.5)。
(以上座標是指X,Y,Z座標,沒有旋轉度)
保存文件到功能包的world目錄下,保存的文件名稱需要以.sdf為後綴,此處文件名為house.sdf。


<sdf version='1.9'>
<world name='empty'>
<physics name='1ms' type='ignored'>
<max_step_size>0.001</max_step_size>
<real_time_factor>1</real_time_factor>
<real_time_update_rate>1000</real_time_update_rate>
</physics>
<plugin name='gz::sim::systems::Physics' filename='ignition-gazebo-physics-system'/>
<plugin name='gz::sim::systems::UserCommands' filename='ignition-gazebo-user-commands-system'/>
<plugin name='gz::sim::systems::SceneBroadcaster' filename='ignition-gazebo-scene-broadcaster-system'/>
<plugin name='gz::sim::systems::Contact' filename='ignition-gazebo-contact-system'/>
<gravity>0 0 -9.8</gravity>
<magnetic_field>6e-06 2.3e-05 -4.2e-05</magnetic_field>
<atmosphere type='adiabatic'/>
<scene>
<ambient>0.4 0.4 0.4 1</ambient>
<background>0.7 0.7 0.7 1</background>
<shadows>true</shadows>
</scene>
<model name='ground_plane'>
<static>true</static>
<link name='link'>
<collision name='collision'>
<geometry>
<plane>
<normal>0 0 1</normal>
<size>100 100</size>
</plane>
</geometry>
<surface>
<friction>
<ode/>
</friction>
<bounce/>
<contact/>
</surface>
</collision>
<visual name='visual'>
<geometry>
<plane>
<normal>0 0 1</normal>
<size>100 100</size>
</plane>
</geometry>
<material>
<ambient>0.8 0.8 0.8 1</ambient>
<diffuse>0.8 0.8 0.8 1</diffuse>
<specular>0.8 0.8 0.8 1</specular>
</material>
</visual>
<pose>0 0 0 0 -0 0</pose>
<inertial>
<pose>0 0 0 0 -0 0</pose>
<mass>100</mass>
<inertia>
<ixx>1</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>1</iyy>
<iyz>0</iyz>
<izz>1</izz>
</inertia>
</inertial>
<enable_wind>false</enable_wind>
</link>
<pose>0 0 0 0 -0 0</pose>
<self_collide>false</self_collide>
</model>
<model name='box'>
<pose>5.0 0 0.5 -0 0 0</pose>
<link name='box_link'>
<inertial>
<inertia>
<ixx>16.666</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>16.666</iyy>
<iyz>0</iyz>
<izz>16.666</izz>
</inertia>
<mass>100</mass>
<pose>0 0 0 0 -0 0</pose>
</inertial>
<collision name='box_collision'>
<geometry>
<box>
<size>0.1 5 1</size>
</box>
</geometry>
<surface>
<friction>
<ode/>
</friction>
<bounce/>
<contact/>
</surface>
</collision>
<visual name='box_visual'>
<geometry>
<box>
<size>0.1 5 1</size>
</box>
</geometry>
<material>
<ambient>0.3 0.3 0.3 1</ambient>
<diffuse>0.7 0.7 0.7 1</diffuse>
<specular>1 1 1 1</specular>
</material>
</visual>
<pose>0 0 0 0 -0 0</pose>
<enable_wind>false</enable_wind>
</link>
<static>false</static>
<self_collide>false</self_collide>
</model>
<model name='box_0'>
<pose>-5.0 -0 0.50000 -0 0 0</pose>
<link name='box_link'>
<inertial>
<inertia>
<ixx>16.666</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>16.666</iyy>
<iyz>0</iyz>
<izz>16.666</izz>
</inertia>
<mass>100</mass>
<pose>0 0 0 0 -0 0</pose>
</inertial>
<collision name='box_collision'>
<geometry>
<box>
<size>0.1 5 1</size>
</box>
</geometry>
<surface>
<friction>
<ode/>
</friction>
<bounce/>
<contact/>
</surface>
</collision>
<visual name='box_visual'>
<geometry>
<box>
<size>0.1 5 1</size>
</box>
</geometry>
<material>
<ambient>0.3 0.3 0.3 1</ambient>
<diffuse>0.7 0.7 0.7 1</diffuse>
<specular>1 1 1 1</specular>
</material>
</visual>
<pose>0 0 0 0 -0 0</pose>
<enable_wind>false</enable_wind>
</link>
<static>false</static>
<self_collide>false</self_collide>
</model>
<model name='box_1'>
<pose>-0 -2.5 0.5 -0 -0 -0</pose>
<link name='box_link'>
<inertial>
<inertia>
<ixx>16.666</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>16.666</iyy>
<iyz>0</iyz>
<izz>16.666</izz>
</inertia>
<mass>100</mass>
<pose>0 0 0 0 -0 0</pose>
</inertial>
<collision name='box_collision'>
<geometry>
<box>
<size>10 0.1 1</size>
</box>
</geometry>
<surface>
<friction>
<ode/>
</friction>
<bounce/>
<contact/>
</surface>
</collision>
<visual name='box_visual'>
<geometry>
<box>
<size>10 0.1 1</size>
</box>
</geometry>
<material>
<ambient>0.3 0.3 0.3 1</ambient>
<diffuse>0.7 0.7 0.7 1</diffuse>
<specular>1 1 1 1</specular>
</material>
</visual>
<pose>0 0 0 0 -0 0</pose>
<enable_wind>false</enable_wind>
</link>
<static>false</static>
<self_collide>false</self_collide>
</model>
<model name='box_2'>
<pose>-0 2.5 0.5 0 -0 -0</pose>
<link name='box_link'>
<inertial>
<inertia>
<ixx>16.666</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>16.666</iyy>
<iyz>0</iyz>
<izz>16.666</izz>
</inertia>
<mass>100</mass>
<pose>0 0 0 0 -0 0</pose>
</inertial>
<collision name='box_collision'>
<geometry>
<box>
<size>10 0.1 1</size>
</box>
</geometry>
<surface>
<friction>
<ode/>
</friction>
<bounce/>
<contact/>
</surface>
</collision>
<visual name='box_visual'>
<geometry>
<box>
<size>10 0.1 1</size>
</box>
</geometry>
<material>
<ambient>0.3 0.3 0.3 1</ambient>
<diffuse>0.7 0.7 0.7 1</diffuse>
<specular>1 1 1 1</specular>
</material>
</visual>
<pose>0 0 0 0 -0 0</pose>
<enable_wind>false</enable_wind>
</link>
<static>false</static>
<self_collide>false</self_collide>
</model>
<light name='sun' type='directional'>
<pose>0 0 10 0 -0 0</pose>
<cast_shadows>true</cast_shadows>
<intensity>1</intensity>
<direction>-0.5 0.1 -0.9</direction>
<diffuse>0.8 0.8 0.8 1</diffuse>
<specular>0.2 0.2 0.2 1</specular>
<attenuation>
<range>1000</range>
<linear>0.01</linear>
<constant>0.90000000000000002</constant>
<quadratic>0.001</quadratic>
</attenuation>
<spot>
<inner_angle>0</inner_angle>
<outer_angle>0</outer_angle>
<falloff>0</falloff>
</spot>
</light>
</world>
</sdf>
2.修改sdf文件
修改sdf文件,調整立方體的尺寸,實現牆體的合圍。在sdf文件中,四個立方體分別對應了四個<model>標籤,其name屬性分別為box、box_1、box_2、box_3,將box和box_1中的<size>1 1 1</size>修改為<size>0.1 5 1</size>,將box_2和box_3中的<size>1 1 1</size>修改為<size>10 0.1 1</size>(注意:每個<model>標籤下,都包含兩個<size>標籤,分別位於<collision>標籤和<visual>標籤下,兩個<size>標籤內容都需要修改)。
修改後與的house.sdf文件內容如下:
<sdf version='1.9'>
<world name='empty'>
<physics name='1ms' type='ignored'>
<max_step_size>0.001</max_step_size>
<real_time_factor>1</real_time_factor>
<real_time_update_rate>1000</real_time_update_rate>
</physics>
<plugin name='ign::gazebo::systems::Physics' filename='ignition-gazebo-physics-system'/>
<plugin name='ign::gazebo::systems::UserCommands' filename='ignition-gazebo-user-commands-system'/>
<plugin name='ign::gazebo::systems::SceneBroadcaster' filename='ignition-gazebo-scene-broadcaster-system'/>
<plugin name='ign::gazebo::systems::Contact' filename='ignition-gazebo-contact-system'/>
<gravity>0 0 -9.8</gravity>
<magnetic_field>6e-06 2.3e-05 -4.2e-05</magnetic_field>
<atmosphere type='adiabatic'/>
<scene>
<ambient>0.4 0.4 0.4 1</ambient>
<background>0.7 0.7 0.7 1</background>
<shadows>true</shadows>
</scene>
<model name='ground_plane'>
<static>true</static>
<link name='link'>
<collision name='collision'>
<geometry>
<plane>
<normal>0 0 1</normal>
<size>100 100</size>
</plane>
</geometry>
<surface>
<friction>
<ode/>
</friction>
<bounce/>
<contact/>
</surface>
</collision>
<visual name='visual'>
<geometry>
<plane>
<normal>0 0 1</normal>
<size>100 100</size>
</plane>
</geometry>
<material>
<ambient>0.8 0.8 0.8 1</ambient>
<diffuse>0.8 0.8 0.8 1</diffuse>
<specular>0.8 0.8 0.8 1</specular>
</material>
</visual>
<pose>0 0 0 0 -0 0</pose>
<inertial>
<pose>0 0 0 0 -0 0</pose>
<mass>100</mass>
<inertia>
<ixx>1</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>1</iyy>
<iyz>0</iyz>
<izz>1</izz>
</inertia>
</inertial>
<enable_wind>false</enable_wind>
</link>
<pose>0 0 0 0 -0 0</pose>
<self_collide>false</self_collide>
</model>
<model name='box'>
<pose>5.0 0 0.5 -0 0 0</pose>
<link name='box_link'>
<inertial>
<inertia>
<ixx>16.666</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>16.666</iyy>
<iyz>0</iyz>
<izz>16.666</izz>
</inertia>
<mass>100</mass>
<pose>0 0 0 0 -0 0</pose>
</inertial>
<collision name='box_collision'>
<geometry>
<box>
<size>0.1 5 1</size>
</box>
</geometry>
<surface>
<friction>
<ode/>
</friction>
<bounce/>
<contact/>
</surface>
</collision>
<visual name='box_visual'>
<geometry>
<box>
<size>0.1 5 1</size>
</box>
</geometry>
<material>
<ambient>0.3 0.3 0.3 1</ambient>
<diffuse>0.7 0.7 0.7 1</diffuse>
<specular>1 1 1 1</specular>
</material>
</visual>
<pose>0 0 0 0 -0 0</pose>
<enable_wind>false</enable_wind>
</link>
<static>false</static>
<self_collide>false</self_collide>
</model>
<model name='box_0'>
<pose>-5.0 -0 0.50000 -0 0 0</pose>
<link name='box_link'>
<inertial>
<inertia>
<ixx>16.666</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>16.666</iyy>
<iyz>0</iyz>
<izz>16.666</izz>
</inertia>
<mass>100</mass>
<pose>0 0 0 0 -0 0</pose>
</inertial>
<collision name='box_collision'>
<geometry>
<box>
<size>0.1 5 1</size>
</box>
</geometry>
<surface>
<friction>
<ode/>
</friction>
<bounce/>
<contact/>
</surface>
</collision>
<visual name='box_visual'>
<geometry>
<box>
<size>0.1 5 1</size>
</box>
</geometry>
<material>
<ambient>0.3 0.3 0.3 1</ambient>
<diffuse>0.7 0.7 0.7 1</diffuse>
<specular>1 1 1 1</specular>
</material>
</visual>
<pose>0 0 0 0 -0 0</pose>
<enable_wind>false</enable_wind>
</link>
<static>false</static>
<self_collide>false</self_collide>
</model>
<model name='box_1'>
<pose>-0 -2.5 0.5 -0 -0 -0</pose>
<link name='box_link'>
<inertial>
<inertia>
<ixx>16.666</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>16.666</iyy>
<iyz>0</iyz>
<izz>16.666</izz>
</inertia>
<mass>100</mass>
<pose>0 0 0 0 -0 0</pose>
</inertial>
<collision name='box_collision'>
<geometry>
<box>
<size>10 0.1 1</size>
</box>
</geometry>
<surface>
<friction>
<ode/>
</friction>
<bounce/>
<contact/>
</surface>
</collision>
<visual name='box_visual'>
<geometry>
<box>
<size>10 0.1 1</size>
</box>
</geometry>
<material>
<ambient>0.3 0.3 0.3 1</ambient>
<diffuse>0.7 0.7 0.7 1</diffuse>
<specular>1 1 1 1</specular>
</material>
</visual>
<pose>0 0 0 0 -0 0</pose>
<enable_wind>false</enable_wind>
</link>
<static>false</static>
<self_collide>false</self_collide>
</model>
<model name='box_2'>
<pose>-0 2.5 0.5 0 -0 -0</pose>
<link name='box_link'>
<inertial>
<inertia>
<ixx>16.666</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>16.666</iyy>
<iyz>0</iyz>
<izz>16.666</izz>
</inertia>
<mass>100</mass>
<pose>0 0 0 0 -0 0</pose>
</inertial>
<collision name='box_collision'>
<geometry>
<box>
<size>10 0.1 1</size>
</box>
</geometry>
<surface>
<friction>
<ode/>
</friction>
<bounce/>
<contact/>
</surface>
</collision>
<visual name='box_visual'>
<geometry>
<box>
<size>10 0.1 1</size>
</box>
</geometry>
<material>
<ambient>0.3 0.3 0.3 1</ambient>
<diffuse>0.7 0.7 0.7 1</diffuse>
<specular>1 1 1 1</specular>
</material>
</visual>
<pose>0 0 0 0 -0 0</pose>
<enable_wind>false</enable_wind>
</link>
<static>false</static>
<self_collide>false</self_collide>
</model>
<light name='sun' type='directional'>
<pose>0 0 10 0 -0 0</pose>
<cast_shadows>true</cast_shadows>
<intensity>1</intensity>
<direction>-0.5 0.1 -0.9</direction>
<diffuse>0.8 0.8 0.8 1</diffuse>
<specular>0.2 0.2 0.2 1</specular>
<attenuation>
<range>1000</range>
<linear>0.01</linear>
<constant>0.90000000000000002</constant>
<quadratic>0.001</quadratic>
</attenuation>
<spot>
<inner_angle>0</inner_angle>
<outer_angle>0</outer_angle>
<falloff>0</falloff>
</spot>
</light>
</world>
</sdf>
3.編寫launch文件
在launch目錄下新建launch文件gazebo_sim_world.launch.py,並輸入如下內容:
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
def generate_launch_description():
this_pkg = get_package_share_directory('demo_gazebo_sim')
pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim')
world_file = os.path.join(this_pkg,"world","house.sdf")
gz_sim = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py')),
launch_arguments={
'gz_args': '-r ' + world_file
}.items(),
)
return LaunchDescription([
gz_sim
])
4.構建
終端中進入當前工作空間,編譯功能包:
colcon build --packages-select demo_gazebo_sim
5.執行
終端中進入當前工作空間,調用如下指令執行launch文件:
. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py
運行結果如下圖所示。
也可以根據個人喜好,繼續設計房間模型。

IgnG添加模型
在Ignition Gazebo官網提供了許多仿真模型,可以自行下載並使用以優化仿真環境,使其更多樣、美觀且真實。
資源下載
仿真Ignition Gazebo的官方模型鏈接:
http://app.ignitionrobotics.org/fuel/models
自行選擇仿真模型點擊進入詳情頁面,然後點擊下載按鈕即可將模型資源保存到本地。
在用户目錄下新建ign_models目錄,將下載的資源解壓縮到該目錄以作備用。
資源配置
為了可以讓Ignition Gazebo識別到模型資源,下一步還需要修改用户目錄下的 .bashrc 文件,添加如下代碼:
# Humble版本一般是下面的,但是有可能会更新,如果不生效,请尝试Jazzy的宏
export IGN_GAZEBO_RESOURCE_PATH=~/ign_models
# Jazzy版本的宏改了,如下:
export GZ_SIM_RESOURCE_PATH=~/ign_models
https://gazebosim.org/docs/latest/fuel_insert/
模型添加
終端下進入功能包demo_gazebo_sim的world目錄,使用指令ign gazebo house.sdf 或者gz sim house.sdf啓動仿真環境,點擊窗口右上的摺疊按鈕,搜索Resource Spawner並打開,點擊Local resources並選擇模型拖拽至仿真環境中。將修改後的內容保存至house.sdf文件。

正常下載資源後,這個local resources這裏就會顯示了
house.sdf文件示例內容如下:
<sdf version='1.9'>
<world name='empty'>
<physics name='1ms' type='ignored'>
<max_step_size>0.001</max_step_size>
<real_time_factor>1</real_time_factor>
<real_time_update_rate>1000</real_time_update_rate>
</physics>
<plugin name='gz::sim::systems::Physics' filename='ignition-gazebo-physics-system'/>
<plugin name='gz::sim::systems::UserCommands' filename='ignition-gazebo-user-commands-system'/>
<plugin name='gz::sim::systems::SceneBroadcaster' filename='ignition-gazebo-scene-broadcaster-system'/>
<plugin name='gz::sim::systems::Contact' filename='ignition-gazebo-contact-system'/>
<gravity>0 0 -9.8</gravity>
<magnetic_field>6e-06 2.3e-05 -4.2e-05</magnetic_field>
<atmosphere type='adiabatic'/>
<scene>
<ambient>0.4 0.4 0.4 1</ambient>
<background>0.7 0.7 0.7 1</background>
<shadows>true</shadows>
</scene>
<model name='ground_plane'>
<static>true</static>
<link name='link'>
<collision name='collision'>
<geometry>
<plane>
<normal>0 0 1</normal>
<size>100 100</size>
</plane>
</geometry>
<surface>
<friction>
<ode/>
</friction>
<bounce/>
<contact/>
</surface>
</collision>
<visual name='visual'>
<geometry>
<plane>
<normal>0 0 1</normal>
<size>100 100</size>
</plane>
</geometry>
<material>
<ambient>0.8 0.8 0.8 1</ambient>
<diffuse>0.8 0.8 0.8 1</diffuse>
<specular>0.8 0.8 0.8 1</specular>
</material>
</visual>
<pose>0 0 0 0 -0 0</pose>
<inertial>
<pose>0 0 0 0 -0 0</pose>
<mass>100</mass>
<inertia>
<ixx>1</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>1</iyy>
<iyz>0</iyz>
<izz>1</izz>
</inertia>
</inertial>
<enable_wind>false</enable_wind>
</link>
<pose>0 0 0 0 -0 0</pose>
<self_collide>false</self_collide>
</model>
<model name='box'>
<pose>5.02632 -2e-06 0.500002 -0 4.4e-05 4.6e-05</pose>
<link name='box_link'>
<inertial>
<inertia>
<ixx>16.666</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>16.666</iyy>
<iyz>0</iyz>
<izz>16.666</izz>
</inertia>
<mass>100</mass>
<pose>0 0 0 0 -0 0</pose>
</inertial>
<collision name='box_collision'>
<geometry>
<box>
<size>0.1 5 1</size>
</box>
</geometry>
<surface>
<friction>
<ode/>
</friction>
<bounce/>
<contact/>
</surface>
</collision>
<visual name='box_visual'>
<geometry>
<box>
<size>0.1 5 1</size>
</box>
</geometry>
<material>
<ambient>0.3 0.3 0.3 1</ambient>
<diffuse>0.7 0.7 0.7 1</diffuse>
<specular>1 1 1 1</specular>
</material>
</visual>
<pose>0 0 0 0 -0 0</pose>
<enable_wind>false</enable_wind>
</link>
<static>false</static>
<self_collide>false</self_collide>
</model>
<model name='box_0'>
<pose>-5.01336 -0.00029 0.500002 0 -4.2e-05 -0.005335</pose>
<link name='box_link'>
<inertial>
<inertia>
<ixx>16.666</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>16.666</iyy>
<iyz>0</iyz>
<izz>16.666</izz>
</inertia>
<mass>100</mass>
<pose>0 0 0 0 -0 0</pose>
</inertial>
<collision name='box_collision'>
<geometry>
<box>
<size>0.1 5 1</size>
</box>
</geometry>
<surface>
<friction>
<ode/>
</friction>
<bounce/>
<contact/>
</surface>
</collision>
<visual name='box_visual'>
<geometry>
<box>
<size>0.1 5 1</size>
</box>
</geometry>
<material>
<ambient>0.3 0.3 0.3 1</ambient>
<diffuse>0.7 0.7 0.7 1</diffuse>
<specular>1 1 1 1</specular>
</material>
</visual>
<pose>0 0 0 0 -0 0</pose>
<enable_wind>false</enable_wind>
</link>
<static>false</static>
<self_collide>false</self_collide>
</model>
<model name='box_1'>
<pose>-0 -2.5 0.5 1e-06 0 0</pose>
<link name='box_link'>
<inertial>
<inertia>
<ixx>16.666</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>16.666</iyy>
<iyz>0</iyz>
<izz>16.666</izz>
</inertia>
<mass>100</mass>
<pose>0 0 0 0 -0 0</pose>
</inertial>
<collision name='box_collision'>
<geometry>
<box>
<size>10 0.1 1</size>
</box>
</geometry>
<surface>
<friction>
<ode/>
</friction>
<bounce/>
<contact/>
</surface>
</collision>
<visual name='box_visual'>
<geometry>
<box>
<size>10 0.1 1</size>
</box>
</geometry>
<material>
<ambient>0.3 0.3 0.3 1</ambient>
<diffuse>0.7 0.7 0.7 1</diffuse>
<specular>1 1 1 1</specular>
</material>
</visual>
<pose>0 0 0 0 -0 0</pose>
<enable_wind>false</enable_wind>
</link>
<static>false</static>
<self_collide>false</self_collide>
</model>
<model name='box_2'>
<pose>-0.000154 2.52488 0.500821 -0.018068 -0 -0.003156</pose>
<link name='box_link'>
<inertial>
<inertia>
<ixx>16.666</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>16.666</iyy>
<iyz>0</iyz>
<izz>16.666</izz>
</inertia>
<mass>100</mass>
<pose>0 0 0 0 -0 0</pose>
</inertial>
<collision name='box_collision'>
<geometry>
<box>
<size>10 0.1 1</size>
</box>
</geometry>
<surface>
<friction>
<ode/>
</friction>
<bounce/>
<contact/>
</surface>
</collision>
<visual name='box_visual'>
<geometry>
<box>
<size>10 0.1 1</size>
</box>
</geometry>
<material>
<ambient>0.3 0.3 0.3 1</ambient>
<diffuse>0.7 0.7 0.7 1</diffuse>
<specular>1 1 1 1</specular>
</material>
</visual>
<pose>0 0 0 0 -0 0</pose>
<enable_wind>false</enable_wind>
</link>
<static>false</static>
<self_collide>false</self_collide>
</model>
<include>
<uri>file://Bed</uri>
<name>Bed</name>
<pose>2.82155 1.18752 0 0 -0 0</pose>
</include>
<include>
<uri>file://Office Desk</uri>
<name>Desk</name>
<pose>2.78306 -1.97796 0 0 -0 1.57</pose>
</include>
<include>
<uri>file://Bathtub</uri>
<name>Bathtub</name>
<pose>-3.87509 1.82783 0 0 -0 0</pose>
</include>
<include>
<uri>file://Vanity</uri>
<name>Vanity</name>
<pose>-2.5974 1.85613 -0.010992 0.021648 0 -1.57</pose>
</include>
<include>
<uri>file://Vanity</uri>
<name>Vanity_1</name>
<pose>-2.5974 0.634325 -0.010992 0.021648 0 -1.57</pose>
</include>
<include>
<uri>file://Dining Table</uri>
<name>DiningTable</name>
<pose>-0.374337 1.33602 0 0 0 -1.57</pose>
</include>
<include>
<uri>file://Chair</uri>
<name>Chair</name>
<pose>2.79762 -1.26474 -0 -0 0 -2.3062</pose>
</include>
<include>
<uri>file://Sofa</uri>
<name>Sofa</name>
<pose>-0.546136 -1.92328 0.000119 -0 0 1.57</pose>
</include>
<light name='sun' type='directional'>
<pose>0 0 10 0 -0 0</pose>
<cast_shadows>true</cast_shadows>
<intensity>1</intensity>
<direction>-0.5 0.1 -0.9</direction>
<diffuse>0.8 0.8 0.8 1</diffuse>
<specular>0.2 0.2 0.2 1</specular>
<attenuation>
<range>1000</range>
<linear>0.01</linear>
<constant>0.90000000000000002</constant>
<quadratic>0.001</quadratic>
</attenuation>
<spot>
<inner_angle>0</inner_angle>
<outer_angle>0</outer_angle>
<falloff>0</falloff>
</spot>
</light>
</world>
</sdf>
構建
終端中進入當前工作空間,編譯功能包:
colcon build --packages-select demo_gazebo_sim
執行
終端中進入當前工作空間,調用如下指令執行launch文件:
. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py
運行結果如下圖所示。

IgnG添加機器人
Ignition Gazebo中可以直接創建機器人模型,或者也可以加載ROS2中URDF格式的機器人模型,此處我們使用後者(也可以選擇用自己的urdf小車,但是注意修改launch的路徑)。
我沒有用趙虛左老師的mycar_description,用的自己的小車模型,後面的一系列源碼都在下方這個Github倉庫中,需要的可以自行clone.
https://github.com/tungchiahui/ROS2\_WS/tree/main/6.ws\_simulations
準備機器人模型功能包
複製機器人描述功能包mycar_description到工作空間的src目錄,ign_models中新建mycar_description目錄,並將功能包mycar_description下的mesh目錄複製進ign_models中的mycar_description目錄。
機器人模型功能包下新建launch文件
新建launch文件mycar_desc_sim.launch.py,並輸入如下內容:
from launch import LaunchDescription
from launch_ros.actions import Node
import os
from ament_index_python.packages import get_package_share_directory
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command,LaunchConfiguration
from launch.actions import DeclareLaunchArgument
#示例: ros2 launch cpp06_urdf display.launch.py model:=`ros2 pkg prefix --share cpp06_urdf`/urdf/urdf/demo01_helloworld.urdf
def generate_launch_description():
MYCAR_MODEL = os.environ['MYCAR_MODEL']
mycar_description = get_package_share_directory("mycar_description")
default_model_path = os.path.join(mycar_description,"urdf",MYCAR_MODEL + ".urdf")
model = DeclareLaunchArgument(name="model", default_value=default_model_path)
# 加载机器人模型
# 启动 robot_state_publisher 节点并以参数方式加载 urdf 文件
robot_description = ParameterValue(Command(["xacro ",LaunchConfiguration("model")]))
robot_state_publisher = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
parameters=[{"robot_description": robot_description}]
)
return LaunchDescription([
model,
robot_state_publisher,
])
較之於以往該文件缺少了joint_state_publisher節點,該節點作用是發佈活動關節狀態,這一功能後續由ignition實現。
添加機器人模型
修改gazebo_sim_world.launch.py文件,包含機器人模型的發佈文件並在Gazebo中生成機器人模型,修改後的代碼如下:
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
def generate_launch_description():
this_pkg = get_package_share_directory("demo_gazebo_sim")
mycar_desc_pkg = get_package_share_directory("mycar_description")
pkg_ros_gz_sim = get_package_share_directory("ros_gz_sim")
world_file = os.path.join(this_pkg,"world","house.sdf")
gz_sim = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(pkg_ros_gz_sim, "launch", "gz_sim.launch.py")),
launch_arguments={
"gz_args": "-r " + world_file
}.items(),
)
mycar_desc = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(mycar_desc_pkg,"launch","mycar_desc_sim.launch.py")
)
)
spawn = Node(package="ros_gz_sim", executable="create",
arguments=[
"-name", "mycar",
"-x", "0",
"-z", "0.01", #设置为0,可能会陷进地里
"-y", "0",
"-topic", "/robot_description"],
output="screen")
return LaunchDescription([
gz_sim,
spawn,
mycar_desc,
])
構建
終端中進入當前工作空間,編譯功能包:
colcon build --packages-select mycar_description demo_gazebo_sim
執行
終端中進入當前工作空間,調用如下指令執行launch文件:(執行起來有問題的話,你只要學過urdf怎麼跑,應該擁有自我尋找錯誤的能力了,自己找吧)
. install/setup.bash
# MYCAR_MODEL值可以设置为arduino、stm32_2w 或stm32_4w(这个是具体的urdf文件名,在mycar_description包下的)
export MYCAR_MODEL=stm32_4w
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py
運行結果如下圖所示。

IgnG運動控制器(實質上就是標籤)
本節將介紹如何讓你的機器人動起來。
原理就是給urdf或xacro等添加
http://sdformat.org/tutorials?tut=sdformat_urdf_extensions&cat=specification&

https://gazebosim.org/api/plugin/2/index.html
安裝庫:
https://gazebosim.org/api/plugin/2/installation.html
sudo apt-get update
sudo apt-get install lsb-release
sudo sh -c 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" > /etc/apt/sources.list.d/gazebo-stable.list'
wget http://packages.osrfoundation.org/gazebo.key -O - | sudo apt-key add -
sudo apt-get update
sudo apt install libgz-plugin2-dev
利用插件去讓小車動,比如有兩輪差速插件,四輪麥輪插件等等
同時該插件還提供了一些可以控制輸出的選項,因為是仿真,所以還要告訴插件輪子對應的joint名稱等信息,這樣就有了下面這個參數表格:
| 配置項 | 含義 |
|---|---|
| ros | ros相關配置,包含命名空間和話題重映射等 |
| update_rate | 數據更新速率 |
| left_joint | 左輪關節名稱 |
| right_joint | 右輪關節名稱 |
| wheel_separation | 左右輪子的間距 |
| wheel_diameter | 輪子的直徑 |
| max_wheel_torque | 輪子最大的力矩 |
| max_wheel_acceleration | 輪子最大的加速度 |
| publish_odom | 是否發佈里程計 |
| publish_odom_tf | 是否發佈里程計的tf開關 |
| publish_wheel_tf | 是否發佈輪子的tf數據開關 |
| odometry_frame | 里程計的framed ID,最終體現在話題和TF上 |
| robot_base_frame | 機器人的基礎frame的ID |
修改URDF文件
arduino.urdf 和 stm32_2w.urdf 文件中,在<robot>根標籤下添加如下代碼:
<gazebo>
<plugin filename="libignition-gazebo-diff-drive-system.so"
name="ignition::gazebo::systems::DiffDrive">
<left_joint>left_joint</left_joint>
<right_joint>right_joint</right_joint>
<wheel_separation>0.2097</wheel_separation>
<wheel_radius>0.03415</wheel_radius>
<odom_publish_frequency>10</odom_publish_frequency>
<frame_id>odom</frame_id>
<child_frame_id>base_footprint</child_frame_id>
<topic>/cmd_vel</topic>
<max_linear_acceleration>10</max_linear_acceleration>
<min_linear_acceleration>-10</min_linear_acceleration>
<max_angular_acceleration>10</max_angular_acceleration>
<min_angular_acceleration>-10</min_angular_acceleration>
<max_linear_velocity>0.5</max_linear_velocity>
<min_linear_velocity>-0.5</min_linear_velocity>
<max_angular_velocity>1</max_angular_velocity>
<min_angular_velocity>-1</min_angular_velocity>
</plugin>
</gazebo>
<gazebo>
<plugin filename="ignition-gazebo-joint-state-publisher-system"
name="ignition::gazebo::systems::JointStatePublisher">
</plugin>
</gazebo>
stm32_4w.urdf 文件中,在<robot>根標籤下添加如下代碼:
<gazebo>
<plugin
filename="ignition-gazebo-diff-drive-system"
name="ignition::gazebo::systems::DiffDrive">
<left_joint>left_former_joint</left_joint>
<left_joint>left_rear_joint</left_joint>
<right_joint>right_former_joint</right_joint>
<right_joint>right_rear_joint</right_joint>
<wheel_separation>0.4</wheel_separation>
<wheel_radius>0.0415</wheel_radius>
<odom_publish_frequency>50</odom_publish_frequency>
<frame_id>odom</frame_id>
<child_frame_id>base_footprint</child_frame_id>
<topic>/cmd_vel</topic>
<max_linear_acceleration>10</max_linear_acceleration>
<min_linear_acceleration>-10</min_linear_acceleration>
<max_angular_acceleration>10</max_angular_acceleration>
<min_angular_acceleration>-10</min_angular_acceleration>
<max_linear_velocity>0.5</max_linear_velocity>
<min_linear_velocity>-0.5</min_linear_velocity>
<max_angular_velocity>1</max_angular_velocity>
<min_angular_velocity>-1</min_angular_velocity>
</plugin>
</gazebo>
<gazebo>
<plugin filename="ignition-gazebo-joint-state-publisher-system"
name="ignition::gazebo::systems::JointStatePublisher">
</plugin>
</gazebo>
如果是麥輪(ROS1的,ROS2的待更新)(把輪子關節設置為自己輪子關節名即可):
<robot name="mycar" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:macro name="joint_trans" params="joint_name">
<transmission name="${joint_name}_trans">
<type>transmission_interface/SimpleTransmission</type>
<joint name="${joint_name}">
<hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface>
</joint>
<actuator name="${joint_name}_motor">
<hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface>
<mechanicalReduction>1</mechanicalReduction>
</actuator>
</transmission>
</xacro:macro>
<xacro:joint_trans joint_name="LeftFrontwheelToBase" />
<xacro:joint_trans joint_name="LeftBackwheelToBase" />
<xacro:joint_trans joint_name="RightFrontwheelToBase" />
<xacro:joint_trans joint_name="RightBackwheelToBase" />
<gazebo>
<plugin name="mecanum_controller" filename="libgazebo_ros_planar_move.so">
<commandTopic>cmd_vel</commandTopic>
<odometryTopic>odom</odometryTopic>
<odometryFrame>odom</odometryFrame>
<leftFrontJoint>LeftFrontwheelToBase</leftFrontJoint>
<rightFrontJoint>RightFrontwheelToBase</rightFrontJoint>
<leftRearJoint>LeftBackwheelToBase</leftRearJoint>
<rightRearJoint>RightBackwheelToBase</rightRearJoint>
<odometryRate>100</odometryRate>
<robotBaseFrame>base_footprint</robotBaseFrame>
<broadcastTF>1</broadcastTF>
</plugin>
</gazebo>
</robot>
修改launch文件
修改gazebo_sim_world.launch.py文件,修改後的代碼如下:
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
def generate_launch_description():
this_pkg = get_package_share_directory("demo_gazebo_sim")
mycar_desc_pkg = get_package_share_directory("mycar_description")
pkg_ros_gz_sim = get_package_share_directory("ros_gz_sim")
world_file = os.path.join(this_pkg,"world","base.sdf")
gz_sim = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(pkg_ros_gz_sim, "launch", "gz_sim.launch.py")),
launch_arguments={
"gz_args": "-r " + world_file
}.items(),
)
mycar_desc = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(mycar_desc_pkg,"launch","mycar_desc_sim.launch.py")
)
)
spawn = Node(package="ros_gz_sim", executable="create",
arguments=[
"-name", "mycar",
"-x", "-4",
"-z", "0.01", #设置为0,可能会陷进地里
"-y", "0",
"-topic", "/robot_description"],
output="screen")
# Bridge
bridge = Node(
package="ros_gz_bridge",
executable="parameter_bridge",
arguments=["/cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist",
"/model/mycar/odometry@nav_msgs/msg/Odometry@gz.msgs.Odometry",
"/model/mycar/tf@tf2_msgs/msg/TFMessage[gz.msgs.Pose_V",
"/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock",
"/world/empty/model/mycar/joint_state@sensor_msgs/msg/JointState[gz.msgs.Model",
],
parameters=[{"qos_overrides./model/mycar.subscriber.reliability": "reliable"}],
remappings=[
("/model/mycar/tf", "/tf"),
("/world/empty/model/mycar/joint_state","joint_states"),
("/model/mycar/odometry","/odom")
],
output="screen"
)
return LaunchDescription([
gz_sim,
spawn,
mycar_desc,
bridge
])
構建
終端中進入當前工作空間,編譯功能包:
colcon build --packages-select mycar_description demo_gazebo_sim
執行
終端中進入當前工作空間,調用如下指令執行launch文件:
. install/setup.bash
export MYCAR_MODEL=stm32_4w # MYCAR_MODEL值可以设置为arduino、stm32_2w 或stm32_4w(这个是具体的urdf文件名,在mycar_description包下的)
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py
再啓動鍵盤控制節點,就可以控制機器人運動了。
ros2 run teleop_twist_keyboard teleop_twist_keyboard
還可以啓動rviz2,以查看里程計消息以及座標變換。終端中進入當前工作空間,調用如下指令執行launch文件:
啓動rviz2
. install/setup.bash
rviz2
RVIZ2軟件配置如下圖所示:



Ignition Gazebo仿真之傳感器
本節將介紹如何為仿真機器人添加雷達、相機等傳感器。本節代碼部分內容對於我們教程中涉及到的arduino、stm32_2w以及stm32_4w等機器人模型而言是完全通用的。
添加傳感器插件
在進行傳感器模擬之前,需要先添加一個名為ignition-gazebo-sensors-system的插件,打開urdf文件,在<robot>根標籤內添加如下代碼:
<gazebo>
<plugin
filename="ignition-gazebo-sensors-system"
name="ignition::gazebo::systems::Sensors">
<render_engine>ogre2</render_engine>
</plugin>
</gazebo>
ignition-gazebo-sensors-system是Ignition Gazebo仿真環境的插件,提供傳感器模型和相關功能,用於創建、模擬和測試各種傳感器設備。它包含常見傳感器模型,如攝像頭、激光雷達等。
添加各種傳感器
(注意,你的模型一定要有以下幾個傳感器的模型)
雷達的模型不需要collision,請刪掉,否則會擋激光射出。
<gazebo reference="laser">
<sensor name='gpu_lidar' type='gpu_lidar'>
<topic>scan</topic>
<update_rate>30</update_rate>
<lidar>
<scan>
<horizontal>
<samples>640</samples>
<resolution>1</resolution>
<min_angle>-3.1415926</min_angle>
<max_angle>3.1415926</max_angle>
</horizontal>
<vertical>
<samples>16</samples>
<resolution>1</resolution>
<min_angle>-0.261799</min_angle>
<max_angle>0.261799</max_angle>
</vertical>
</scan>
<range>
<min>0.08</min>
<max>10.0</max>
<resolution>0.01</resolution>
</range>
</lidar>
<alwaysOn>1</alwaysOn>
<visualize>true</visualize>
<ignition_frame_id>laser</ignition_frame_id>
</sensor>
</gazebo>
<gazebo reference="camera" >
<sensor name="cam_link" type="camera">
<update_rate>10.0</update_rate>
<always_on>true</always_on>
<ignition_frame_id>camera</ignition_frame_id>
<pose>0 0 0 0 0 0</pose>
<topic>/image_raw</topic>
<camera name="my_camera">
<horizontal_fov>1.3962634</horizontal_fov>
<image>
<width>600</width>
<height>600</height>
<format>R8G8B8</format>
</image>
<clip>
<near>0.02</near>
<far>300</far>
</clip>
</camera>
</sensor>
</gazebo>
<gazebo reference="camera">
<sensor name="depth_camera" type="depth_camera">
<update_rate>10</update_rate>
<topic>depth_camera</topic>
<camera>
<horizontal_fov>1.05</horizontal_fov>
<image>
<width>256</width>
<height>256</height>
<format>R_FLOAT32</format>
</image>
<clip>
<near>0.1</near>
<far>10.0</far>
</clip>
</camera>
<alwaysOn>1</alwaysOn>
<ignition_frame_id>camera</ignition_frame_id>
</sensor>
</gazebo>
從官網找到的imu傳感器的
<gazebo>
<plugin filename="libignition-gazebo-imu-system.so"
name="ignition::gazebo::systems::Imu">
</plugin>
</gazebo>
<gazebo reference="base_link">
<sensor name="imu_sensor" type="imu">
<always_on>1</always_on>
<update_rate>30</update_rate>
<visualize>true</visualize>
<topic>imu</topic>
</sensor>
</gazebo>
可以用ign topic -e -t /imu測試gazebo是否發佈了話題,後面再用gazebo_bridge把話題給ROS2就行了。
默認情況下,rviz2沒有顯示imu消息的插件,需要自行安裝相關插件,具體安裝指令如下:
sudo apt install ros-${ROS_DISTRO}-imu-tools
SolidWorks自動生成的模型可能翻轉了laser_joint,請你修改回正,這樣可能rivz2就有激光了,然後修改一下可視化的模型,讓模型正常,不要給碰撞,不然可能會遮擋激光。

修改gazebo_sim_world.launch.py文件,修改後的代碼如下:
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
def generate_launch_description():
this_pkg = get_package_share_directory("demo_gazebo_sim")
mycar_desc_pkg = get_package_share_directory("mycar_description")
pkg_ros_gz_sim = get_package_share_directory("ros_gz_sim")
world_file = os.path.join(this_pkg,"world","house.sdf")
gz_sim = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(pkg_ros_gz_sim, "launch", "gz_sim.launch.py")),
launch_arguments={
"gz_args": "-r " + world_file
}.items(),
)
mycar_desc = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(mycar_desc_pkg,"launch","mycar_desc_sim.launch.py")
)
)
spawn = Node(package="ros_gz_sim", executable="create",
arguments=[
"-name", "mycar",
"-x", "-4",
"-z", "0.01", #设置为0,可能会陷进地里
"-y", "0",
"-topic", "/robot_description"],
output="screen")
# Bridge
bridge = Node(
package="ros_gz_bridge",
executable="parameter_bridge",
arguments=["/cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist",
"/model/mycar/odometry@nav_msgs/msg/Odometry@gz.msgs.Odometry",
"/model/mycar/tf@tf2_msgs/msg/TFMessage[gz.msgs.Pose_V",
"/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock",
"/world/empty/model/mycar/joint_state@sensor_msgs/msg/JointState[gz.msgs.Model",
"/scan@sensor_msgs/msg/LaserScan@gz.msgs.LaserScan",
"/scan/points@sensor_msgs/msg/PointCloud2@gz.msgs.PointCloudPacked",
"/image_raw@sensor_msgs/msg/Image@gz.msgs.Image",
"/camera_info@sensor_msgs/msg/CameraInfo@gz.msgs.CameraInfo",
"/depth_camera@sensor_msgs/msg/Image@gz.msgs.Image",
"/imu@sensor_msgs/msg/Imu[gz.msgs.IMU",
"/imu/angular_velocity@geometry_msgs/msg/Vector3[gz.msgs.Vector3d"
],
parameters=[{"qos_overrides./model/mycar.subscriber.reliability": "reliable"}],
remappings=[
("/model/mycar/tf", "/tf"),
("/world/empty/model/mycar/joint_state","joint_states"),
("/model/mycar/odometry","/odom")
],
output="screen"
)
return LaunchDescription([
gz_sim,
spawn,
mycar_desc,
bridge
])
構建
終端中進入當前工作空間,編譯功能包:
colcon build --packages-select mycar_description demo_gazebo_sim
執行
終端中進入當前工作空間,調用如下指令執行launch文件:
. install/setup.bash
export MYCAR_MODEL=stm32_4w # MYCAR_MODEL值可以设置为arduino、stm32_2w 或stm32_4w
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py
再啓動鍵盤控制節點,就可以控制機器人運動了。
還可以啓動rviz2,以查看機器人發佈的諸多數據。終端中進入當前工作空間,調用如下指令執行launch文件:
. install/setup.bash
rviz2


我沒有用趙虛左老師的mycar_description,用的自己的小車模型,後面的一系列源碼都在下方這個Github倉庫中,需要的可以自行clone.
https://github.com/tungchiahui/ROS2_WS/tree/main/6.ws_simulations
(把上面的全部復現,才能夠進行下一章導航,下一章導航依然基於仿真)