Wiki
Ignition Gazebo(Gazebo Fortress)
Existing English translations were reused. 9 current source blocks remain in Simplified Chinese, without triggering paid translation.
1.Ignition Gazebo (Gazebo Fortress, based on ROS2 Humble)
** It is recommended to use ROS2 Jazzy; this ROS2 Humble Gazebo seems like a transitional version, and the code may conflict with subsequent versions! ** (This version's tutorial is no longer updated; in the future, we will focus on updating ROS2 Jazzy and later versions, that is, updating Gazebo Harmonic and later versions.)
See detailsGz Sim tutorial
1.1.Ign Gazebo Installation and Operation
Each version of Gazebo undergoes significant changes.
In particular, the old version of Gazebo (black interface) used by ROS1 and the new version of Gazebo (white interface) used by ROS2.
ROS2Different versions of Gazebo also vary widely; for example, many tags differ significantly between Humble and Jazzy, as well as between Jazzy and later versions.
This article uses the humble version (i.e., Ignition Gazebo) as the tutorial.
Of course, for compatibility with future Gazebo versions, there will also be a tutorial below teaching you how to migrate from Ignition Gazebo to Gazebo Sim (the very, very latest version of 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 都是一个强大的工具。
If you want to migrate from Ignition Gazebo(ROS2 Humble) to Gazebo(ROS2 Jazzy), please scroll down; there is a section below that explains how to migrate.
https://docs.ros.org/en/humble/Tutorials/Advanced/Simulators/Gazebo/Gazebo.html
The following website is the official tutorial (ROS2 Humble's Ignition Gazebo Fortress):
https://gazebosim.org/docs/fortress/getstarted/
https://gazebosim.org/docs/fortress/library_reference_nav/
Source code:https://github.com/gazebosim/docs/blob/master/fortress/tutorials
Install
Ignition Gazebo is an independent project that does not depend on ROS2 and can be installed on its own. However, if ROS2 is installed, the corresponding version of Ignition Gazebo is already integrated into the ROS2 repository, and it can be installed directly using the following command:
# 通用命令
sudo apt install ros-${ROS_DISTRO}-ros-gz
# Humble版本
sudo apt install ros-humble-ros-gz
# Jazzy版本
sudo apt install ros-jazzy-ros-gz

Run
After Ignition Gazebo is installed, it can be launched in two ways.
Method 1: Start in the Ignition Gazebo way, with the following command:
# Humble版本
ign gazebo
# Jazzy版本
gz sim
Method 2: Start in the ROS2 manner, with the following command:
ros2 launch ros_gz_sim gz_sim.launch.py
The results of both are consistent, as shown in the figure below: In the pop-up window, select the simulation environment and then click therunbutton to run.

Interface Introduction
Next, taking the Empty simulation environment as an example, let's introduce the interface components of Ignition Gazebo.

Note: If your Gazebo is not laggy, but Ignition Gazebo is extremely laggy, please confirm that Ignition Gazebo is opened with a discrete GPU, not an integrated GPU.
If you do not know how to switch the application's graphics card, you can simply disable the integrated graphics and switch from hybrid output to discrete graphics card output.
Toolbar
- The top toolbar contains two buttons, the file menu button on the left (horizontal stripes) and the plugins button on the right (vertical ellipsis).
- File menu button (horizontal stripes)

- The File menu button contains settings such as saving the simulation environment to a file, saving and loading interface configurations, and customizing the interface style.
- Plugin button on the right (vertical ellipsis)

- The plugin button lists all available plugins. Clicking it will pop up a plugin list; scroll down this list to view all plugins. When one is selected, its interface will appear in the right panel.
3D viewport
- The toolbar in the upper left contains buttons for various geometric bodies (sphere, box, cylinder) and transformation controls. Through the collection body buttons, box, sphere, or cylinder models can be directly inserted into the simulation environment. Simply click the shape to be inserted, then place it into the environment. The shape will automatically snap to the ground plane.


- The main view displays the simulation environment, and we can navigate the scene in different ways using the mouse, with the relevant operations as follows:
左键单击:选择实体
右键单击:打开带有选项的菜单:
Move to:移动到以实体为中心的场景
Follow:选择一个实体让视图保持居中,无论是移动还是平移
Remove:从模拟中删除实体
Copy:复制实体
Past: 粘贴实体
View:显示实体的重心(Center of Mass)、碰撞边界(Collisions)、惯性(Inertia)、
关节(Joints)、坐标系(Frames)、透明度(Transparent)、线框(Wireframe)等属性
左键单击并拖动:在场景中平移
右键单击并拖动:放大和缩小
滚轮向前/向后:放大和缩小
滚轮单击并拖动:旋转场景
- To move this ball, you need to click the move mode in the upper left corner, then left-click to select the object


- At the bottom of the window, from left to right, are the play button, the step size button, and the real-time factor (Real-Time Factor, RTF). Clicking the play button will start running the simulation environment, and clicking it again will pause the run. The step size button is used to set the discrete unit of simulation time, and the step size can be customized by hovering the mouse over the button. The real-time factor indicates the ratio of the simulation running speed relative to real time.

Right panel
The right panel is used to display plugins. The current simulation environment contains two plugins by default: Model and Entity Tree.

-
The Entity Tree displays the list of entities in the simulation environment;
-
After clicking an entity in the Entity Tree, the relevant information of that entity can be displayed in the Model.
-
You can also hold Ctrl and click to select multiple entities;
-
You can also right-click any plugin to open basic settings or close it.

Many plugins are built into Ignition Gazebo and can be added by clicking the button on the right side of the toolbar, for example: you can select the Grid Config plugin to adjust the characteristics of the world grid, including cell size, grid position, cell count, or color, etc.
As the application deepens later, some other plugins will also be introduced successively.


1.2.Integration with ROS2
This section will introduce how to implement the integration of Ignition Gazebo with ROS2 to enable interaction between them, for example, the robot's motion can be controlled through ROS2's keyboard control node, and the robot's odometry (odom) data can be displayed in rviz2. The general process is as follows:
-
Start the Ignition Gazebo simulation environment;
-
Establish a connection between ros_gz_bridge and Ignition Gazebo via ROS2;
-
Start nodes related to ROS2 to implement data transmission and reception with Ignition Gazebo.
All integration implementations of Ignition Gazebo with ROS2 basically follow the above process.
Start the simulation environment
When installing Ignition Gazebo, some simulation environments are already built in and can be launched directly. Here we can use a simulation file namedvisualize_lidar.sdfThis file's corresponding simulation environment includes the simulation of a differential drive robot and a lidar. The launch command is as follows:
ign gazebo -v 4 -r visualize_lidar.sdf
#或者
gz sim -v 4 -r visualize_lidar.sdf
Alternatively, it can also be started by ROS2 launch, with the following command:
ros2 launch ros_gz_sim gz_sim.launch.py gz_args:="-v 4 -r visualize_lidar.sdf" # 启动状态
The two methods are essentially the same, both start Ignition Gazebo and load the visualize_lidar.sdf file.

Establish Connection
Although the robot in the simulation environment has been configured with a motion control plugin and can subscribe to velocity commands via/model/vehicle_blue/cmd_vela topic and move, the message format in Ignition Gazebo is not consistent with that in ROS2, so the ros_gz_bridge bridge package is still needed to convert messages between the two. The command is as follows:
ros2 run ros_gz_bridge parameter_bridge /model/vehicle_blue/cmd_vel@geometry_msgs/msg/Twist]gz.msgs.Twist
Through this instruction, messages published on/model/vehicle_blue/cmd_velon the topicgeometry_msgs/msg/Twistof type ROS2 can be converted intogz.msgs.Twistmessages of the type recognizable by Ignition Gazebo.
Start ROS2 node
Start the keyboard control node of ROS2, and remap the topic to/model/vehicle_blue/cmd_velThe command is as follows:
ros2 run teleop_twist_keyboard teleop_twist_keyboard --ros-args -r /cmd_vel:=/model/vehicle_blue/cmd_vel
Next, you can use the keyboard to control the robot's movement.

1.3.ros_gz_bridge
ros_gz_bridge is the bridge connecting ROS2 and Ignition Gazebo; ROS2 and the messages used by Ignition Gazebo are not compatible and must be converted through ros_gz_bridge.
ros_gz_bridge使用语法
ROS2The bridging with Ignition Gazebo is implemented through the ros_gz_bridge node in the parameter_bridge package, and its usage syntax is as follows:
parameter_bridge [<topic@ROS2_type@Gz_type> ..] [<service@ROS2_srv_type[@Gz_req_type@Gz_rep_type]> ..]
在话题Topic中, 第一个@ 符号是话题名称和消息类型的 分隔符 。
After the first @ symbol is the ROS message type.
After the ROS message type is @,the symbol:
-
@ indicates bidirectional bridging;
-
\[ indicates bridging from Ignition Gazebo to ROS;
-
\] indicates bridging from ROS to Ignition Gazebo.
The direction symbol is followed by the Gazebo Transport message type.
(the two @ symbols do not have the same meaning)
In the service Service, the first @ symbol is the separator between the service name and type.
After the first @ symbol is the ROS service type. Optionally, include the Gazebo request and response types, separated by an @ symbol between them.
Only supports exposing Gazebo services as ROS services, i.e., the ROS service forwards requests to the Gazebo service, and then forwards the response back to the ROS client.
Bidirectional bridging example:
parameter_bridge /chatter@std_msgs/String@gz.msgs.StringMsg
Example of bridging from Gazebo to ROS:
parameter_bridge /chatter@std_msgs/String[gz.msgs.StringMsg
Example of bridging from ROS to Gazebo:
parameter_bridge /chatter@std_msgs/String]gz.msgs.StringMsg
Service bridging example:
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
You can also runros2 run ros_gz_bridge parameter_bridge -hthe command to view the official documentation.
ros_gz_bridge Supported message types
The following is the correspondence table between ROS2 and topic message types in Ignition Gazebo:
| ROS2Message type | Gazebo Transport type |
|---|---|
| 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 |
And the service message type correspondence table:
| ROS2 message type | Gazebo request | Gazebo response |
|---|---|---|
| ros_gz_interfaces/srv/ControlWorld | gz.msgs.WorldControl | gz.msgs.Boolean |
1.4.Integration and optimization with ROS2
In the integration of Ignition Gazebo with ROS2 implementation, different modules need to be started using different commands in the terminal. This process is somewhat complex to implement. This section will introduce how to optimize it using launch files.
Create a new feature package
First, call the following command to create a function package:
ros2 pkg create demo_gazebo_sim

Add directory
Under the newly created feature package, add the directories: launch, rviz, and world. And add the following code in CmakeLists.txt:
install(DIRECTORY rviz world launch DESTINATION share/${PROJECT_NAME})
The launch directory is used to store launch files, the rviz directory is used to store rviz2 configuration files, and the world directory is used to store files related to the Ignition Gazebo simulation environment.

Generate the configuration file for rviz2 in the rviz directory
Start rviz2, save the default configuration directly to the rviz directory of the current package, and name the saved file sim.rviz.

Copy the world file
在ignition安装路径下的worlds目录(/usr/share/ignition/ignition-gazebo6/worlds)中复制visualize_lidar.sdf文件至world目录。
If it is not in this path, it may be under the ROS installation path:
/opt/ros/jazzy/opt/gz_sim_vendor/share/gz/gz-sim8/worlds/
If not already done, manually search for it:
sudo find / -name "visualize_lidar.sdf"

Write a launch file
Create a new launch file gazebo_sim_demo.launch.py in the launch directory, and enter the following content:
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
])
In this launch file, the Ignition Gazebo simulation environment is started, a connection between the simulation and ros_gz_bridge is established through ROS2, and the rviz2 node is launched. When establishing the connection, the conversion of messages such as velocity commands, odometry, and coordinate transforms is implemented.

build
In the terminal, enter the current workspace and compile the package:
colcon build --packages-select demo_gazebo_sim

Execute
In the terminal, navigate to the current workspace and run the launch file using the following command:
. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_demo.launch.py
Open a new terminal and start the keyboard control node:
ros2 run teleop_twist_keyboard teleop_twist_keyboard
Reconfigure rviz2, setFixed Frametovehicle_blue/odomand add the TF plugin. Add the Odometry plugin and set its topic to/model/vehicle_blue/odometryWhen velocity commands are sent via keyboard control, the robot in the simulation environment starts moving, and messages such as coordinate transforms and odometry can be echoed in rviz2.

1.5.Simulation environment creation SDF file
In the previous sections, we used the built-in simulation environment of Ignition Gazebo. Starting from this section, we will introduce how to build a simulation environment by ourselves. The example in this section will simulate a rectangular room 10 m long and 5 m wide. In this example, you can first launch Ignition Gazebo and build the simulation environment by dragging and dropping, and then modify the files corresponding to the simulation environment to adjust the details.
Relationship between SDF, URDF, and Xacro:
-
URDF 和 SDF 的区别:
-
复杂性: SDF 支持的功能更强大,能够描述完整的仿真环境;URDF 更适合定义机器人模型。
-
用途: URDF 是 ROS 的标准;SDF 是 Gazebo 的标准。
-
物理引擎支持: URDF 通过插件支持 Gazebo;SDF 原生支持 Gazebo。
-
格式转换: URDF 可以转换为 SDF(通过 ROS 提供的工具
gz sdf -p)。
-
-
Xacro 的作用:
- Xacro 是 URDF 的生成工具,帮助用户高效编写 URDF 文件,但它与 SDF 无直接关系。
Practical Recommendations
-
In Gazebo simulation: If you are using ROS 2 and Gazebo, you can directly use SDF files, which offer more powerful features.
-
In ROS: If it is mainly used for robot control and planning, URDF or URDF generated by Xacro is recommended.
-
Combining both: Use URDF for control and SDF for simulation. For example, after defining the robot structure with URDF, convert it to SDF with the help of Gazebo plugins.
Illustrative comparison
URDF example:
<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 example (generating 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 Example:
<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. Create an SDF file
First, call the instructionign gazeboLaunch Gazebo, select the Empty simulation environment, then add cubes, with each cube corresponding to a wall.
The coordinates corresponding to the up, down, left, and right cubes box, box_1, box_2, box_3 are (5.0,0.0,0.5), (-5.0,0.0,0.5), (0.0,2.5,0.5), (0.0,-2.5,0.5), respectively.
(The above coordinates refer to X, Y, Z coordinates, with no rotation angles.)
Save the file to the world directory of the package. The saved file name must have the .sdf suffix. Here, the file name is 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. Modify the SDF file
Modify the SDF file to adjust the dimensions of the cubes and achieve an enclosed wall. In the SDF file, the four cubes correspond respectively to four<model>tags, whosenameattributes are respectivelybox、box_1、box_2、box_3; change theboxandbox_1in<size>1 1 1</size>to<size>0.1 5 1</size>; change thebox_2andbox_3in<size>1 1 1</size>to<size>10 0.1 1</size>(Note: under each<model>tag, there are two<size>tags, located respectively under the<collision>tag and the<visual>tag; the contents of both<size>tags need to be modified).
The modified file content with house.sdf is as follows:
<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. Write launch files
Create a new launch file gazebo_sim_world.launch.py in the launch directory, and enter the following content:
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. Construction
In the terminal, enter the current workspace and compile the package:
colcon build --packages-select demo_gazebo_sim
5. Execution
In the terminal, navigate to the current workspace and run the launch file using the following command:
. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py
The execution result is shown in the figure below.
You can also continue designing the room model according to personal preference.

1.6.IgnG add model
The official Ignition Gazebo website provides many simulation models that can be downloaded and used to optimize the simulation environment, making it more diverse, visually appealing, and realistic.
Resource Download
Official model links for simulating Ignition Gazebo:
http://app.ignitionrobotics.org/fuel/models
Select a simulation model on your own and click to enter the details page, then click the download button to save the model resource locally.
Create a new ign_models directory under the user directory, and extract the downloaded resources into this directory for later use.
Resource Configuration
In order for Ignition Gazebo to recognize the model resources, the next step is to modify the .bashrc file in the user directory and add the following code:
# Humble版本一般是下面的,但是有可能会更新,如果不生效,请尝试Jazzy的宏
export IGN_GAZEBO_RESOURCE_PATH=~/ign_models
# Jazzy版本的宏改了,如下:
export GZ_SIM_RESOURCE_PATH=~/ign_models
https://gazebosim.org/docs/latest/fuel_insert/
Add Model
In the terminal, enter the world directory of the package demo_gazebo_sim and use the commandign gazebo house.sdf orgz sim house.sdfStart the simulation environment, click the collapse button in the upper right corner of the window, search forResource Spawnerand open it, clickLocal resourcesand select a model to drag into the simulation environment. Save the modified content to the house.sdf file.

After the resources are downloaded normally, they will be displayed here in 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>
build
In the terminal, enter the current workspace and compile the package:
colcon build --packages-select demo_gazebo_sim
Execute
In the terminal, navigate to the current workspace and run the launch file using the following command:
. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py
The execution result is shown in the figure below.

1.7.IgnG Add Bot
In Ignition Gazebo, you can directly create a robot model, or you can also load a robot model in URDF format from ROS2. Here we use the latter (you can also choose to use your own URDF robot, but be sure to modify the launch path).
我没有用赵虚左老师的mycar_description,用的自己的小车模型,后面的一系列源码都在下方这个Github仓库中,需要的可以自行clone.
https://github.com/tungchiahui/ROS2_WS/tree/main/6.ws_simulations
Prepare the robot model package
复制机器人描述功能包mycar_description到工作空间的src目录,ign_models中新建mycar_description目录,并将功能包mycar_description下的mesh目录复制进ign_models中的mycar_description目录。
Create a new launch file under the robot model function package.
Create a new launch file mycar_desc_sim.launch.py and enter the following content:
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,
])
Compared with previous versions, this file lacks the joint_state_publisher node, whose function is to publish articulated joint states; this functionality was subsequently implemented by ignition.
Add Robot Model
Modify the gazebo_sim_world.launch.py file to include the launch file for the robot model and spawn the robot model in Gazebo. The modified code is as follows:
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,
])
build
In the terminal, enter the current workspace and compile the package:
colcon build --packages-select mycar_description demo_gazebo_sim
Execute
In the terminal, enter the current workspace and call the following command to execute the launch file: (If there are problems running it, as long as you have learned how to run URDF, you should have the ability to find errors yourself—go find them on your own.)
. 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
The execution result is shown in the figure below.

1.8.IgnG motion controller (essentially just alabel)
This section will introduce how to make your robot move.
The principle is to add to urdf or xacro, etc.Tags:
http://sdformat.org/tutorials?tut=sdformat_urdf_extensions&cat=specification&

https://gazebosim.org/api/plugin/2/index.html
Install library:
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
Use plugins to make the car move, such as a two-wheel differential plugin, a four-wheel Mecanum wheel plugin, etc.
At the same time, this plugin also provides some options that can control the output. Because it is a simulation, it is also necessary to tell the plugin information such as the names of the joints corresponding to the wheels, so the following parameter table is obtained:
| Configuration Item | Meaning |
|---|---|
| ros | ROS-related configuration, including namespace and topic remapping, etc. |
| update_rate | Data update rate |
| left_joint | Left wheel joint name |
| right_joint | Right wheel joint name |
| wheel_separation | Distance between left and right wheels |
| wheel_diameter | Wheel diameter |
| max_wheel_torque | Maximum wheel torque |
| max_wheel_acceleration | Maximum wheel acceleration |
| publish_odom | Whether to publish odometry |
| publish_odom_tf | Whether to publish the odometry TF switch |
| publish_wheel_tf | Whether to publish the wheel TF data switch |
| odometry_frame | The frame ID of the odometry, which is ultimately reflected in the topic and TF |
| robot_base_frame | The ID of the robot's base frame |
Modify the URDF file
arduino.urdf and stm32_2w.urdf file, under the<robot>root tag, add the following code:
<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 In the file, under the<robot>root tag, add the following code:
<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>
If it is a mecanum wheel (for ROS1, ROS2 needs to be updated) (just set the wheel joint to your own wheel joint name):
<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>
Modify the launch file
Modify the gazebo_sim_world.launch.py file. The modified code is as follows:
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
])
build
In the terminal, enter the current workspace and compile the package:
colcon build --packages-select mycar_description demo_gazebo_sim
Execute
In the terminal, navigate to the current workspace and run the launch file using the following command:
. 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
Then restart the keyboard control node, and you can control the robot's movement.
ros2 run teleop_twist_keyboard teleop_twist_keyboard
You can also start rviz2 to view odometry messages and coordinate transformations. In the terminal, enter the current workspace and call the following command to execute the launch file:
Start rviz2
. install/setup.bash
rviz2
The RVIZ2 software configuration is shown in the following figure:



1.9.Sensors in Ignition Gazebo Simulation
This section will introduce how to add sensors such as lidar and cameras to a simulated robot. The code content in this section is fully applicable to the robot models involved in our tutorial, such as Arduino, stm32_2w, and stm32_4w.
Add Sensor Plugin
Before performing sensor simulation, you need to first add a plugin namedignition-gazebo-sensors-systemOpen the URDF file and add the following code within the<robot>root tag:
<gazebo>
<plugin
filename="ignition-gazebo-sensors-system"
name="ignition::gazebo::systems::Sensors">
<render_engine>ogre2</render_engine>
</plugin>
</gazebo>
ignition-gazebo-sensors-system is a plugin for the Ignition Gazebo simulation environment that provides sensor models and related functionality for creating, simulating, and testing various sensor devices. It includes common sensor models such as cameras, LiDAR, and more.
Add various sensors
(Note that your model must include models of the following sensors)
The radar model does not need collision, please delete it, otherwise it will block the laser from being emitted.
<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>
The IMU sensor found from the official website
<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>
You can useign topic -e -t /imuto test whether Gazebo has published the topic, and later just use gazebo_bridge to ROS2 the topic.
By default, rviz2 does not have a plugin for displaying IMU messages, so the relevant plugin must be installed manually. The specific installation command is as follows:
sudo apt install ros-${ROS_DISTRO}-imu-tools
The model automatically generated by SolidWorks may have flipped laser_joint. Please correct it back to upright so that rviz2 may then show the laser. Then modify the visualization model to make the model normal and not let it collide, otherwise it may block the laser.

Modify the gazebo_sim_world.launch.py file. The modified code is as follows:
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
])
build
In the terminal, enter the current workspace and compile the package:
colcon build --packages-select mycar_description demo_gazebo_sim
Execute
In the terminal, navigate to the current workspace and run the launch file using the following command:
. 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
Then restart the keyboard control node, and you can control the robot's movement.
You can also start rviz2 to view the various data published by the robot. In the terminal, enter the current workspace and call the following command to execute the launch file:
. install/setup.bash
rviz2


我没有用赵虚左老师的mycar_description,用的自己的小车模型,后面的一系列源码都在下方这个Github仓库中,需要的可以自行clone.
https://github.com/tungchiahui/ROS2_WS/tree/main/6.ws_simulations
(Only after fully reproducing all of the above can the next chapter navigation be carried out; the next chapter navigation is still based on simulation.)