☰
ROS2 Launch文件完全指南:从节点启动到仿真部署
2026/10/3 6:05:30 网站建设 项目流程

开头

在接触ROS2之前,我一度觉得启动一个机器人系统就是打开三个终端,分别source一下、再轮流敲rosrun,顶多再为每个终端换个配色方便区分。直到开始写真正完整的机器人应用,比如同时跑驱动、SLAM、导航、RViz和Gazebo,才意识到这种“手工起节点”的方式有多脆弱:关错窗口、顺序不对、参数漏传、忘记source,任何一步出错都要重来。也就是从那时候起,我开始认真研究launch文件,这套ROS2自带的启动机制,也是这篇笔记要聊的核心内容。

这篇笔记对应我自己整理的学习笔记第4.6节,主要围绕“使用launch启动脚本”展开,适合刚学完ROS2基础通信(话题、服务、动作)但还没系统接触launch的读者,也适合已经写过简单launch文件,想搞清楚参数传递、命名空间、条件控制和调试技巧的人。看完这一篇,你能明白launch文件为什么是ROS2工程落地时绕不开的一环,也能直接照着我给的模板写出一份能用的launch文件,省掉在启动环节反复折腾的时间。

1. launch文件到底解决了什么问题

1.1 没有launch之前是怎么启动节点的

我最早用ROS2做小乌龟实验的时候,启动方式非常简单:先在一个终端里source环境,然后跑ros2 run turtlesim turtlesim_node,再开一个终端跑ros2 run turtlesim turtle_teleop_key,控制窗口就出现了。那时候觉得也没多麻烦,两个终端而已。

但等到系统稍微复杂一点,比如做一台两轮差速小车,需要同时启动底盘驱动、激光雷达驱动、里程计发布、串口桥接、RViz可视化、键盘控制,哪怕只是验证一个最简单的开环运动,也要开五六个终端,每个窗口都要手动敲一遍命令。敲错一个包名、少了source、在错误的目录下执行,都会报错,而且报错信息五花八门,光是排查“为什么我这个节点起不来”就要花不少时间。

更麻烦的是参数。如果底盘驱动里需要设置串口号、波特率、雷达的frame_id、里程计协方差这些参数,用rosrun启动的话要么在命令行后面一个一个传,要么就得提前在单独的yaml文件里配置好了再手工加载。每次修改都要重新敲一遍完整命令,很容易敲漏。这在开发调试阶段是能忍的,但一旦要把这套系统交给别人用,或者换一台电脑部署,就成了灾难。

1.2 launch文件的核心价值

launch文件解决的就是这个“启动编排”问题。你可以把多个节点的启动方式、启动顺序、参数配置、命名空间、重映射关系,全部写进一个launch文件里,然后只需要一行命令ros2 launch <包名> <launch文件名>,系统就会按照你定义好的方式把所有节点拉起来。

我自己的体会是,launch文件最大的价值不是“少敲几行命令”,而是让启动过程变得可复现、可维护、可共享。可复现指的是同样的launch文件在任何一台配置好的机器上启动,行为完全一致;可维护指的是换传感器、改参数,你不需要改动代码,只需要改launch文件;可共享则是说你可以把launch文件作为包的“使用入口”,别人拿到你的包,不用问你怎么启动,看一眼launch文件就全都明白了。

从ROS2的架构设计角度来说,launch也是一个独立的子系统,它的核心是launch和launch_ros这两个包。launch负责通用的进程管理与事件循环,launch_ros则提供了ROS2专属的启动描述实体,比如Node、ComposableNode、Parameter。理解了这层拆分的逻辑,后面看launch文档和源码就不会一头雾水。

2. ROS2 launch系统的核心机制与关键概念

2.1 launch文件其实是Python代码

ROS2的launch文件支持三种格式:Python、XML、YAML。官方推荐且绝大多数开源项目使用的都是Python格式,原因很简单:Python格式有完整的编程能力,变量、循环、函数、条件判断都能用,而且调试起来更直观。

我第一次接触这个的时候有个认知偏差,总以为launch文件是一个“被ROS2解析的配置文件”,跟JSON一样。实际上,ROS2把launch文件当作Python程序来执行,执行的结果是一棵LaunchDescription的实体树。launch系统拿到这棵树之后,会遍历里面的action,逐步执行。

所以,你可以在launch文件里写Python的for循环来批量生成节点,可以用if句式做条件判断,可以把参数组织成字典然后批量塞给节点。这种灵活性是ROS1时代XML格式launch完全比不上的。我建议新学者直接用Python格式,不要花时间在XML和YAML格式上,除非你要维护旧项目。

2.2 Node动作的常用参数

在launch文件里,往系统里加一个节点,靠的是launch_ros.actions.Node这个类。下面是一个最典型的最小写法:

from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): return LaunchDescription([ Node( package='turtlesim', executable='turtlesim_node', name='my_turtle', output='screen' ) ])

这里面几个参数需要说清楚。

package是功能包名,executable是可执行文件的名字,注意不是节点名,而是你在setup.py或CMakeLists.txt里注册的入口名字。name是给节点起的别名,会覆盖节点内部通过init初始化的节点名,改成my_turtle后,用ros2 node list看到的就是/my_turtle。

output有几种常见取值。'screen'表示把节点的标准输出打印到当前终端,不加这个参数,基本上看不到节点的printf日志。调试阶段建议都加上。想存日志的话可以用'log',或者通过log_cmd结合append_env等方式做更复杂的日志重定向。还有arguments,用来给可执行文件传命令行参数,模拟你在终端里ros2 run后面跟的那些参数。

2.3 ExecuteProcess、IncludeLaunchFile与Group

光有Node其实已经能解决大部分场景了,但launch系统里还有几个重要动作类型,分别是ExecuteProcess、IncludeLaunchFile(或IncludeLaunchDescription)和Group。

ExecuteProcess用来执行任意系统命令。比如,底层驱动不是ROS节点,而是一个普通的可执行程序,或者启动时想顺手执行一条cp命令做文件备份,这时候就用ExecuteProcess。它的典型用法是传入cmd列表:

from launch.actions import ExecuteProcess ExecuteProcess( cmd=['gnome-terminal', '--', 'bash', '-c', 'echo hello'], output='screen' )

我常常用它来启动额外的终端窗口,比如在launch里开一个窗口单独运行键盘控制节点,这样不会和主进程的输出混在一起。

IncludeLaunchDescription用来把其他launch文件包含进来,相当于把多个launch文件组合成一个更大的launch。比如导航包自带一个很复杂的导航launch,你不想改它,只想在自己的launch文件里把它引进来,同时传几个参数进去,那就可以用IncludeLaunchDescription。它在ROS2里的写法比ROS1复杂一些,一般配合PathJoinSubstitution使用,后面实操部分会给出完整例子。

Group用来对节点做分组管理,最常用的场景是配合scoped参数给一组节点统一设置命名空间。比如在同一个launch里启动两台机器人,它们的节点名可能都是/lidar_driver,直接用会冲突,就可以用两个Group分别设置不同的命名空间,让它们变成/robot1/lidar_driver和/robot2/lidar_driver。

3. 手把手写一个launch文件并跑起来

3.1 建立项目结构

我建议以功能包为单位存放launch文件,也就是把launch文件放在你自己的ROS2包内的launch目录里。这样做的原因是launch文件里通常会引用包内的参数文件、模型文件、配置文件,放在包内就可以用ament_index_python提供的接口来定位包的路径,而不是靠相对路径。

先创建一个工作空间和功能包,我习惯用ament_python类型的包来做示例,因为不需要写C++编译代码,学launch的时候改动最少:

mkdir -p ~/ros2_ws/src cd ~/ros2_ws/src ros2 pkg create --build-type ament_python launch_demo cd launch_demo mkdir launch

在launch_demo目录下创建launch目录,后续launch文件都放在这里。顺便准备一个简单模型,我拿一个自定义的Python节点来做示例,模拟一个发布话题的传感器节点。

3.2 添加依赖与配置setup.py

这里有一个关键点:ROS2的launch文件默认不会被打包进安装目录。如果你直接运行ros2 launch launch_demo demo.launch.py,系统会提示找不到launch文件。这是因为Python包的setup.py里没有把launch目录作为数据文件安装。

需要在setup.py里做两处修改。第一处,在data_files中加入launch目录的安装规则:

import os from glob import glob from setuptools import setup package_name = 'launch_demo' setup( name=package_name, version='0.0.0', packages=[package_name], data_files=[ ('share/ament_index/resource_index/packages', ['resource/' + package_name]), ('share/' + package_name, ['package.xml']), # 重点:把launch目录安装到share目录下 (os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')), ], ... )

这段代码的作用是用glob把launch目录下所有.launch.py文件安装到<prefix>/share/launch_demo/launch/目录下。不写这一段,源码目录下编译时能找到launch文件,但clean之后再运行就找不到了,这也是很多人遇到“launch文件找不到”问题的最常见原因。

第二处,在entry_points里其实不需要专门注册launch文件。这里跟ROS1不一样,ROS2的launch文件不要求注册到console_scripts,只需要安装在share目录下,ros2 launch命令会自动去相应的share目录里查找。

3.3 编写第一个launch文件

我在launch目录下新建一个demo.launch.py,内容是一个仿真传感器节点加一个可视化节点,方便验证launch生效:

from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): # 模拟激光雷达数据发布的节点 lidar_node = Node( package='launch_demo', executable='lidar_node', name='lidar_front', output='screen', parameters=[{'frame_id': 'laser_frame'}] ) rviz_node = Node( package='rviz2', executable='rviz2', name='rviz2', output='screen' ) return LaunchDescription([ lidar_node, rviz_node, ])

这里补充一点:parameters可以传列表,列表里的元素可以是字典、yaml文件路径,也可以是ParameterFile。实际使用时,我更喜欢用yaml文件管理大量参数,而字典适合调试阶段快速传几个值。

你可能会问:executable='lidar_node'对应的可执行文件真的存在吗?在纯Python包里还需要在setup.py的entry_points里注册这个命令,才能用ros2 run launch_demo lidar_node把它拉起来。如果你暂时没有这个节点,可以直接把executable改成系统自带的小乌龟节点来测试,比如package='turtlesim', executable='turtlesim_node'。

我用虚拟节点做示例时,写的lidar_node入口如下:

# 放在 launch_demo/lidar_node.py 中 import rclpy from rclpy.node import Node as RclNode class LidarNode(RclNode): def __init__(self): super().__init__('lidar_node') self.declare_parameter('frame_id', 'laser_frame') self.get_logger().info('lidar_node started, frame_id: %s' % self.get_parameter('frame_id').value) def main(args=None): rclpy.init(args=args) node = LidarNode() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()

在setup.py中注册入口:

entry_points={ 'console_scripts': [ 'lidar_node = launch_demo.lidar_node:main', ], },

这样整条链路就通了。

3.4 编译并运行

在launch文件写好之后,编译安装包:

cd ~/ros2_ws colcon build --packages-select launch_demo source install/setup.bash

然后运行:

ros2 launch launch_demo demo.launch.py

如果一切正常,屏幕上会打印两个节点的日志输出,然后你可以再开一个终端用ros2 node list查看节点列表,应该能看到/lidar_front和/rviz2两个节点。

这里有个小细节:Node的name参数如果你给了lidar_front,节点在ROS2图里的名字就是/lidar_front;如果省略name参数,ROS2会用代码里初始化的节点名,也就是lidar_node。我建议在一个launch文件管理多个同类型节点时,显式给name参数,避免节点名混乱。

4. 进阶用法:参数、命名空间、包含、条件

4.1 让launch文件具备外部传参能力

实际项目里,launch文件不能总是写死参数,否则每次换串口号都要改源码。ROS2的launch系统提供了DeclareLaunchArgument和LaunchConfiguration这一对组合,专门用来处理外部传参。

比如我想让用户通过命令行指定frame_id:

from launch.actions import DeclareLaunchArgument from launch.substitutions import LaunchConfiguration def generate_launch_description(): frame_id = LaunchConfiguration('frame_id') return LaunchDescription([ DeclareLaunchArgument( 'frame_id', default_value='laser_frame', description='激光雷达坐标系名称' ), Node( package='launch_demo', executable='lidar_node', name='lidar_front', parameters=[{'frame_id': frame_id}] ), ])

运行的时候就可以这样覆盖默认值:

ros2 launch launch_demo demo.launch.py frame_id:=scan_frame

DeclareLaunchArgument的default_value如果不写,参数就是必填的,不传会报错。我建议所有参数都给出默认值,这样别人直接运行也不会失败。

LaunchConfiguration是一个惰性替换对象,它真正的取值是在launch真正执行时从启动上下文中读取的。这带来一个有意思的特性,你可以把LaunchConfiguration直接传给Node的package参数,实现动态指定功能包。

4.2 命名空间与重映射

多机器人系统或者需要隔离两组相同节点的时候,命名空间很有用。还是拿雷达节点举例,如果同一台机器上挂了两颗雷达,它们的驱动代码一样,节点名也一样,那就不行了。用Group可以把它们放在不同命名空间下:

from launch.actions import GroupAction from launch_ros.actions import PushRosNamespace def generate_launch_description(): front_group = GroupAction([ PushRosNamespace('front'), Node( package='launch_demo', executable='lidar_node', name='lidar', ), ]) rear_group = GroupAction([ PushRosNamespace('rear'), Node( package='launch_demo', executable='lidar_node', name='lidar', ), ]) return LaunchDescription([front_group, rear_group])

这样前雷达发布的话题就是/front/lidar/scan,后雷达就是/rear/lidar/scan,互不干扰。有些老资料会把PushRosNamespace和Group混着用,其实记住一个结论就行:PushRosNamespace是放在GroupAction里生效的,它改变的是该分组内所有节点和话题的命名空间前缀。

4.3 包含其他launch文件

当你启动完整机器人系统时,大概率需要复用别人的launch文件,比如仿真器的启动文件、导航栈的启动文件、底盘驱动的启动文件,不应该把这些内容复制粘贴到你自己的launch里,否则升级依赖包的时候你的launch就崩了。正确做法是使用IncludeLaunchDescription。

ROS2的标准写法是:

from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import ThisLaunchFileDir from launch.actions import IncludeLaunchDescription def generate_launch_description(): gazebo_launch = IncludeLaunchDescription( PythonLaunchDescriptionSource([ ThisLaunchFileDir(), '/gazebo.launch.py' ]), launch_arguments={'world': 'empty.world'}.items() ) return LaunchDescription([gazebo_launch])

这里用ThisLaunchFileDir()来获取当前launch文件所在的目录,然后拼接出子launch的路径。这种方式比写绝对路径可靠得多,因为别人下载你的包时目录可能完全不同。你还可以用find_package方式引入其他包里的launch:

from ament_index_python.packages import get_package_share_directory import os turtle_launch_dir = get_package_share_directory('turtlesim') turtle_launch_file = os.path.join(turtle_launch_dir, 'launch', 'turtlesim_node.launch.py')

不过这种写法要注意,如果turtlesim包本身没有安装launch文件到share目录,os.path.join会得到一个不存在的路径,此时启动会报错。

4.4 条件与循环

launch文件本质上是Python代码,所以条件判断和循环可以直接写。比如我想根据一个布尔参数决定是否启动RViz:

from launch.actions import DeclareLaunchArgument from launch.substitutions import LaunchConfiguration, BooleanSubstitution from launch.conditions import IfCondition from launch_ros.actions import Node def generate_launch_description(): use_rviz = LaunchConfiguration('use_rviz') return LaunchDescription([ DeclareLaunchArgument( 'use_rviz', default_value='true' ), Node( package='rviz2', executable='rviz2', name='rviz2', condition=IfCondition(use_rviz) ), ])

IfCondition接收一个LaunchConfiguration或者其他替换对象,只有当它解析出的字符串是true时,这个节点才会被启动。对应的还有UnlessCondition。BooleanSubstitution常用于需要把字符串转成布尔值的场景,比如嵌套条件判断时。

循环就更好用了。比如要启动4个话题发送节点,可以直接在generate_launch_description里写for循环,把Node塞到列表中:

def generate_launch_description(): nodes = [] for i in range(4): nodes.append(Node( package='launch_demo', executable='lidar_node', name=f'lidar_{i}', namespace=f'robot{i}' )) return LaunchDescription(nodes)

这种灵活性在ROS1的XML launch里做不到,也是我推荐大家直接学Python格式的最重要原因。

5. 常见问题与排查技巧实录

5.1 launch文件修改了需要重新编译吗

这是新手问得最多的问题。你修改了launch文件的内容,比如改了一个参数、加了一个节点,直接在源码目录下再次运行ros2 launch,看到的结果可能是旧的。原因有两层。

第一层,ros2 launch默认从安装目录(install/launch_demo/share/launch_demo/launch/)查找launch文件,不是从你源码目录找。如果你修改的是源码目录里的launch文件,如果没有重新编译安装,运行时就找不到,或者找到的是上一次编译安装的版本。第二层,Python的launch文件不需要编译,但需要被复制到安装目录。所以修改launch文件后,请执行一次编译安装:

colcon build --packages-select launch_demo source install/setup.bash

如果你觉得每次敲build太麻烦,可以在源码目录下运行一个带--symlink-install的编译方式:

colcon build --symlink-install

这个方式会在安装目录里创建符号链接,指向源码文件,后续改动launch文件,只要重新source一下环境即可,连build都可以省略。我自己的习惯是个人开发阶段一律用--symlink-install,CI或者发布前再改成正常的build。

5.2 提示找不到launch文件

运行ros2 launch launch_demo demo.launch.py时如果提示找不到文件,先不要急着怀疑路径写错。第一步,确认包是否已经编译安装,ls install/launch_demo/share/launch_demo/launch/看看有没有你的launch文件。第二步,确认setup.py里是否加了data_files的glob规则。第三步,重新source环境,执行ros2 pkg prefix launch_demo,看看包路径是不是指向当前工作空间。

还有一个容易忽略的问题:launch文件的命名必须以.launch.py结尾。ros2 launch在查找时,会根据你给的名称自动拼接.launch.py后缀。如果你给的是demo.py,它会去找demo.py.launch.py,自然找不到。除非你使用绝对路径指定launch文件,比如ros2 launch ~/ros2_ws/src/launch_demo/launch/demo.launch.py,否则请按规定后缀命名。

5.3 节点启动顺序与依赖问题

ROS2节点之间是松耦合的,launch文件并行启动所有节点,理论上不保证哪一个先启动完成。如果你的代码逻辑里,A节点必须在B节点话题发布之后才能正常工作,你有几种处理方式。

第一种,代码里做好等待逻辑,用rclpy的wait_for_service或订阅回调加超时机制。第二种,在launch里使用TimerAction延迟启动某些节点:

from launch.actions import TimerAction TimerAction( period=5.0, actions=[Node(...)] )

第三种,使用OpaqueFunction在运行时主动检查话题、服务等是否就绪。这个方案更灵活,但写起来稍复杂。

我的建议是能不在launch层面等就不要等,尽量让代码本身具备等待机制。launch层面的延迟是“脚本式”的,如果启动环境变慢,固定延迟可能不够,又或者变快,延迟浪费了启动时间,不是根治方案。

5.4 多工作空间source与launch查找顺序

ros2 launch命令底层会根据环境变量AMENT_PREFIX_PATH去查找功能包。如果你在多个工作空间里source过不同版本的同名包,启动时实际生效的是环境中排在前面那个。这个坑我踩过一次:改完代码在A工作空间编译,却忘了source A的setup.bash,结果一直跑的是B工作空间的旧版本包,报错报得莫名其妙。

排查方法很简单,运行ros2 pkg prefix launch_demo会告诉你当前解析到这个包的绝对路径,再看一眼echo $AMENT_PREFIX_PATH里哪些路径在前面,心里就有数了。source的顺序、shell的启动脚本(比如~/.bashrc)里是否自动source了旧工作空间,都值得检查一遍。

5.5 常见报错信息速查表

场景报错信息常见原因解决方案
找不到launch文件Package 'launch_demo' not found未source环境或未编译安装colcon build后`` source install/setup.bash`
找不到launch文件The launch file ... does not existsetup.py没有安装launch目录或文件名后缀不对检查data_files和.launch.py后缀
找不到可执行文件executable "lidar_node" not foundsetup.py的entry_points未注册或包名不对检查console_scripts入口
节点参数报错parameter 'frame_id' not declared你没有调用declare_parameter在节点代码中声明参数
启动卡住没有日志输出output未设置为'screen'给Node加output='screen'
包含launch失败InvalidLaunchFileException被包含文件里没有generate_launch_description检查被包含launch文件本身能否直接运行

6. 实战:用launch一次启动Gazebo仿真和机器人状态发布

6.1 场景描述

写到这里,我想用一个更贴近真实项目的例子收尾。假设你正在做一个差速小车仿真,需要同时启动Gazebo仿真环境、机器人模型描述、状态发布节点和RViz。没有launch的时候,你至少需要开4到5个终端。用launch之后,一条命令就能全起来。

这个实战其实也是我笔记里4.6节最原始的需求:当时我在做fishbot仿真,每次都要手动启动Gazebo、robot_state_publisher、joint_state_publisher和RViz,后来我把这一整套写成了launch,爽快多了。

6.2 完整launch代码与说明

import os from launch import LaunchDescription from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import LaunchConfiguration, PathJoinSubstitution from launch_ros.actions import Node from ament_index_python.packages import get_package_share_directory def generate_launch_description(): pkg_share = get_package_share_directory('launch_demo') # 声明是否启动Gazebo use_gazebo = LaunchConfiguration('use_gazebo', default='true') # 加载机器人模型文件(xacro文件) urdf_path = os.path.join(pkg_share, 'urdf', 'my_robot.urdf') # robot_state_publisher:把URDF发布到TF robot_state_publisher = Node( package='robot_state_publisher', executable='robot_state_publisher', name='robot_state_publisher', output='screen', parameters=[{'robot_description': urdf_path}] ) # joint_state_publisher:发布关节状态 joint_state_publisher = Node( package='joint_state_publisher', executable='joint_state_publisher', name='joint_state_publisher', output='screen' ) # Gazebo启动,使用gazebo_ros的gazebo.launch.py gazebo_launch = IncludeLaunchDescription( PythonLaunchDescriptionSource(PathJoinSubstitution([ get_package_share_directory('gazebo_ros'), 'launch', 'gazebo.launch.py' ])), launch_arguments={ 'world': os.path.join(pkg_share, 'worlds', 'empty.world') }.items() ) # 将机器人模型写入Gazebo spawn_entity = Node( package='gazebo_ros', executable='spawn_entity.py', arguments=['-topic', 'robot_description', '-entity', 'my_robot'], output='screen' ) # RViz rviz = Node( package='rviz2', executable='rviz2', name='rviz2', output='screen' ) return LaunchDescription([ DeclareLaunchArgument( 'use_gazebo', default_value='true', description='是否启动Gazebo仿真环境' ), robot_state_publisher, joint_state_publisher, gazebo_launch, spawn_entity, rviz, ])

这段代码里有一个地方需要重点说明:robot_description参数为什么会传一个URDF文件的路径?在robot_state_publisher节点内部,它会读取该参数,如果参数是文件路径,ROS2会尝试把它作为文件读取;但如果传的是字符串内容,它就把同步内容当作XML直接解析。这两种方式我在实际项目里都见过。如果希望启动时完全由launch控制,可以把文件读成字符串再传:

with open(urdf_path, 'r') as f: robot_desc = f.read() # 然后 parameters=[{'robot_description': robot_desc}]

两种方式都行,区别在于后面一种在launch执行阶段就完成了文件读取,启动过程更稳定,不容易受当前工作目录影响。

7. 我踩过的坑和最终建议

最近一次用launch做完整仿真启动,我遇到过一个问题:launch文件里同时启动了joint_state_publisher和robot_state_publisher,但在RViz里怎么都看不到机器人的TF树。排查了一下午才发现,joint_state_publisher默认发布的关节消息会被robot_state_publisher覆盖,导致TF断断续续。最终解决方式是给joint_state_publisher设置use_sim_time为true,并且确保Gazebo里的仿真时间被正确同步,也就是在parameters里加上{'use_sim_time': True}。这个问题不写出来,可能很多人要折腾很久。

还有一个体会是,launch文件本身也是代码,也应该做版本管理、写注释、保持结构清晰。我见过不少项目的launch文件写得像一坨乱麻,所有内容全堆在一个巨型文件里,几千行都不止。维护起来的痛苦程度不亚于维护一段面条式代码。我自己的原则是:一个launch文件只负责一个主题,比如robot_bringup.launch.py负责启动整机系统,gazebo_sim.launch.py负责仿真,多个launch之间用IncludeLaunchDescription串联,而不是所有逻辑写在一个文件里。

最后,如果你刚开始学launch,我建议从最小例子开始跑通,先写一个只有单个节点的launch文件,再逐步加参数、命名空间、包含、条件。不要一上来就抄整车的launch文件,里面的OpaqueFunction、RegisterEventHandler、OnProcessExit这些高级特性,初学者看两遍基本是懵的。等基础版本的launch文件能顺畅跑起来,再去看那些复杂的官方launch,就会觉得豁然开朗。launch这套东西一旦上手,你会发现开发效率的提升不是一点半点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询