源码版本 :Gazebo Sim 10 (Jetty),对应仓库 gazebosim/gz-sim 参考 :Zread - gz-sim 源码解读 ,Gazebo Sim API 文档 本文定位 :不仅分析源码架构,还覆盖组件生态的用法和实战配置,帮助无人机开发者快速上手
一、为什么要读 Gazebo Sim 源码? Gazebo Sim(原 Ignition Gazebo)是当前机器人仿真领域事实标准之一。它是 Gazebo Classic 的完全重写版本,积累了超过 16 年的机器人仿真经验。理解其源码对以下场景有直接价值:
自定义物理仿真 :需要修改物理步进逻辑、添加自定义力/力矩
高性能传感器开发 :编写自定义传感器插件(如事件相机、声纳阵列)
PX4/Ardupilot SITL 集成 :理解仿真端如何与飞控软件交互
Sim-to-Real 调优 :精确控制仿真步长、物理引擎参数、传感器噪声模型
分布式仿真 :理解多 Runner、网络同步的实现机制
本文将从源码层面逐层剖析 Gazebo Sim 的核心架构。
二、仓库结构总览 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 gz-sim/ ├── src/ # 核心源码 │ ├── Server.cc # 仿真服务器入口 │ ├── ServerPrivate.cc # Server 内部实现 │ ├── SimulationRunner.cc # 仿真主循环(核心中的核心) │ ├── SimulationRunner.hh # Runner 头文件 │ ├── EntityComponentManager.cc # ECS 管理器实现 │ ├── SystemManager.cc # System 生命周期管理 │ ├── SystemLoader.cc # 插件动态加载 │ ├── SdfEntityCreator.cc # SDF → Entity 转换 │ ├── LevelManager.cc # 关卡/LOD 管理 │ ├── Barrier.cc # 线程同步屏障 │ ├── network/ # 分布式仿真网络层 │ │ ├── NetworkManagerPrimary.cc │ │ └── NetworkManagerSecondary.cc │ ├── gui/ # GUI 相关源码 │ └── systems/ # 60+ 内置 System 插件 │ ├── physics/ # 物理系统(核心) │ ├── sensors/ # 传感器系统 │ ├── diff_drive/ # 差速驱动 │ ├── multicopter_motor_model/ # 多旋翼电机模型 │ ├── imu/ # IMU 传感器 │ └── ... ├── include/gz/sim/ # 公共 API 头文件 │ ├── Server.hh │ ├── System.hh # System 接口定义 │ ├── EntityComponentManager.hh │ ├── Entity.hh │ ├── Types.hh │ └── components/ # 所有 Component 类型定义 ├── examples/ # 示例 │ ├── plugin/ # 插件示例 │ ├── standalone/ # 独立程序示例 │ └── worlds/ # SDF 世界文件 ├── test/ # 测试套件 └── tutorials/ # 70+ 教程文档
代码量约 92.2% C++,5.4% QML,其余为 CMake。
三、ECS 架构:Gazebo Sim 的设计哲学 Gazebo Sim 的核心设计模式是 Entity-Component-System (ECS) ,这与游戏引擎(如 Unity DOTS、Unreal Mass)中广泛使用的架构一致。ECS 将数据与逻辑彻底分离,带来三个关键优势:
内存局部性 :同类型 Component 连续存储,缓存友好
并行性 :System 之间可独立执行,适合多线程
可组合性 :通过组合不同 Component 创建任意实体,无需继承层次
3.1 三个核心概念
概念
源码位置
作用
Entity
Entity.hh
纯 ID(uint64_t),无任何数据或行为
Component
components/*.hh
纯数据容器,挂载到 Entity 上
System
System.hh
纯逻辑,操作具有特定 Component 组合的 Entity
3.2 Entity:一个 uint64_t 的 ID Entity 的定义极其简单:
1 2 3 using Entity = uint64_t ;const Entity kNullEntity = std::numeric_limits<Entity>::max ();
Entity 本身不存储任何数据,只是一个标识符。仿真世界中的所有对象——World、Model、Link、Joint、Sensor、Visual——都是 Entity,区别仅在于它们挂载了不同的 Component。
3.3 Component:纯数据容器 Component 是类型化的数据容器。Gazebo Sim 预定义了大量 Component 类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 components::Pose components::WorldPose components::LinearVelocity components::AngularVelocity components::Inertial components::Gravity components::Physics components::Name components::Model components::Link components::Joint components::Sensor components::World components::ParentEntity components::Imu components::Camera components::GpuLidar
Component 的核心特征是无行为 ——它只存储数据,所有逻辑都在 System 中实现。
3.4 EntityComponentManager(ECM):ECS 的中枢 EntityComponentManager 是整个 ECS 的核心数据结构,管理所有 Entity 和 Component 的创建、查询、修改和删除。
源码位置 :src/EntityComponentManager.cc(约 2343 行),头文件 include/gz/sim/EntityComponentManager.hh
关键 API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Entity CreateEntity () ;template <typename ComponentTypeT>ComponentTypeT* CreateComponent (const Entity _entity, const ComponentTypeT &_data) ;template <typename ComponentTypeT>const ComponentTypeT* Component (const Entity _entity) const ;template <typename ... ComponentTypeTs>Entity EntityByComponents (const ComponentTypeTs&... _desiredComponents) const ;template <typename ... ComponentTypeTs>void Each (typename identity<std::function<bool ( const Entity&, const ComponentTypeTs*...)>>::type _f) const ;void RequestRemoveEntity (Entity _entity, bool _recursive = true ) ;
Entity 层次图(Directed Graph) ECM 内部维护一个有向图来表示 Entity 之间的父子关系:
1 2 3 4 using EntityGraph = math::graph::DirectedGraph<Entity, bool >;const EntityGraph& Entities () const ;std::unordered_set<Entity> Descendants (Entity _entity) const ;
典型的 Entity 层次结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 World (Entity 1) ├── Model "quadrotor" (Entity 2) │ ├── Link "base_link" (Entity 3) │ │ ├── Visual "body_visual" (Entity 4) │ │ ├── Collision "body_collision" (Entity 5) │ │ └── Sensor "imu_sensor" (Entity 6) │ ├── Link "rotor_0" (Entity 7) │ │ └── Joint "rotor_0_joint" (Entity 8) │ ├── Link "rotor_1" (Entity 9) │ ... ├── Model "ground_plane" (Entity 20) │ └── Link "link" (Entity 21) └── Light "sun" (Entity 30)
状态变更追踪 ECM 精细追踪每个 Component 的变更状态,这对于高效的状态序列化和网络同步至关重要:
1 2 3 4 5 6 enum class ComponentState { NoChange = 0 , PeriodicChange = 1 , OneTimeChange = 2 };
ChangedState() 方法只序列化变更过的 Component,大幅减少网络传输和日志记录的数据量。
友元类设计 ECM 有几个特权友元类,它们可以调用 protected 方法来管理 Entity 生命周期:
1 2 3 4 5 friend class GuiRunner ;friend class SimulationRunner ;friend class SystemManager ;friend class NetworkManagerPrimary ;friend class NetworkManagerSecondary ;
这些方法包括 ClearNewlyCreatedEntities()、ProcessRemoveEntityRequests()、SetAllComponentsUnchanged() 等,普通 System 插件无法访问。
四、Server:仿真服务器的入口 源码位置 :src/Server.cc
Server 是用户与 Gazebo Sim 交互的主要入口点。它的构造函数完成以下工作:
1 2 3 4 5 6 7 8 9 10 Server::Server (const ServerConfig &_config) { }
4.1 启动流程 1 2 3 4 5 6 7 8 9 10 11 12 13 Server(config) │ ├── 解析 SDF Root │ └── 获取所有 World 定义 │ ├── CreateSimulationRunners(sdfRoot) │ └── 每个 World → 一个 SimulationRunner │ ├── SetupTransport() │ └── 建立 gz-transport 主题和服务 │ └── DownloadAssets(config) └── 后台线程下载 Fuel 模型资源
4.2 Server::Run() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 bool Server::Run (const bool _blocking, const uint64_t _iterations, const bool _paused) { for (auto &runner : this ->dataPtr->simRunners) runner->SetPaused (_paused); if (_blocking) return this ->dataPtr->Run (_iterations); this ->dataPtr->runThread = std::thread ( &ServerPrivate::Run, this ->dataPtr.get (), _iterations, &cond); }
关键设计点:
支持阻塞 和非阻塞 两种运行模式
非阻塞模式在新线程中运行,通过 condition_variable 保证 running 标志在函数返回前被设置
RunOnce(_paused) 是 Run(true, 1, _paused) 的便捷封装
五、SimulationRunner:仿真主循环的核心 源码位置 :src/SimulationRunner.cc(约 1912 行)——这是整个 Gazebo Sim 最核心的文件。
5.1 核心成员变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class SimulationRunner { std::unique_ptr<SystemManager> systemMgr; EventManager eventMgr; std::unique_ptr<ParametersRegistry> parametersRegistry; EntityComponentManager entityCompMgr; EntityComponentManager initialEntityCompMgr; std::unique_ptr<LevelManager> levelMgr; std::unique_ptr<NetworkManager> networkMgr; std::chrono::steady_clock::duration updatePeriod{2 ms}; std::chrono::steady_clock::duration stepSize{10 ms}; double desiredRtf{1.0 }; double realTimeFactor{0.0 }; UpdateInfo currentInfo; std::vector<std::thread> postUpdateThreads; std::unique_ptr<Barrier> postUpdateStartBarrier; std::unique_ptr<Barrier> postUpdateStopBarrier; std::unique_ptr<transport::Node> node; transport::Node::Publisher statsPub; transport::Node::Publisher clockPub; };
5.2 构造函数:世界初始化 构造函数中有一段关键的 RTF 计算逻辑值得关注:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 SimulationRunner::SimulationRunner (const sdf::World &_world, ...) { const sdf::Physics *physics = _world.PhysicsByIndex (0 ); this ->stepSize = std::chrono::duration_cast <...>( std::chrono::duration <double >(physics->MaxStepSize ())); this ->desiredRtf = physics->RealTimeFactor (); this ->updatePeriod = std::chrono::nanoseconds ( static_cast <int >(this ->stepSize.count () / this ->desiredRtf)); this ->systemMgr = std::make_unique <SystemManager>( _systemLoader, &this ->entityCompMgr, &this ->eventMgr, ...); this ->levelMgr = std::make_unique <LevelManager>(this , ...); if (_createEntities) { this ->SetWorldSdf (_world); this ->SetCreateEntities (); this ->CreateEntities (); } this ->node->Advertise ("control" , &SimulationRunner::OnWorldControl, this ); this ->node->Advertise ("control/state" , ...); this ->node->Advertise ("playback/control" , ...); }
5.3 Run():主循环 Run() 方法是整个仿真的驱动心脏:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 bool SimulationRunner::Run (const uint64_t _iterations) { this ->running = true ; this ->statsPub = this ->node->Advertise <msgs::WorldStatistics>("stats" ); this ->clockPub = this ->node->Advertise <msgs::Clock>("clock" ); uint64_t processedIterations{0 }; auto nextUpdateTime = std::chrono::steady_clock::now () + this ->updatePeriod; while (this ->running && (_iterations == 0 || processedIterations < _iterations)) { if (this ->createEntities) this ->CreateEntities (); this ->UpdatePhysicsParams (); this ->UpdateCurrentInfo (); if (this ->resetInitiated) this ->entityCompMgr.ResetTo (this ->initialEntityCompMgr); this ->Step (this ->currentInfo); if (!this ->currentInfo.paused) { auto sleepTarget = nextUpdateTime - 200u s; std::this_thread::sleep_until (sleepTarget); while (std::chrono::steady_clock::now () < nextUpdateTime) { } nextUpdateTime += this ->updatePeriod; } } return true ; }
时间控制策略的精妙之处 :Gazebo Sim 采用混合 sleep/busy-wait 策略来实现精确的 RTF 控制。纯 sleep_until 会因 CPU C-state 省电模式导致唤醒延迟(通常 50-200μs),导致 RTF 偏低。因此在距离目标时间 200μs 时切换到 busy-wait,确保精确到达目标时刻。
5.4 Step():单步执行 Step() 是每个仿真步的完整工作流:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 void SimulationRunner::Step (const UpdateInfo &_info) { this ->currentInfo = _info; this ->ProcessNewWorldControlState (); this ->PublishStats (); this ->levelMgr->UpdateLevelsState (); this ->ProcessSystemQueue (); this ->ProcessRecreateEntitiesRemove (); this ->systemMgr->ProcessPendingEntitySystems (); this ->UpdateSystems (); this ->ProcessMessages (); this ->entityCompMgr.ClearNewlyCreatedEntities (); this ->ProcessRecreateEntitiesCreate (); this ->systemMgr->ProcessRemovedEntities (...); this ->entityCompMgr.ProcessRemoveEntityRequests (); this ->entityCompMgr.ClearRemovedComponents (); this ->entityCompMgr.SetAllComponentsUnchanged (); }
5.5 UpdateSystems():三阶段更新 这是每个仿真步中最关键的函数——它按照三个阶段顺序执行所有 System:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void SimulationRunner::UpdateSystems () { if (this ->resetInitiated) { this ->systemMgr->Reset (this ->currentInfo, this ->entityCompMgr); return ; } for (auto & [priority, systems] : this ->systemMgr->SystemsPreUpdate ()) { for (auto & system : systems) system->PreUpdate (this ->currentInfo, this ->entityCompMgr); } for (auto & [priority, systems] : this ->systemMgr->SystemsUpdate ()) { for (auto & system : systems) system->Update (this ->currentInfo, this ->entityCompMgr); } this ->entityCompMgr.LockAddingEntitiesToViews (true ); if (this ->postUpdateStartBarrier && this ->postUpdateStopBarrier) { MaybeGilScopedRelease release; this ->postUpdateStartBarrier->Wait (); this ->postUpdateStopBarrier->Wait (); } this ->entityCompMgr.LockAddingEntitiesToViews (false ); }
三阶段的设计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ┌─────────────────────────────────────────────────────────┐ │ 一个仿真步(Step) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ │ PreUpdate │ → │ Update │ → │ PostUpdate │ │ │ │ (顺序) │ │ (顺序) │ │ (并行) │ │ │ │ │ │ │ │ │ │ │ │ 读写 ECM │ │ 读写 ECM │ │ 只读 ECM │ │ │ │ │ │ │ │ │ │ │ │ 控制信号 │ │ 物理步进 │ │ 传感器/控制器输出 │ │ │ │ 网络同步 │ │ 碰撞检测 │ │ 日志记录 │ │ │ │ 关节指令 │ │ 约束求解 │ │ 遥测发布 │ │ │ └──────────┘ └──────────┘ └──────────────────┘ │ │ │ │ ←——————— 同一线程 ————————→ ←— 多线程并行 ——→ │ └─────────────────────────────────────────────────────────┘
六、System 接口:插件的四种”生命周期钩子” 源码位置 :include/gz/sim/System.hh
6.1 接口定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class System {public : using PriorityType = int32_t ; constexpr static PriorityType kDefaultPriority = {0 }; constexpr static std::string_view kPriorityElementName = {"gz:system_priority" }; }; class ISystemConfigure { virtual void Configure ( const Entity &_entity, const std::shared_ptr<const sdf::Element> &_sdf, EntityComponentManager &_ecm, EventManager &_eventMgr) = 0 ;}; class ISystemPreUpdate { virtual void PreUpdate (const UpdateInfo &_info, EntityComponentManager &_ecm) = 0 ;}; class ISystemUpdate { virtual void Update (const UpdateInfo &_info, EntityComponentManager &_ecm) = 0 ;}; class ISystemPostUpdate { virtual void PostUpdate (const UpdateInfo &_info, const EntityComponentManager &_ecm) = 0 ;}; class ISystemReset { virtual void Reset (const UpdateInfo &_info, EntityComponentManager &_ecm) = 0 ;};
注意 PostUpdate 的 ECM 参数是 const 引用——PostUpdate 只能读取状态,不能修改。
6.2 UpdateInfo 结构 1 2 3 4 5 6 7 8 struct UpdateInfo { std::chrono::steady_clock::duration simTime{0 }; std::chrono::steady_clock::duration realTime{0 }; std::chrono::steady_clock::duration dt{0 }; uint64_t iterations{0 }; bool paused{true }; };
关于 simTime 的重要说明 :simTime 不是”当前时刻”,而是 PreUpdate 和 Update 执行完成后 到达的时刻。如果仿真暂停(paused = true),时间不前进,simTime 保持不变。
6.3 优先级系统 System 的执行顺序由优先级控制,数值越小越先执行:
1 2 3 4 5 6 7 namespace systems { constexpr System::PriorityType kUserCommandsPriority = -16384 ; constexpr System::PriorityType kPrePhysicsPriority = -128 ; constexpr System::PriorityType kPhysicsPriority = -64 ; constexpr System::PriorityType kPostPhysicsSensorPriority = -32 ; }
执行顺序:UserCommands (-16384) → 用户 PrePhysics (-128) → Physics (-64) → PostPhysicsSensor (-32) → 默认 System (0)
这保证了:
用户指令先被处理
物理引擎在用户控制信号之后执行
传感器在物理更新之后采样
6.4 Physics System:物理引擎的桥接 源码位置 :src/systems/physics/Physics.hh
Physics System 是 Gazebo Sim 中最重要的内置 System,它实现了 ISystemConfigure、ISystemUpdate 和 ISystemReset 接口:
1 2 3 4 5 6 7 8 9 10 11 class Physics : public System, public ISystemConfigure, public ISystemConfigurePriority, public ISystemReset, public ISystemUpdate { };
Physics System 通过 gz-physics 库的 Feature 系统与具体物理引擎(DART、Bullet Featherstone、TPE)对接,使用的 Feature 包括:
1 2 3 4 5 6 7 8 9 10 11 #include <gz/physics/ForwardStep.hh> #include <gz/physics/GetEntities.hh> #include <gz/physics/Joint.hh> #include <gz/physics/Link.hh> #include <gz/physics/Shape.hh> #include <gz/physics/ContactProperties.hh> #include <gz/physics/FreeGroup.hh> #include <gz/physics/FixedJoint.hh> #include <gz/physics/mesh/MeshShape.hh> #include <gz/physics/heightmap/HeightmapShape.hh>
6.5 PostUpdate 的并行执行机制 PostUpdate 是三个阶段中唯一并行执行的。源码中使用 Barrier 同步原语实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void SimulationRunner::ProcessSystemQueue () { unsigned int threadCount = this ->systemMgr->SystemsPostUpdate ().size () + 1u ; this ->postUpdateStartBarrier = std::make_unique <Barrier>(threadCount); this ->postUpdateStopBarrier = std::make_unique <Barrier>(threadCount); for (auto &system : this ->systemMgr->SystemsPostUpdate ()) { this ->postUpdateThreads.push_back (std::thread ([&]() { while (this ->postUpdateThreadsRunning) { this ->postUpdateStartBarrier->Wait (); if (this ->postUpdateThreadsRunning) system->PostUpdate (this ->currentInfo, this ->entityCompMgr); this ->postUpdateStopBarrier->Wait (); } })); } }
每个 PostUpdate System 有自己的专属线程,通过两个 Barrier 实现”启动-等待”同步:
1 2 3 4 5 主线程: ──┤ StartBarrier.Wait() ├────────────┤ StopBarrier.Wait() ├── │ │ PostUpdate 0: ├─── PostUpdate() ──────────────────┤ PostUpdate 1: ├─── PostUpdate() ──────────────────┤ PostUpdate 2: ├─── PostUpdate() ──────────────────┤
注意 :在 PostUpdate 并行执行期间,ECM 会锁定视图的实体添加(LockAddingEntitiesToViews(true)),防止并发修改。同时,如果编译了 Python 支持,主线程会释放 GIL(MaybeGilScopedRelease),允许 Python 编写的 PostUpdate System 在各自线程中获取 GIL。
七、插件加载机制 7.1 动态库加载 System 插件以共享库(.so/.dll)形式存在,通过 SystemLoader 动态加载:
1 2 3 4 5 6 7 SystemPluginPtr SystemLoader::LoadPlugin (const sdf::Plugin &_plugin) { }
7.2 插件注册 每个插件通过 GZ_ADD_PLUGIN 宏注册:
1 2 3 4 5 GZ_ADD_PLUGIN ( hello_world::HelloWorld, gz::sim::System, hello_world::HelloWorld::ISystemPostUpdate)
7.3 SDF 中的插件配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <world name ="example" > <plugin filename ="gz-sim-physics-system" name ="gz::sim::systems::Physics" > <gz:system_priority > -64</gz:system_priority > </plugin > <plugin filename ="gz-sim-sensors-system" name ="gz::sim::systems::Sensors" > <render_engine > ogre2</render_engine > </plugin > <model name ="quadrotor" > <plugin filename ="gz-sim-multicopter-motor-model-system" name ="gz::sim::systems::MulticopterMotorModel" > </plugin > </model > </world >
八、SDF → Entity 的转换过程 源码位置 :src/SdfEntityCreator.cc
SdfEntityCreator 负责将 SDF 文件中的描述转换为 ECS 中的 Entity + Component:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 SDF World │ ├── <model> → Entity + {Model, Name, Pose, Static, ...} │ │ │ ├── <link> → Entity + {Link, Name, Pose, Inertial, ...} │ │ │ │ │ ├── <visual> → Entity + {Visual, Name, Geometry, Material, ...} │ │ ├── <collision> → Entity + {Collision, Name, Geometry, ...} │ │ └── <sensor> → Entity + {Sensor, Name, SensorType, ...} │ │ │ └── <joint> → Entity + {Joint, Name, JointType, ParentLink, ChildLink, ...} │ ├── <light> → Entity + {Light, Name, Pose, ...} │ └── <physics> → Components on World Entity {Physics, Gravity, ...}
每个 SDF 元素被转换为一个 Entity,并挂载对应的 Component。ParentEntity Component 维护父子关系。
九、分布式仿真架构 Gazebo Sim 支持分布式仿真,通过 Primary/Secondary Runner 架构实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ┌─────────────────────────────────┐ │ Primary Runner │ │ │ │ ┌───────────────────────────┐ │ │ │ EntityComponentManager │ │ │ │ (完整世界状态) │ │ │ └───────────────────────────┘ │ │ NetworkManagerPrimary │ │ - 同步状态到 Secondary │ │ - 收集 Secondary 结果 │ └──────────┬──────────────────────┘ │ gz-transport ┌──────┴──────┐ ▼ ▼ ┌──────────┐ ┌──────────┐ │Secondary │ │Secondary │ │Runner 1 │ │Runner 2 │ │(区域 A) │ │(区域 B) │ └──────────┘ └──────────┘
Primary Runner 通过 NetworkManagerPrimary 协调 Secondary Runner,每个 Secondary 负责仿真世界的一部分(通过 Performer/Level 系统划分)。状态变更通过 ChangedState() 序列化后经 gz-transport 传输。
十、关键设计模式与工程亮点 10.1 Reset 机制 Gazebo Sim 通过保存初始 ECM 副本实现 Reset:
1 2 3 4 5 EntityComponentManager entityCompMgr; EntityComponentManager initialEntityCompMgr; this ->entityCompMgr.ResetTo (this ->initialEntityCompMgr);
10.2 Entity 重建(Recreate) 对于需要”删除后重新创建”的 Entity(如重置模型位姿),Gazebo Sim 分两步处理:
ProcessRecreateEntitiesRemove() — 标记待重建的 Entity 为删除
System 在 UpdateSystems() 中处理删除
ProcessRecreateEntitiesCreate() — 克隆原始 Entity 创建新实例
这确保新旧 Entity 不会同时存在(避免名称冲突),同时保证 System 能正确清理旧 Entity 的状态。
10.3 Python GIL 管理 由于 Gazebo Sim 支持 Python 编写的 System 插件,PostUpdate 的并行执行需要特殊的 GIL 处理:
1 2 3 4 5 6 7 struct MaybeGilScopedRelease { MaybeGilScopedRelease () { if (Py_IsInitialized () != 0 && PyGILState_Check () == 1 ) this ->release.emplace (); } std::optional<pybind11::gil_scoped_release> release; };
主线程在 PostUpdate 前释放 GIL,让各 PostUpdate 线程中的 Python System 可以独立获取 GIL 执行 Python 代码。
10.4 时间控制精度 RTF 公式推导:
R T F = s i m _ t i m e r e a l _ t i m e = s t e p _ s i z e p e r i o d RTF = \frac{sim\_time}{real\_time} = \frac{step\_size}{period} R T F = r e a l _ t im e s im _ t im e = p er i o d s t e p _ s i z e
∴ p e r i o d = s t e p _ s i z e R T F \therefore period = \frac{step\_size}{RTF} ∴ p er i o d = R T F s t e p _ s i z e
默认配置下:step_size = 10ms,RTF = 1.0,所以 period = 10ms(100Hz 更新频率)。
如果设置 RTF = 2.0,则 period = 5ms,仿真以双倍速运行。
十一、内置 System 一览 Gazebo Sim 包含 60+ 内置 System,按功能分类:
物理与运动
System
功能
阶段
Physics
物理引擎步进
Update
DiffDrive
差速驱动控制
PreUpdate
AckermannSteering
阿克曼转向
PreUpdate
JointController
关节控制器
PreUpdate
JointPositionController
关节位置控制
PreUpdate
MulticopterMotorModel
多旋翼电机模型
PreUpdate
MulticopterVelocityControl
多旋翼速度控制
PreUpdate
Buoyancy
浮力
PreUpdate
Hydrodynamics
水动力
PreUpdate
LiftDrag
升力/阻力
PreUpdate
ApplyJointForce
施加关节力
PreUpdate
Thruster
推进器
PreUpdate
TrajectoryFollower
轨迹跟随
PreUpdate
传感器
System
功能
阶段
Sensors
传感器管理(渲染传感器)
PostUpdate
Imu
IMU 传感器
PostUpdate
Altimeter
高度计
PostUpdate
AirPressure
气压传感器
PostUpdate
AirSpeed
空速传感器
PostUpdate
ForceTorque
力/力矩传感器
PostUpdate
NavSat
GNSS/GPS 传感器
PostUpdate
Contact
接触传感器
PostUpdate
通信
System
功能
AcousticComms
水下声学通信
RFComms
射频通信(含距离/噪声模型)
PerfectComms
理想通信
CommsEndpoint
通信端点
工具与可视化
System
功能
PosePublisher
位姿发布
SceneBroadcaster
场景状态广播
LogRecord / LogPlayback
日志记录/回放
UserCommands
用户指令处理
BatteryPlugin
电池建模
WindEffects
风场效果
DetachableJoint
可分离关节
十二、对无人机仿真开发者的实战指导 12.1 编写自定义 System 插件 最小可运行的自定义 System:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <gz/sim/System.hh> #include <gz/plugin/Register.hh> namespace my_systems {class CustomForce : public gz::sim::System, public gz::sim::ISystemConfigure, public gz::sim::ISystemPreUpdate { public : void Configure (const gz::sim::Entity &_entity, const std::shared_ptr<const sdf::Element> &_sdf, gz::sim::EntityComponentManager &_ecm, gz::sim::EventManager &) override { this ->entity = _entity; if (_sdf->HasElement ("force_magnitude" )) this ->forceMag = _sdf->Get <double >("force_magnitude" ); } void PreUpdate (const gz::sim::UpdateInfo &_info, gz::sim::EntityComponentManager &_ecm) override { if (_info.paused) return ; auto linkEntity = _ecm.EntityByComponents ( gz::sim::components::Link (), gz::sim::components::ParentEntity (this ->entity), gz::sim::components::Name ("base_link" )); if (linkEntity != gz::sim::kNullEntity) { auto forceComp = _ecm.Component < gz::sim::components::ExternalWorldWrenchCmd>(linkEntity); } } private : gz::sim::Entity entity{gz::sim::kNullEntity}; double forceMag{0.0 }; }; } GZ_ADD_PLUGIN ( my_systems::CustomForce, gz::sim::System, my_systems::CustomForce::ISystemConfigure, my_systems::CustomForce::ISystemPreUpdate)
12.2 关键注意事项
PreUpdate 和 Update 是顺序执行的 :不要在其中做耗时操作,否则会拖慢整个仿真
PostUpdate 是并行执行且只读的 :传感器数据处理、日志记录、遥测发布应放在 PostUpdate
Configure 时并非所有 Entity 都已加载 :如果需要访问插件父元素之外的 Entity,应在 PreUpdate 中延迟初始化
Entity 删除是延迟的 :RequestRemoveEntity() 只是入队,实际删除在 Step 末尾的 ProcessRemoveEntityRequests() 中执行
优先级很重要 :如果你的 System 需要在物理步进前应用力,确保优先级小于 kPhysicsPriority(-64)
12.3 PX4 SITL 集成的底层逻辑 在 Gazebo + PX4 SITL 仿真中:
PX4 通过 gz-transport 订阅传感器数据(IMU、GPS、气压计等——由各传感器 System 在 PostUpdate 阶段发布)
PX4 计算控制输出,通过 gz-transport 发布执行器指令
MulticopterMotorModel System 在 PreUpdate 阶段读取执行器指令,计算推力/力矩
Physics System 在 Update 阶段执行物理步进,应用所有力和力矩
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 PX4 SITL Gazebo Sim ┌──────────┐ ┌──────────────────────┐ │ │ actuator_cmds │ PreUpdate: │ │ 控制器 │ ─────────────────→ │ MulticopterMotor │ │ │ │ Model (读取指令, │ │ │ │ 计算推力/力矩) │ │ │ │ │ │ │ │ Update: │ │ │ │ Physics (物理步进) │ │ │ │ │ │ │ sensor_data │ PostUpdate: │ │ 状态估计 │ ←───────────────── │ IMU/GPS/Baro │ │ │ │ (发布传感器数据) │ └──────────┘ └──────────────────────┘
十三、性能关键路径分析 通过源码中的 GZ_PROFILE 宏标注,可以识别性能关键路径:
1 2 3 4 5 6 7 8 9 10 11 12 SimulationRunner::Run └── SimulationRunner::Step ├── PublishStats (低开销) ├── ProcessSystemQueue (仅在添加/删除 System 时触发) └── UpdateSystems (★ 主要开销) ├── PreUpdate (顺序,单线程) ├── Update (顺序,单线程) │ └── Physics (物理引擎步进 — 通常最耗时) └── PostUpdate (并行,多线程) ├── Sensors (渲染传感器 — GPU 密集) ├── IMU (计算密集) └── ...
性能瓶颈 通常在:
Physics Update :碰撞检测和约束求解是 CPU 密集型
Sensors PostUpdate :渲染类传感器(相机、LiDAR)是 GPU 密集型
ECM 查询 :大量 Each() 遍历在 Entity 数量很多时可能成为瓶颈
十四、Gazebo Sim 相比 Classic 的核心优势 很多开发者对”为什么要从 Gazebo Classic 迁移到 Gazebo Sim”感到困惑。下面从实际开发体验出发,解释每个架构差异带来的具体好处 。
14.1 架构对比总览
特性
Gazebo Classic
Gazebo Sim
对开发者的实际好处
架构模式
面向对象(继承层次)
ECS(数据驱动)
添加新功能不需要修改核心代码,只需组合 Component
物理引擎
内置 ODE,可选 Bullet/DART/Simbody
通过 gz-physics 抽象,插件化
一行 SDF 配置切换物理引擎,对比不同引擎的仿真精度
渲染引擎
OGRE 1.x
OGRE 2.x(通过 gz-rendering)
PBR 材质、全局光照、更真实的阴影——视觉制导仿真质量大幅提升
传感器
内置于核心
独立库 gz-sensors
可独立测试传感器,不需要启动完整仿真
通信
自定义 TCP
gz-transport(protobuf)
跨进程、跨机器通信,原生支持 protobuf 序列化
分布式仿真
不支持
Primary/Secondary Runner
大规模蜂群仿真可分布到多台机器
插件 API
World/Model/Sensor Plugin(3 套 API)
统一的 System 接口
只需学习一套接口,PreUpdate/Update/PostUpdate 覆盖所有需求
Python 支持
有限
完整(pybind11)
直接用 Python 编写 System 插件,适合快速原型验证
日志/回放
有限
完整的状态序列化
精确回放任意时间点的仿真状态,便于调试和回归测试
并行执行
单线程
PostUpdate 多线程并行
多传感器场景(相机+LiDAR+IMU)性能显著提升
14.2 ECS 对无人机开发者的具体好处 在 Gazebo Classic 中,如果你想给无人机添加一个自定义的气动力模型,你需要继承 ModelPlugin,在 Load() 中获取 Link 指针,在 OnUpdate() 中施加力。但如果你同时还想读取这个力的结果来做日志记录,你需要再写一个单独的插件或者在同一个插件中塞入日志逻辑。
在 Gazebo Sim 中,关注点天然分离 :
施加力 → 写一个实现 ISystemPreUpdate 的 System,优先级设为 kPrePhysicsPriority
记录日志 → 写一个实现 ISystemPostUpdate 的 System,自动并行执行
两个 System 通过 ECM 中的 Component 隐式通信,完全解耦
14.3 物理引擎切换 Gazebo Sim 通过 gz-physics 的 Feature 抽象层,可以在 SDF 中一行配置切换物理引擎:
1 2 3 4 5 6 7 8 9 10 11 <physics name ="1ms" type ="dart" > <max_step_size > 0.001</max_step_size > <real_time_factor > 1.0</real_time_factor > </physics > <physics name ="1ms" type ="bullet" > <max_step_size > 0.001</max_step_size > <real_time_factor > 1.0</real_time_factor > </physics >
对截击机仿真的意义:可以用 DART 做常规飞行测试,用 Bullet Featherstone 做高速碰撞场景测试,同一套 SDF 模型无需任何修改。
14.4 渲染质量提升 Gazebo Classic 使用 OGRE 1.x,光照和材质效果有限。Gazebo Sim 使用 OGRE 2.x,支持:
PBR(Physically Based Rendering)材质 :金属度/粗糙度工作流
全局光照 :更真实的间接照明
级联阴影贴图 :高质量实时阴影
GPU 传感器噪声 :相机噪声在 GPU 着色器中实现,不影响 CPU 性能
对视觉制导截击机的仿真尤为重要——更真实的渲染意味着视觉算法在仿真中的表现更接近真实世界。
十五、Gazebo 组件生态与用法详解 Gazebo Sim 不是一个单体应用,而是由多个独立库组成的生态系统。理解这些组件才能真正发挥 Gazebo Sim 的能力。
15.1 生态系统总览 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌──────────────────────────────────────────────────────┐ │ gz-sim (本文核心) │ │ Server / SimulationRunner / ECM / Systems │ ├──────────────────────────────────────────────────────┤ │ 依赖的库 │ │ │ │ gz-physics gz-rendering gz-sensors │ │ (物理引擎) (渲染引擎) (传感器模拟) │ │ │ │ gz-transport gz-msgs gz-gui │ │ (通信层) (消息定义) (GUI 框架) │ │ │ │ gz-math gz-common gz-fuel-tools │ │ (数学库) (通用工具) (模型下载) │ │ │ │ gz-plugin SDFormat gz-tools │ │ (插件加载) (场景描述) (CLI 工具) │ └──────────────────────────────────────────────────────┘
15.2 gz-transport:通信层 gz-transport 是 Gazebo 生态的”神经系统”,基于 ZeroMQ + protobuf 实现跨进程通信。
发布者示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <gz/transport/Node.hh> #include <gz/msgs/actuators.pb.h> gz::transport::Node node; auto pub = node.Advertise <gz::msgs::Actuators>("/X3/command/motor_speed" );gz::msgs::Actuators msg; msg.mutable_velocity ()->Resize (4 , 0 ); msg.set_velocity (0 , 700.0 ); msg.set_velocity (1 , 700.0 ); msg.set_velocity (2 , 700.0 ); msg.set_velocity (3 , 700.0 ); pub.Publish (msg);
订阅者示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <gz/transport/Node.hh> #include <gz/msgs/imu.pb.h> void OnImu (const gz::msgs::IMU &_msg) { auto lin = _msg.linear_acceleration (); auto ang = _msg.angular_velocity (); std::cout << "Accel: " << lin.x () << ", " << lin.y () << ", " << lin.z () << " | Gyro: " << ang.x () << ", " << ang.y () << ", " << ang.z () << std::endl; } gz::transport::Node node; node.Subscribe ("/imu" , OnImu); gz::transport::waitForShutdown ();
CMakeLists.txt 构建配置 1 2 3 4 5 6 7 8 9 10 cmake_minimum_required (VERSION 3.20 )project (my_gz_app)find_package (gz-transport13 REQUIRED)find_package (gz-msgs10 REQUIRED)add_executable (my_subscriber subscriber.cc)target_link_libraries (my_subscriber gz-transport13::gz-transport13 gz-msgs10::gz-msgs10)
15.3 gz CLI 工具:调试利器 Gazebo Sim 提供了一套强大的命令行工具,是仿真调试的核心手段。
主题(Topic)操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 gz topic -l gz topic -i -t /world/quadcopter/stats gz topic -e -t /imu gz topic -z -t /imu gz topic -t /X3/gazebo/command/motor_speed \ --msgtype gz.msgs.Actuators \ -p 'velocity:[700, 700, 700, 700]'
模型(Model)操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 gz model --list gz model -m X3 gz model -m X3 --pose gz model -m X3 --link base_link gz model -m X3 --joint rotor_0_joint
服务(Service)操作 1 2 3 4 5 6 7 8 9 gz service -l gz service -s /world/quadcopter/control \ --reqtype gz.msgs.WorldControl \ --reptype gz.msgs.Boolean \ --timeout 2000 \ --req 'pause: true'
Fuel 模型管理 1 2 3 4 5 gz fuel list -t model -o OpenRobotics gz fuel download -u https://fuel.gazebosim.org/1.0/OpenRobotics/models/X3%20UAV
15.4 传感器噪声模型配置 Gazebo Sim 通过 SDF 的 <noise> 标签为传感器添加高斯噪声,这对 Sim-to-Real 至关重要。
IMU 噪声配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <sensor name ="imu_sensor" type ="imu" > <always_on > 1</always_on > <update_rate > 400</update_rate > <imu > <angular_velocity > <x > <noise type ="gaussian" > <mean > 0.0</mean > <stddev > 0.0002</stddev > <bias_mean > 0.0000075</bias_mean > <bias_stddev > 0.0000008</bias_stddev > </noise > </x > </angular_velocity > <linear_acceleration > <x > <noise type ="gaussian" > <mean > 0.0</mean > <stddev > 0.017</stddev > <bias_mean > 0.1</bias_mean > <bias_stddev > 0.001</bias_stddev > </noise > </x > </linear_acceleration > </imu > </sensor >
相机噪声配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <sensor name ="camera_sensor" type ="camera" > <update_rate > 30</update_rate > <camera > <horizontal_fov > 1.047</horizontal_fov > <image > <width > 640</width > <height > 480</height > </image > <noise > <type > gaussian</type > <mean > 0.0</mean > <stddev > 0.007</stddev > </noise > </camera > </sensor >
LiDAR 噪声配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <sensor name ="lidar" type ="gpu_lidar" > <update_rate > 10</update_rate > <lidar > <scan > <horizontal > <samples > 640</samples > <resolution > 1</resolution > <min_angle > -1.396263</min_angle > <max_angle > 1.396263</max_angle > </horizontal > </scan > <range > <min > 0.08</min > <max > 10.0</max > <resolution > 0.01</resolution > </range > <noise > <type > gaussian</type > <mean > 0.0</mean > <stddev > 0.01</stddev > </noise > </lidar > </sensor >
15.5 MulticopterMotorModel:多旋翼电机模型详解 MulticopterMotorModel 是无人机仿真中最关键的 System 插件。它将转速指令转换为推力和力矩,施加到旋翼链接上。
完整参数配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <plugin filename ="gz-sim-multicopter-motor-model-system" name ="gz::sim::systems::MulticopterMotorModel" > <robotNamespace > X3</robotNamespace > <jointName > X3/rotor_0_joint</jointName > <linkName > X3/rotor_0</linkName > <turningDirection > ccw</turningDirection > <timeConstantUp > 0.0125</timeConstantUp > <timeConstantDown > 0.025</timeConstantDown > <maxRotVelocity > 800.0</maxRotVelocity > <motorConstant > 8.54858e-06</motorConstant > <momentConstant > 0.016</momentConstant > <rotorDragCoefficient > 8.06428e-05</rotorDragCoefficient > <rollingMomentCoefficient > 1e-06</rollingMomentCoefficient > <commandSubTopic > gazebo/command/motor_speed</commandSubTopic > <motorSpeedPubTopic > motor_speed/0</motorSpeedPubTopic > <actuator_number > 0</actuator_number > <rotorVelocitySlowdownSim > 10</rotorVelocitySlowdownSim > <motorType > velocity</motorType > </plugin >
推力计算原理 电机模型在每个 PreUpdate 步中:
读取指令转速 ω c m d \omega_{cmd} ω c m d (通过 gz-transport 订阅)
通过一阶滤波器模拟电机动态响应:
ω a c t u a l ( t + d t ) = ω a c t u a l ( t ) + d t τ ( ω c m d − ω a c t u a l ( t ) ) \omega_{actual}(t+dt) = \omega_{actual}(t) + \frac{dt}{\tau} (\omega_{cmd} - \omega_{actual}(t)) ω a c t u a l ( t + d t ) = ω a c t u a l ( t ) + τ d t ( ω c m d − ω a c t u a l ( t ))
计算推力和力矩:
T = k T ⋅ ω a c t u a l 2 T = k_T \cdot \omega^2_{actual} T = k T ⋅ ω a c t u a l 2
M = k M ⋅ T M = k_M \cdot T M = k M ⋅ T
其中 k T k_T k T = motorConstant,k M k_M k M = momentConstant,τ \tau τ = timeConstantUp/Down
15.6 完整四旋翼世界文件示例 以下是一个来自官方示例的完整 SDF 世界文件(examples/worlds/quadcopter.sdf),包含了所有必要的 System 插件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 <?xml version="1.0" ?> <sdf version ="1.6" > <world name ="quadcopter" > <physics name ="1ms" type ="ignored" > <max_step_size > 0.001</max_step_size > <real_time_factor > 1.0</real_time_factor > </physics > <plugin filename ="gz-sim-physics-system" name ="gz::sim::systems::Physics" > </plugin > <plugin filename ="gz-sim-user-commands-system" name ="gz::sim::systems::UserCommands" > </plugin > <plugin filename ="gz-sim-scene-broadcaster-system" name ="gz::sim::systems::SceneBroadcaster" > </plugin > <plugin filename ="gz-sim-imu-system" name ="gz::sim::systems::Imu" > </plugin > <plugin filename ="gz-sim-air-pressure-system" name ="gz::sim::systems::AirPressure" > </plugin > <light type ="directional" name ="sun" > <cast_shadows > true</cast_shadows > <pose > 0 0 10 0 0 0</pose > <diffuse > 0.8 0.8 0.8 1</diffuse > <specular > 0.2 0.2 0.2 1</specular > <direction > -0.5 0.1 -0.9</direction > </light > <model name ="ground_plane" > <static > true</static > <link name ="link" > <collision name ="collision" > <geometry > <plane > <normal > 0 0 1</normal > </plane > </geometry > </collision > <visual name ="visual" > <geometry > <plane > <normal > 0 0 1</normal > <size > 100 100</size > </plane > </geometry > </visual > </link > </model > <include > <uri > https://fuel.gazebosim.org/1.0/OpenRobotics/models/X3 UAV/4</uri > <plugin filename ="gz-sim-multicopter-motor-model-system" name ="gz::sim::systems::MulticopterMotorModel" > <robotNamespace > X3</robotNamespace > <jointName > X3/rotor_0_joint</jointName > <linkName > X3/rotor_0</linkName > <turningDirection > ccw</turningDirection > <timeConstantUp > 0.0125</timeConstantUp > <timeConstantDown > 0.025</timeConstantDown > <maxRotVelocity > 800.0</maxRotVelocity > <motorConstant > 8.54858e-06</motorConstant > <momentConstant > 0.016</momentConstant > <commandSubTopic > gazebo/command/motor_speed</commandSubTopic > <actuator_number > 0</actuator_number > <rotorDragCoefficient > 8.06428e-05</rotorDragCoefficient > <rollingMomentCoefficient > 1e-06</rollingMomentCoefficient > <motorSpeedPubTopic > motor_speed/0</motorSpeedPubTopic > <rotorVelocitySlowdownSim > 10</rotorVelocitySlowdownSim > <motorType > velocity</motorType > </plugin > </include > </world > </sdf >
运行与控制 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 gz sim quadcopter.sdf gz topic -t /X3/gazebo/command/motor_speed \ --msgtype gz.msgs.Actuators \ -p 'velocity:[700, 700, 700, 700]' gz topic -t /X3/gazebo/command/motor_speed \ --msgtype gz.msgs.Actuators \ -p 'velocity:[0, 0, 0, 0]' gz topic -e -t /imu gz model -m X3 --pose
15.7 完整自定义 System 插件(含 CMake 构建) 第十二节给出了一个不完整的示例,这里补充完整版,包括施加外部风力扰动和构建配置。
wind_disturbance.cc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <gz/sim/System.hh> #include <gz/sim/Model.hh> #include <gz/sim/Link.hh> #include <gz/sim/Util.hh> #include <gz/plugin/Register.hh> #include <gz/math/Vector3.hh> namespace custom_systems {class WindDisturbance : public gz::sim::System, public gz::sim::ISystemConfigure, public gz::sim::ISystemPreUpdate { public : void Configure (const gz::sim::Entity &_entity, const std::shared_ptr<const sdf::Element> &_sdf, gz::sim::EntityComponentManager &_ecm, gz::sim::EventManager &) override { this ->model = gz::sim::Model (_entity); if (_sdf->HasElement ("link_name" )) this ->linkName = _sdf->Get <std::string>("link_name" ); if (_sdf->HasElement ("wind_force_x" )) this ->windForce.X (_sdf->Get <double >("wind_force_x" )); if (_sdf->HasElement ("wind_force_y" )) this ->windForce.Y (_sdf->Get <double >("wind_force_y" )); if (_sdf->HasElement ("wind_force_z" )) this ->windForce.Z (_sdf->Get <double >("wind_force_z" )); } void PreUpdate (const gz::sim::UpdateInfo &_info, gz::sim::EntityComponentManager &_ecm) override { if (_info.paused) return ; if (this ->linkEntity == gz::sim::kNullEntity) { this ->linkEntity = this ->model.LinkByName (_ecm, this ->linkName); if (this ->linkEntity == gz::sim::kNullEntity) return ; } gz::sim::Link link (this ->linkEntity) ; link.AddWorldWrench (_ecm, this ->windForce, gz::math::Vector3d::Zero); } private : gz::sim::Model model{gz::sim::kNullEntity}; gz::sim::Entity linkEntity{gz::sim::kNullEntity}; std::string linkName{"base_link" }; gz::math::Vector3d windForce{0 , 0 , 0 }; }; } GZ_ADD_PLUGIN ( custom_systems::WindDisturbance, gz::sim::System, custom_systems::WindDisturbance::ISystemConfigure, custom_systems::WindDisturbance::ISystemPreUpdate)
CMakeLists.txt 1 2 3 4 5 6 7 cmake_minimum_required (VERSION 3.20 )project (wind_disturbance)find_package (gz-sim8 REQUIRED) add_library (WindDisturbance SHARED wind_disturbance.cc)target_link_libraries (WindDisturbance gz-sim8::gz-sim8)
SDF 中使用 1 2 3 4 5 6 7 8 9 10 <model name ="my_drone" > <plugin filename ="WindDisturbance" name ="custom_systems::WindDisturbance" > <link_name > base_link</link_name > <wind_force_x > 2.0</wind_force_x > <wind_force_y > 1.5</wind_force_y > <wind_force_z > 0.0</wind_force_z > </plugin > </model >
1 2 3 4 5 6 mkdir build && cd buildcmake .. && make GZ_SIM_SYSTEM_PLUGIN_PATH=$(pwd ) gz sim world.sdf
十六、参考资源
源码仓库 : github.com/gazebosim/gz-sim
API 文档 : gazebosim.org/api/sim/10/
Zread 源码解读 : zread.ai/gazebosim/gz-sim
架构设计文档 : doc/architecture_design.md
创建 System 插件教程 : tutorials/creating_system_plugins.md
使用 Component 教程 : gazebosim.org/api/sim/8/usingcomponents.html
gz-physics 库 : github.com/gazebosim/gz-physics
gz-sensors 库 : github.com/gazebosim/gz-sensors
gz-transport 库 : github.com/gazebosim/gz-transport
gz-rendering 库 : github.com/gazebosim/gz-rendering
gz-gui 库 : github.com/gazebosim/gz-gui
gz-fuel-tools 库 : github.com/gazebosim/gz-fuel-tools
Gazebo Fuel 模型库 : app.gazebosim.org/fuel
quadcopter.sdf 示例 : gz-sim/examples/worlds/quadcopter.sdf
gz-transport 通信教程 : gazebosim.org/api/transport/15/messages.html
MulticopterMotorModel 类文档 : gazebosim.org/api/sim/10/classgz_1_1sim_1_1systems_1_1MulticopterMotorModel.html