Gz Sim(Gazebo Harmonic)
Gz Sim (Gazebo Harmonic and later versions (ROS2 Jazzy and later versions))
The new version of Gazebo is a brand-new robot simulation tool used in ROS2. It is an upgraded version of Gazebo. In Humble, it is called Ignition Gazebo (also known as Gazebo Fortress), and in Jazzy, it is called Gazebo Harmonic (with the Ignition name removed) (https://community.gazebosim.org/t/a-new-era-for-gazebo/1356). It offers better performance and usability, and provides a powerful simulation environment through tight integration with ROS2.
Gazebo Installation and Running
Official Tutorial https://docs.ros.org/en/jazzy/Tutorials/Advanced/Simulators/Gazebo/Gazebo.html
The following website is the official tutorial (Gazebo Harmonic for ROS2 Jazzy):
https://gazebosim.org/docs/harmonic/getstarted/
https://gazebosim.org/docs/harmonic/library_reference_nav/
Source code: https://github.com/gazebosim/docs/blob/master/harmonic/tutorials
Installation
Gazebo is an independent project that does not depend on ROS2 and can be installed on its own. However, if ROS2 is already installed, the corresponding version of Gazebo is integrated into the ROS2 repository, and you can install it directly by running 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

Running
Once Gazebo is installed, it can be launched in two ways.
Method 1: Launch using Gazebo, with the following command:
# Humble版本
ign gazebo
# Jazzy版本
gz sim
Method 2: Start using ROS2. The command is as follows:
ros2 launch ros_gz_sim gz_sim.launch.py
The execution results of both are the same, as shown in the figure below: In the pop-up window, select the simulation environment and then click the run button to run.

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

Note: If your Gazebo isn't lagging, but Gazebo is extremely laggy, please make sure Gazebo is running with the dedicated graphics card, not the integrated graphics.
If you don't know how to switch the application graphics card, you can simply disable the integrated graphics and switch from hybrid output to dedicated graphics output.
Toolbar
- The toolbar at the top contains two buttons: the file menu button (horizontal stripes) on the left and the plugin button (vertical ellipsis) on the right.
- File menu button (horizontal lines)

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

- The plugin button lists all available plugins. Clicking it will bring up the 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 shapes (sphere, box, cylinder) and transform controls. Using the shape buttons, you can directly insert box, sphere, or cylinder models into the simulation environment. Simply click the shape you want to insert, then place it into the environment. The shape will automatically snap to the ground plane.


- The main view displays the simulation environment. We can navigate the scene using the mouse in different ways, 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, click the Move mode in the top-left corner, then left-click to select the object.


- At the bottom of the window, from left to right, are the Play button, Step button, and Real-Time Factor (RTF). Clicking the Play button starts the simulation environment; clicking it again pauses the simulation. The Step button sets the discrete unit of simulation time, and you can customize the step size by hovering your mouse over the button. The Real-Time Factor indicates the ratio of the simulation speed to real time.

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

- The Entity Tree displays a list of entities in the simulation environment.
- After clicking an entity in the Entity Tree, its related information 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 its basic settings or disable it.

Gazebo comes with many built-in plugins. You can click the button on the right side of the toolbar to add them yourself. For example, you can select the Grid Config plugin to adjust the world grid's features, including cell size, grid position, cell count, or color.
As the application deepens, other plugins will be introduced over time.


Integration with ROS2
This section will introduce how to integrate Ignition Gazebo with ROS2 to enable interaction between the two. For example, you can control the robot's movement using a ROS2 keyboard control node and display the robot's odometry (odom) data in rviz2. The general workflow is as follows:
- Start the Ignition Gazebo simulation environment.
- Establish the connection between ROS2 and Ignition Gazebo via ros_gz_bridge;
- Start the ROS2-related nodes to enable data transmission and reception with Ignition Gazebo.
All integration implementations between Ignition Gazebo and ROS2 basically follow the above workflow.
Launch the Simulation Environment
When installing Ignition Gazebo, some simulation environments are already built in and can be launched directly. Here, we can use the simulation file named visualize_lidar.sdf, which includes a differential drive robot and a LiDAR simulation. 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 launched using ROS2 launch, with the following command:
ros2 launch ros_gz_sim gz_sim.launch.py gz_args:="-v 4 -r visualize_lidar.sdf"
Both methods are essentially the same: they start Ignition Gazebo and load the visualize_lidar.sdf file.

Establishing a Connection
Although the robot in the simulation environment has been configured with a motion control plugin and can subscribe to velocity commands via the /model/vehicle_blue/cmd_vel topic to move, the message formats in Gazebo and ROS2 are not consistent. Therefore, the ros_gz_bridge bridge package is needed to convert messages between the two. The command to invoke it is as follows:
ros2 run ros_gz_bridge parameter_bridge /model/vehicle_blue/cmd_vel@geometry_msgs/msg/Twist]gz.msgs.Twist
This command can convert ROS2 messages of type geometry_msgs/msg/Twist published on the /model/vehicle_blue/cmd_vel topic into messages of type gz.msgs.Twist that can be recognized by Gazebo.
Starting a ROS2 Node
Start the ROS2 keyboard control node and remap the topic to /model/vehicle_blue/cmd_vel, using the following command:
ros2 run teleop_twist_keyboard teleop_twist_keyboard --ros-args -r /cmd_vel:=/model/vehicle_blue/cmd_vel
Now you can use the keyboard to control the robot's movement.

ros_gz_bridge
ros_gz_bridge is the bridge connecting ROS2 and Gazebo. The messages used by ROS2 and Gazebo are not compatible, so they must be converted through ros_gz_bridge.
ros_gz_bridge Usage Syntax
The bridge between ROS2 and Gazebo is implemented through the parameter_bridge node in the ros_gz_bridge package, with the following usage syntax:
parameter_bridge [<topic@ROS2_type@Gz_type> ..] [<service@ROS2_srv_type[@Gz_req_type@Gz_rep_type]> ..]
In the topic, the first @ symbol serves as the separator between the topic name and the message type.
The first @ symbol is followed by a ROS message type.
ROS message types are followed by the @, , or symbols:
- @ indicates a bidirectional bridge;
- [ Indicates the bridge from Gazebo to ROS;
- ] indicates the bridge from ROS to Gazebo.
After the direction symbol is the Gazebo Transport message type.
(The two @ symbols do not have the same meaning.)
In a Service, the first @ symbol is the separator between the service name and type.
The first @ symbol is followed by the ROS service type. Optionally, Gazebo request and response types can be included, separated by @ symbols between them.
Only supports exposing Gazebo services as ROS services, meaning 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/msg/String@gz.msgs.StringMsg
Bridge example from Gazebo to ROS:
parameter_bridge /chatter@std_msgs/msg/String[gz.msgs.StringMsg
Bridge example from ROS to Gazebo:
parameter_bridge /chatter@std_msgs/msg/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
Message types supported by ros_gz_bridge
The following lists some commonly used mappings in Jazzy and those listed in the official documentation; the complete mappings are subject to ros2 run ros_gz_bridge parameter_bridge -h and the ros_gz_bridge documentation for the corresponding ROS2 version.
Below is the correspondence table of topic message types in ROS2 and Gazebo:
| ROS2 message types | Gazebo Transport types |
|---|---|
| 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 |
There are also some supplementary types.
| ROS 2 message types | Gazebo Transport types |
|---|---|
| actuator_msgs/msg/Actuators | gz.msgs.Actuators |
| geometry_msgs/msg/PoseWithCovarianceStamped | gz.msgs.PoseWithCovariance |
| geometry_msgs/msg/TwistStamped | gz.msgs.Twist |
| gps_msgs/msg/GPSFix | gz.msgs.NavSat |
| marine_acoustic_msgs/msg/Dvl | gz.msgs.DVLVelocityTracking |
| ros_gz_interfaces/msg/EntityWrench | gz.msgs.EntityWrench |
| ros_gz_interfaces/msg/LogicalCameraImage | gz.msgs.LogicalCameraImage |
| ros_gz_interfaces/msg/LogPlaybackStatistics | gz.msgs.LogPlaybackStatistics |
| ros_gz_interfaces/msg/ParamVec | gz.msgs.Param |
| ros_gz_interfaces/msg/ParamVec | gz.msgs.Param_V |
| ros_gz_interfaces/msg/WorldStatistics | gz.msgs.WorldStatistics |
| sensor_msgs/msg/Range | gz.msgs.LaserScan |
| vision_msgs/msg/Detection2D | gz.msgs.AnnotatedAxisAligned2DBox |
| vision_msgs/msg/Detection2DArray | gz.msgs.AnnotatedAxisAligned2DBox_V |
| vision_msgs/msg/Detection3D | gz.msgs.AnnotatedOriented3DBox |
| vision_msgs/msg/Detection3DArray | gz.msgs.AnnotatedOriented3DBox_V |
And the service message type correspondence table:
| ROS2 message types | Gazebo request | Gazebo response |
|---|---|---|
| ros_gz_interfaces/srv/ControlWorld | gz.msgs.WorldControl | gz.msgs.Boolean |
Optimized integration with ROS2
In the implementation of Gazebo and ROS2 integration, different commands need to be used in the terminal to start different modules, making the process somewhat complex. This section will introduce how to optimize it using launch files.
Create a New Package
First, call the following command to create a function package:
ros2 pkg create demo_gazebo_sim

Add Table of Contents
Add the following directories under the newly created package: launch, rviz, world. Then add the following code to 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 Gazebo simulation environment.

Generate rviz2 configuration files in the rviz directory
Launch 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
Copy the visualize_lidar.sdf file from the worlds directory under the ROS installation path (/opt/ros/jazzy/opt/gz_sim_vendor/share/gz/gz-sim8/worlds/) to the world directory.
If it is not found under the above path, it may be under the installation path of ign (Humble's).
/usr/share/ignition/ignition-gazebo6/worlds
If you haven't already, manually search for it:
sudo find / -name "visualize_lidar.sdf"

Writing 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 ROS2 is established via ros_gz_bridge, and the rviz2 node is launched. When establishing the connection, message conversion for velocity commands, odometry, and coordinate transforms is implemented.

Build
In the terminal, navigate to 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 following command to execute the launch file:
. 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.
- Set
Fixed Frametovehicle_blue/odom. - Add the TF plugin.
- Add the Odometry plugin and set the topic to
/model/vehicle_blue/odometry. When sending speed commands via keyboard control, the robot in the simulation environment begins to move, and messages such as coordinate transformations and odometry can be echoed back in rviz2.

Creating a Simulation Environment 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 your own simulation environment. The example in this section will simulate a rectangular room that is 10 meters long and 5 meters wide. For this example, you can first launch Ignition Gazebo and build the simulation environment by dragging and dropping, then modify the corresponding simulation environment files to adjust the details.
Relationship between SDF, URDF, and Xacro:
- Difference between URDF and SDF:
- Complexity: SDF supports more powerful features and can describe a complete simulation environment; URDF is better suited for defining robot models.
- Purpose: URDF is the standard for ROS; SDF is the standard for Gazebo.
- Physics Engine Support: URDF supports Gazebo through plugins; SDF natively supports Gazebo.
- Format Conversion: URDF can be converted to SDF (via the tool
gz sdf -pprovided by ROS).
- The Role of Xacro:
- Xacro is a URDF generation tool that helps users efficiently write URDF files, but it has no direct relationship with SDF.
Practical Advice
- In the Gazebo simulation: If you are using ROS 2 and Gazebo, you can directly use SDF files, which offer more powerful functionality.
- In ROS: If primarily used for robot control and planning, URDF or URDF generated by Xacro is recommended.
- Combining the two: Use URDF for control and SDF for simulation. For example, after defining the robot structure with URDF, convert it to SDF using Gazebo plugins.
Example 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 command gz sim to launch 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, and box_3 are (5.0, 0.0, 0.5), (-5.0, 0.0, 0.5), (0.0, 2.5, 0.5), and (0.0, -2.5, 0.5), respectively.
(The coordinates above refer to X, Y, Z coordinates, with no rotation.)
Save the file to the world directory of the function package. The saved file must have the .sdf extension; here, the file name is house.sdf.


<sdf version='1.10'>
<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='gz-sim-physics-system'/>
<plugin name='gz::sim::systems::UserCommands' filename='gz-sim-user-commands-system'/>
<plugin name='gz::sim::systems::SceneBroadcaster' filename='gz-sim-scene-broadcaster-system'/>
<plugin name='gz::sim::systems::Contact' filename='gz-sim-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>true</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>true</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>true</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>true</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 cube dimensions and enclose the walls. In the SDF file, the four cubes correspond to four <model> tags, whose name attributes are box, box_1, box_2, and box_3. Change the <size>1 1 1</size> in box and box_1 to <size>0.1 5 1</size>, and change the <size>1 1 1</size> in box_2 and box_3 to <size>10 0.1 1</size> (Note: Each <model> tag contains two <size> tags, located under the <collision> tag and the <visual> tag respectively; both <size> tag contents need to be modified).
The modified content of the house.sdf file is as follows:
<sdf version='1.10'>
<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='gz-sim-physics-system'/>
<plugin name='gz::sim::systems::UserCommands' filename='gz-sim-user-commands-system'/>
<plugin name='gz::sim::systems::SceneBroadcaster' filename='gz-sim-scene-broadcaster-system'/>
<plugin name='gz::sim::systems::Contact' filename='gz-sim-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>true</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>true</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>true</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>true</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 the launch file
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. Build
In the terminal, navigate to 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 following command to execute the launch file:
. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py
The running result is shown in the figure below.
You can also continue designing the room model according to personal preference.

Adding Models to Gz Sim
The Gazebo official 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 Gazebo model link for simulation:
https://app.gazebosim.org/fuel/models
Select a simulation model, click to enter its details page, and then click the download button to save the model resource to your local device.
Create a new directory named ign_models in the user directory, and extract the downloaded resources into this directory for later use.
You can name this directory whatever you like, as long as it's in pure English, follows Linux naming conventions, and contains no illegal characters.
Resource Allocation
To allow Gazebo to recognize the model resources, the next step is to modify the .bashrc file in the user directory by adding the following code:
# Jazzy版本的宏如下:
export GZ_SIM_RESOURCE_PATH=~/ign_models
# Humble版本一般是下面的
export IGN_GAZEBO_RESOURCE_PATH=~/ign_models
https://gazebosim.org/docs/latest/fuel_insert/
Model Addition
In the terminal, navigate to the world directory of the package demo_gazebo_sim, and use the command gz sim house.sdf to launch the simulation environment. Click the collapse button in the upper right corner of the window, search for Resource Spawner and open it, then click Local resources and select a model to drag into the simulation environment. Save the modified content to the house.sdf file.

After downloading the resources normally, they will appear here in the local resources section.
Here is an example of the contents of the house.sdf file:
<sdf version='1.10'>
<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='gz-sim-physics-system'/>
<plugin name='gz::sim::systems::UserCommands' filename='gz-sim-user-commands-system'/>
<plugin name='gz::sim::systems::SceneBroadcaster' filename='gz-sim-scene-broadcaster-system'/>
<plugin name='gz::sim::systems::Contact' filename='gz-sim-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.05017 0 0.5 -0 0 3e-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>true</static>
<self_collide>false</self_collide>
</model>
<model name='box_0'>
<pose>-5.05003 -7.8e-05 0.5 -0 -0 1.8e-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>true</static>
<self_collide>false</self_collide>
</model>
<model name='box_1'>
<pose>0.000162 -2.53144 0.499999 0 -0 -0.001876</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>true</static>
<self_collide>false</self_collide>
</model>
<model name='box_2'>
<pose>0.00012 2.51517 0.499999 0 -0 -0.00303</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>true</static>
<self_collide>false</self_collide>
</model>
<!-- Jazzy 迁移说明开始:下面是本机 file:// 模型目录,ign_models 只是文件夹名,不属于 Gazebo API,因此保留不改。 -->
<include>
<uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/bed</uri>
<name>Bed</name>
<pose>2.11564 -0.080355 0 0 -0 0</pose>
</include>
<include>
<uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
<name>WhiteCabinet</name>
<pose>-1.69505 1.99357 0 0 0 -1.59625</pose>
</include>
<include>
<uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
<name>WhiteCabinet_1</name>
<pose>-1.71706 1.10132 0 0 0 -1.59625</pose>
</include>
<include>
<uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
<name>WhiteCabinet_2</name>
<pose>-1.74096 0.206747 0 0 0 -1.59625</pose>
</include>
<include>
<uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
<name>WhiteCabinet_3</name>
<pose>-1.76908 -0.654114 0 0 0 -1.59625</pose>
</include>
<include>
<uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
<name>WhiteCabinet_4</name>
<pose>-3.54096 -1.97391 0 0 0 -1.59625</pose>
</include>
<include>
<uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
<name>WhiteCabinet_4_1</name>
<pose>-3.51784 -1.10026 0 0 0 -1.59625</pose>
</include>
<include>
<uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
<name>WhiteCabinet_4_1_1</name>
<pose>-3.44853 2.02068 0 0 0 -1.59625</pose>
</include>
<include>
<uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
<name>WhiteCabinet_4_1_1_1</name>
<pose>-3.499 -0.197449 0 0 0 -1.59625</pose>
</include>
<include>
<uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
<name>WhiteCabinet_3_1</name>
<pose>-0.465147 -0.678914 0 0 0 -1.59625</pose>
</include>
<include>
<uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
<name>WhiteCabinet_3_1_1</name>
<pose>-1.12325 -0.88551 0 0 0 -0.016899</pose>
</include>
<include>
<uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
<name>WhiteCabinet_3_1_2</name>
<pose>-0.445223 0.182127 0 0 0 -1.59625</pose>
</include>
<include>
<uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
<name>WhiteCabinet_3_1_2_1</name>
<pose>-0.422109 1.02215 0 0 0 -1.59625</pose>
</include>
<!-- Jazzy 迁移说明结束:如果以后重命名本机模型目录,需要同步修改这些 file:// 路径。 -->
<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, navigate to 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 following command to execute the launch file:
. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py
The running result is shown in the figure below.

Add a robot to Gazebo
In Gazebo, you can either directly create a robot model or load a robot model in URDF format from ROS2. Here we use the latter approach (you can also choose to use your own URDF car, but be sure to modify the launch file path).
We can use the model from the cpp06_urdf we created earlier.
Prepare Robot Model Function Package
In the workspace, enter the following command to create the package mycar_description.
cd ./src
ros2 pkg create mycar_description --build-type ament_cmake
cd ..
Create the following folders under the function package launch,urdf,rviz,meshes
Modify the following configuration file:
1. package.xml:
In package.xml, you need to manually add some runtime dependencies. The core content is as follows:
<exec_depend>rviz2</exec_depend>
<exec_depend>xacro</exec_depend>
<exec_depend>robot_state_publisher</exec_depend>
<exec_depend>joint_state_publisher</exec_depend>
<exec_depend>ros2launch</exec_depend>
2.CMakeLists.txt Under the function package, several directories were created. Installation paths need to be configured for these directories. The core content is as follows:
install(
DIRECTORY launch urdf rviz meshes
DESTINATION share/${PROJECT_NAME}
)
Overwrite the contents of the urdf folder in the previous cpp06_urdf package into the urdf folder in the mycar_description package.
Create a new mycar_description directory in ign_models, and copy the mesh directory from the mycar_description package into the mycar_description directory within ign_models. (At this point, the meshes folder is empty, which is normal; you may only have .STL files later when you use other models.)
Add SDF-specific tags required by Gazebo
First, modify car_base.urdf.xacro by adding various factors such as inertia, collision, friction, and so on:
<robot xmlns:xacro="http://wiki.ros.org/xacro">
<!-- PI 值 -->
<xacro:property name="PI" value="3.1416"/>
<!-- 定义车辆参数 -->
<!-- 车体长宽高 -->
<xacro:property name="base_link_x" value="0.2"/>
<xacro:property name="base_link_y" value="0.12"/>
<xacro:property name="base_link_z" value="0.07"/>
<!-- 离地间距 -->
<xacro:property name="distance" value="0.015"/>
<!-- 车轮半径 宽度 -->
<xacro:property name="wheel_radius" value="0.025"/>
<xacro:property name="wheel_length" value="0.02"/>
<!-- Gazebo 新增开始:Gazebo 物理参数,用于给 link 生成 inertial -->
<xacro:property name="base_footprint_radius" value="0.001"/>
<xacro:property name="base_footprint_mass" value="0.001"/>
<xacro:property name="base_link_mass" value="1.0"/>
<xacro:property name="wheel_mass" value="0.05"/>
<xacro:property name="wheel_mu1" value="1.0"/>
<xacro:property name="wheel_mu2" value="0.05"/>
<!-- Gazebo 新增结束:Gazebo 物理参数 -->
<!-- Gazebo 新增开始:球体惯性宏,给 base_footprint 使用 -->
<xacro:macro name="sphere_inertial" params="mass radius">
<inertial>
<mass value="${mass}"/>
<inertia
ixx="${2.0 / 5.0 * mass * radius * radius}" ixy="0.0" ixz="0.0"
iyy="${2.0 / 5.0 * mass * radius * radius}" iyz="0.0"
izz="${2.0 / 5.0 * mass * radius * radius}"/>
</inertial>
</xacro:macro>
<!-- Gazebo 新增结束:球体惯性宏 -->
<!-- Gazebo 新增开始:长方体惯性宏,给 base_link 使用 -->
<xacro:macro name="box_inertial" params="mass x y z">
<inertial>
<mass value="${mass}"/>
<inertia
ixx="${mass / 12.0 * (y * y + z * z)}" ixy="0.0" ixz="0.0"
iyy="${mass / 12.0 * (x * x + z * z)}" iyz="0.0"
izz="${mass / 12.0 * (x * x + y * y)}"/>
</inertial>
</xacro:macro>
<!-- Gazebo 新增结束:长方体惯性宏 -->
<!-- Gazebo 新增开始:圆柱车轮惯性宏,给四个轮子使用 -->
<xacro:macro name="wheel_inertial" params="mass radius length">
<inertial>
<mass value="${mass}"/>
<inertia
ixx="${mass / 12.0 * (3.0 * radius * radius + length * length)}" ixy="0.0" ixz="0.0"
iyy="${mass / 2.0 * radius * radius}" iyz="0.0"
izz="${mass / 12.0 * (3.0 * radius * radius + length * length)}"/>
</inertial>
</xacro:macro>
<!-- Gazebo 新增结束:圆柱车轮惯性宏 -->
<!-- 定义颜色 -->
<material name="yellow">
<color rgba="0.7 0.7 0 0.8" />
</material>
<material name="red">
<color rgba="0.8 0.1 0.1 0.8" />
</material>
<material name="gray">
<color rgba="0.2 0.2 0.2 0.95" />
</material>
<!-- 定义 base_footprint -->
<link name="base_footprint">
<visual>
<geometry>
<!-- Codex 修改:把原来的固定 0.001 改成上面新增的参数 -->
<sphere radius="${base_footprint_radius}"/>
</geometry>
</visual>
<!-- Gazebo 新增:base_footprint 的惯性,避免 Gazebo 转 SDF 时丢弃根 link -->
<xacro:sphere_inertial mass="${base_footprint_mass}" radius="${base_footprint_radius}"/>
</link>
<!-- 定义 base_link -->
<link name="base_link">
<visual>
<!-- 形状 -->
<geometry>
<box size="${base_link_x} ${base_link_y} ${base_link_z}" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="yellow"/>
</visual>
<!-- Gazebo 新增开始:base_link 碰撞体,Gazebo 物理仿真需要 collision -->
<collision>
<geometry>
<box size="${base_link_x} ${base_link_y} ${base_link_z}" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
</collision>
<!-- Gazebo 新增结束:base_link 碰撞体 -->
<!-- Gazebo 新增:base_link 惯性,Gazebo 物理仿真需要 inertial -->
<xacro:box_inertial mass="${base_link_mass}" x="${base_link_x}" y="${base_link_y}" z="${base_link_z}"/>
</link>
<joint name="baselink2basefootprint" type="fixed">
<parent link="base_footprint"/>
<child link="base_link"/>
<origin xyz="0.0 0.0 ${distance + base_link_z / 2}"/>
</joint>
<!-- 车轮宏定义 -->
<xacro:macro name="wheel_func" params="wheel_name is_front is_left" >
<link name="${wheel_name}_wheel">
<visual>
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_length}" />
</geometry>
<origin xyz="0 0 0" rpy="${PI / 2} 0 0" />
<material name="gray"/>
</visual>
<!-- Gazebo 新增开始:车轮碰撞体,四个轮子都会通过这个宏生成 -->
<collision>
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_length}" />
</geometry>
<origin xyz="0 0 0" rpy="${PI / 2} 0 0" />
</collision>
<!-- Gazebo 新增结束:车轮碰撞体 -->
<!-- Gazebo 新增:车轮惯性,四个轮子都会通过这个宏生成 -->
<xacro:wheel_inertial mass="${wheel_mass}" radius="${wheel_radius}" length="${wheel_length}"/>
</link>
<joint name="${wheel_name}2baselink" type="continuous">
<parent link="base_link" />
<child link="${wheel_name}_wheel" />
<origin xyz="${(base_link_x / 2 - wheel_radius) * is_front} ${base_link_y / 2 * is_left} ${(base_link_z / 2 + distance - wheel_radius) * -1}" rpy="0 0 0" />
<axis xyz="0 1 0" />
</joint>
<!-- Gazebo 新增:四轮差速原地转弯需要轮胎横向滑移;降低 mu2 可以减少 Gazebo 中的卡顿和抖动 -->
<gazebo reference="${wheel_name}_wheel">
<mu1>${wheel_mu1}</mu1>
<mu2>${wheel_mu2}</mu2>
<fdir1>1 0 0</fdir1>
</gazebo>
</xacro:macro>
<!-- 车轮宏调用 -->
<xacro:wheel_func wheel_name="left_front" is_front="1" is_left="1" />
<xacro:wheel_func wheel_name="left_back" is_front="-1" is_left="1" />
<xacro:wheel_func wheel_name="right_front" is_front="1" is_left="-1" />
<xacro:wheel_func wheel_name="right_back" is_front="-1" is_left="-1" />
</robot>
Modify car.urdf.xacro:
<robot name="car" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:include filename="car_base.urdf.xacro"/>
<xacro:include filename="car_camera.urdf.xacro"/>
<xacro:include filename="car_laser.urdf.xacro"/>
</robot>
Create a 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
def generate_launch_description():
mycar_description = get_package_share_directory("mycar_description")
default_model_path = os.path.join(mycar_description,"urdf/xacro","car.urdf.xacro")
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 to previous versions, this file is missing the joint_state_publisher node, whose role is to publish active joint states. This functionality will later be implemented by Ignition.
Adding a Robot Model
Create the gazebo_sim_robot_world.launch.py file, which includes the robot model publishing file and spawns 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",
"-R", "0",
"-P", "0",
"-Y", "1.57", # Yaw航向角逆时针旋转90度
"-topic", "/robot_description"],
output="screen")
return LaunchDescription([
gz_sim,
spawn,
mycar_desc,
])
Build
In the terminal, navigate to 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 following command to execute the launch file: (If there are issues during execution, as long as you've learned how to run URDF, you should have the ability to find and fix the errors yourself — go ahead and troubleshoot.)
. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_robot_world.launch.py
The running result is shown in the figure below.

Gz Sim Motion Controller
This section will introduce how to make your robot move.
The principle is to add <gazebo> tags to URDF, Xacro, or similar files:
http://sdformat.org/tutorials?tut=sdformat_urdf_extensions&cat=specification&

https://gazebosim.org/api/plugin/2/index.html
Install the library: Official Tutorial Enter https://gazebosim.org/docs/harmonic/library_reference_nav/ Click the plugin to enter 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
Using plugins to make the car move, such as two-wheel differential plugins, four-wheel Mecanum wheel plugins, and so on.
Additionally, this plugin provides some options for controlling the output. Since it's a simulation, you also need to tell the plugin the corresponding joint names for the wheels and other information, resulting in the parameter table below:
| Configuration item | Meaning |
|---|---|
| ros | ROS-related configuration, including namespaces and topic remapping, among others. |
| update_rate | Data update rate |
| left_joint | Left wheel joint name |
| right_joint | Right wheel joint name |
| wheel_separation | Distance between the left and right wheels |
| wheel_diameter | The diameter of the wheel |
| max_wheel_torque | The wheel's maximum torque |
| max_wheel_acceleration | The maximum acceleration of the wheel |
| publish_odom | Publish odometry |
| publish_odom_tf | Whether to publish the odometry tf switch |
| publish_wheel_tf | Switch for publishing wheel tf data |
| odometry_frame | The framed ID of the odometer is ultimately reflected in the topic and TF. |
| robot_base_frame | The robot's base frame ID |
Modifying the URDF File
For a 2-wheel differential drive vehicle, add the following code under the <robot> root tag:
<gazebo>
<plugin filename="gz-sim-diff-drive-system"
name="gz::sim::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="gz-sim-joint-state-publisher-system"
name="gz::sim::systems::JointStatePublisher">
</plugin>
</gazebo>
For a 4-wheel differential drive vehicle, add the following code under the <robot> root tag:
<gazebo>
<plugin
filename="gz-sim-diff-drive-system"
name="gz::sim::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="gz-sim-joint-state-publisher-system"
name="gz::sim::systems::JointStatePublisher">
</plugin>
</gazebo>
We are definitely using a 4-wheel vehicle here. I created a move_control.urdf.xacro specifically to store motion control code:
<robot name="car" xmlns:xacro="http://wiki.ros.org/xacro">
<!-- 运动控制插件 -->
<gazebo>
<plugin
filename="gz-sim-diff-drive-system"
name="gz::sim::systems::DiffDrive">
<left_joint>left_front2baselink</left_joint>
<left_joint>left_back2baselink</left_joint>
<right_joint>right_front2baselink</right_joint>
<right_joint>right_back2baselink</right_joint>
<!-- wheel_separation轮距 是左右轮中心线之间的距离:0.06 - (-0.06) = 0.12m -->
<wheel_separation>0.12</wheel_separation>
<wheel_radius>0.025</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="gz-sim-joint-state-publisher-system"
name="gz::sim::systems::JointStatePublisher">
</plugin>
</gazebo>
</robot>
Modify the launch file
Modify the gazebo_sim_robot_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",
"-R", "0",
"-P", "0",
"-Y", "1.57", #逆时针旋转90度
"-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, navigate to 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 following command to execute the launch file:
. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_robot_world.launch.py
Restart the keyboard control node, and you can control the robot's movement.
ros2 run teleop_twist_keyboard teleop_twist_keyboard
You can also launch rviz2 to view odometry messages and coordinate transforms. In the terminal, navigate to the current workspace and execute the following command to run the launch file:
Start rviz2
. install/setup.bash
rviz2
The RVIZ2 software configuration is shown in the following image:



Gz Sim Simulation: Sensors
This section will introduce how to add sensors such as LiDAR and cameras to the simulated robot. Adding Sensor Plugins
Before simulating the sensor, you need to add a plugin named gz-sim-sensors-system. Open the URDF file and add the following code inside the <robot> root tag:
(It is recommended to create a dedicated gazebo_sensor.urdf.xacro for storage)
<gazebo>
<plugin
filename="gz-sim-sensors-system"
name="gz::sim::systems::Sensors">
<render_engine>ogre2</render_engine>
</plugin>
</gazebo>
The gz-sim-sensors-system is a plugin for the 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 others.
Adding Various Sensors
(Note: Your model must include models for the following sensors)
The radar model does not need collision. Please delete it, otherwise it will block the laser emission.
<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>
<visualize>true</visualize>
<gz_frame_id>laser<gz_frame_id>
<pose relative_to="laser">0 0 0 0 0 0</pose>
</sensor>
</gazebo>
<gazebo reference="camera" >
<sensor name="cam_link" type="camera">
<update_rate>10.0</update_rate>
<always_on>true</always_on>
<gz_frame_id>camera</gz_frame_id>
<pose relative_to="camera">0 0 0 0 0 0</pose>
<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>
<always_on>1</always_on>
<gz_frame_id>camera</gz_frame_id>
<pose relative_to="camera">0 0 0 0 0 0</pose>
</sensor>
</gazebo>
IMU sensor found on the official website
<gazebo>
<plugin filename="gz-sim-imu-system"
name="gz::sim::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 use ign topic -e -t /imu to test whether Gazebo has published a topic, and then use gazebo_bridge to pass the topic to ROS2.
By default, rviz2 does not include a plugin for displaying IMU messages. You need to install the relevant plugin yourself. The specific installation command is as follows:
sudo apt install ros-${ROS_DISTRO}-imu-tools
sudo apt install ros-jazzy-imu-tools
The model automatically generated by SolidWorks may have flipped the laser_joint. Please correct it so that rivz2 can have the laser. Then, modify the visualization model to make it display properly, and do not assign collision properties to it, as that might block the laser.

Modify the gazebo_sim_robot_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",
"-R", "0",
"-P", "0",
"-Y", "1.57", #逆时针旋转90度
"-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, navigate to 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 following command to execute the launch file:
. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_robot_world.launch.py
Here gz_frame_id might trigger a false alarm from the sdf1.10 specification, but you can ignore this warning. In fact, this version of Gazebo reads the custom tag gz_frame_id.
On sdf1.12, it appears to have been changed to frame_id.
You can use ros2 topic echo /scan --once|grep frame_id来查看scan的frame_id是否被修改成功了,修改成功会变成laser,否则会变成gazebo默认的mycar/base_footprint/gpu_lidar.
Problem Solving Reference: https://github.com/gazebosim/gz-sensors/issues/306
Restart the keyboard control node, and you can control the robot's movement.
You can also launch rviz2 to view the various data published by the robot. In the terminal, navigate to the current workspace and execute the following command to run the launch file:
. install/setup.bash
rviz2


(Reproduce everything above to proceed to the next chapter navigation. The next chapter navigation is still based on simulation.)