Qt Quick 粒子系统(一):架构总览与四层模型

Qt Quick 粒子系统(一):架构总览与四层模型 目录一、为什么需要粒子系统二、开发环境与版本说明三、原理分析四层架构3.1 ParticleSystem——容器与调度器3.2 Emitter——发射器3.3 ParticlePainter——渲染器3.4 Affector——影响器3.5 粒子的完整生命周期四、代码实现Concept_ParticleSystem.qml 逐段解析4.1 完整代码4.2 页面结构BaseRect PageTitle4.3 粒子特效层4.4 信息卡片层ListModel Repeater4.5 入场动画延迟渐入效果4.6 动画生命周期管理4.7 文字渲染4.8 项目骨架导航与页面管理4.9 通用组件复用五、运行效果六、常见问题与边界条件七、总结与下篇预告一、为什么需要粒子系统在 QML 开发中我们经常遇到这样的需求游戏中的爆炸火花、天气应用的雪花飘落、音乐播放器的频谱背景、按钮点击后的光点扩散。这些效果有一个共同特征——大量微小元素各自独立运动形成整体的视觉表现。如果用传统的 QML 动画实现每个元素都需要独立的NumberAnimation或Behavior100 个元素就是 100 个动画对象性能和代码量都会急剧恶化。而Qt Quick 粒子系统模块QtQuick.Particles正是为这类场景设计的它在 GPU 层面批量管理成百上千个粒子开发者只需声明发射规则和行为约束系统自动处理生命周期、运动计算和渲染。在正式学习之前先对比三种常见方案的适用边界方案适用场景性能上限开发复杂度QML 动画NumberAnimation 等单个元素的简单动效20个对象仅供参考低Canvas 手绘自定义绘制逻辑中等高ParticleSystem 模块大量独立运动粒子5000-10000粒子仅供参考中ShaderEffectGPU 全自定义最高很高本文是系列第一篇目标是建立 Qt Quick 粒子系统的全局认知框架——理解它的四层架构知道每个组件的职责和协作关系最终用 10 行代码写出一个最小粒子系统。二、开发环境与版本说明本文所有代码基于以下环境验证验证日期2026-06-05Qt 版本6.8.2QtQuick.Particles模块从 Qt 5 引入Qt 6 中沿用本文以 Qt 6 为准编译器MinGW 64-bit操作系统Windows 11构建工具CMake 3.29如果你使用 Qt 6.5 及以上版本代码无需修改即可运行。Qt 5.x 版本也可以使用粒子系统模块但import路径和部分 API 存在差异如import QtQuick.Particles 2.15本文统一以 Qt 6 为准。项目的 CMake 配置中关键部分如下# 启用 QML 模块 qt_add_qml_module(appqml_particlesystem URI qml_particlesystem VERSION 1.0 QML_FILES Main.qml Concept_ParticleSystem.qml # ... 其他 QML 文件 RESOURCES res.qrc )粒子系统本身不需要额外的 C 代码所有逻辑都在 QML 中完成。main.cpp仅负责创建QQmlApplicationEngine并加载入口 QML 文件QQmlApplicationEngine engine;engine.loadFromModule(qml_particlesystem,Main);三、原理分析四层架构Qt Quick 粒子系统的设计可以抽象为四层每层各司其职ParticleSystem容器/调度器管理所有粒子的生命周期、状态和全局调度Emitter发射器决定粒子从哪来、怎么来ParticlePainter渲染器决定粒子长什么样Affector影响器决定粒子发射后受什么力ImageParticleGPU 批量渲染ItemParticleQML 组件渲染3.1 ParticleSystem——容器与调度器ParticleSystem是所有粒子元素的宿主容器。它本身不发射、不渲染、不影响粒子而是负责生命周期管理跟踪每个粒子的诞生、运动和消亡全局调度协调 Emitter、Painter、Affector 之间的数据流状态控制通过running、paused、empty等属性管理系统运行状态一个粒子系统中可以包含多个 Emitter、多个 Painter 和多个 Affector它们通过父子关系或system属性关联到同一个 ParticleSystem// 方式一父子关系推荐简洁 ParticleSystem { ImageParticle { ... } Emitter { ... } } // 方式二显式 system 绑定组件分散在不同层级时使用 ParticleSystem { id: mySystem } ImageParticle { system: mySystem } Emitter { system: mySystem }3.2 Emitter——发射器Emitter决定粒子从哪里来、以什么方式来。它的核心职责包括发射区域通过shape属性限定粒子的出生位置矩形、椭圆、线条等发射频率emitRate控制每秒发射的粒子数量初始属性lifeSpan生命周期、size大小、velocity速度、acceleration加速度运动方向粒子的扩散方向由Direction子组件控制包括AngleDirection角度方向、PointDirection点方向、TargetDirection目标方向等后续文章将详细讲解粒子分组group属性将发射的粒子归入指定组一个 ParticleSystem 可以有多个 Emitter分别发射不同样式的粒子。3.3 ParticlePainter——渲染器ParticlePainter决定粒子长什么样。Qt Quick 提供了两种实现ImageParticle使用图片作为粒子外观GPU 批量渲染性能最优。支持着色、旋转、变形、透明度等属性。ItemParticle使用任意 QML 组件作为粒子外观灵活度最高支持交互点击、拖拽但每个粒子是独立的 Item性能较低。两者通过groups属性指定负责渲染哪些粒子组可以同时使用。3.4 Affector——影响器Affector在粒子发射后、消亡前对其属性进行二次修改。常见效果包括Gravity重力加速度Friction速度衰减Attractor向目标点吸引Wander随机游走Turbulence湍流扰动Affector 同样通过groups属性限定作用范围可以叠加使用。3.5 粒子的完整生命周期将四层串联起来一个粒子从诞生到消亡的完整流程是Emitter 发射粒子赋予初始位置、速度、大小、生命周期等属性ParticleSystem 调度粒子运动根据速度和加速度更新位置Affector 修改粒子属性施加重力、摩擦、湍流等力场ParticlePainter 渲染粒子根据当前属性绘制到屏幕生命周期结束粒子消亡理解这个流程是后续学习的基础——Emitter 控制从哪来Direction 控制往哪飞Affector 控制受什么力Painter 控制长什么样。四、代码实现Concept_ParticleSystem.qml 逐段解析项目中Concept_ParticleSystem.qml是第一个示例页面它同时做了两件事展示四层架构的知识卡片以及用粒子特效作为背景。下面先看完整代码再逐段解析关键设计。4.1 完整代码import QtQuick import QtQuick.Particles import QtQuick.Layouts import common BaseRect { id: root PageTitle { titleText: ParticleSystem - 粒子系统 } // ── 粒子特效层 ────────────────────────────────── Rectangle { Layout.fillWidth: true Layout.fillHeight: true Layout.margins: 10 color: transparent radius: 8 ParticleSystem { id: particleSystem anchors.fill: parent running: root.isCurrentItem ImageParticle { source: qrc:/images/star.png alpha: 0.5 } Emitter { anchors.centerIn: parent width: infoColumn.width height: infoColumn.height emitRate: 30 lifeSpan: 3000 size: 10 sizeVariation: 10 } Wander { system: particleSystem xVariance: 100 yVariance: 100 pace: 100 } } // ── 信息卡片层 ────────────────────────────── ColumnLayout { id: infoColumn anchors.fill: parent anchors.margins: 15 spacing: 15 visible: false ListModel { id: infoModel ListElement { title: ## ParticleSystem 是所有粒子元素的容器 titleColor: #4ECDC4 content: - 管理粒子生命周期、渲染和交互\n- 核心属性: running, paused, empty\n- 核心方法: start(), stop(), pause(), resume(), reset(), restart() } ListElement { title: ## Emitter - 发射器 titleColor: #FFE66D content: - 发射逻辑粒子到 ParticleSystem\n- 核心属性: emitRate, lifeSpan, size, velocity, acceleration, shape, group\n- 核心方法: burst(count), pulse(duration) } ListElement { title: ## ParticlePainter - 渲染器 titleColor: #FF6B6B content: - 指定如何绘制粒子\n- 子类: ImageParticle(图像渲染), ItemParticle(QML组件渲染)\n- 核心属性: groups, system } ListElement { title: ## Affector - 影响器 titleColor: #87CEEB content: - 在粒子生命周期任意时刻修改其属性\n- 子类: Age, Attractor, Friction, Gravity, Turbulence, Wander, SpriteGoal\n- 核心属性: acceleration, velocity, position, relative } } // ── 入场动画 Repeater ──────────────────── Repeater { id: infoRepeater model: infoModel ColumnLayout { id: delegateItem spacing: 5 Layout.fillWidth: true opacity: 0 transform: Translate { id: trans; y: 20 } NumberAnimation { id: entryAnim target: delegateItem property: opacity from: 0; to: 1 duration: 400 easing.type: Easing.OutCubic } NumberAnimation { id: slideAnim target: trans property: y from: 20; to: 0 duration: 400 easing.type: Easing.OutCubic } Timer { id: delayTimer interval: 500 index * 120 onTriggered: { entryAnim.start(); slideAnim.start() } } Connections { target: root function onIsCurrentItemChanged() { if (root.isCurrentItem) { delegateItem.opacity 0 trans.y 20 delayTimer.restart() } else { entryAnim.stop() slideAnim.stop() delayTimer.stop() } } } Component.onCompleted: { if (root.isCurrentItem) delayTimer.start() } // ── 文字渲染 ────────────────────── Text { Layout.fillWidth: true color: model.titleColor text: model.title textFormat: Text.MarkdownText wrapMode: Text.WordWrap } Text { Layout.fillWidth: true color: #aaa text: model.content textFormat: Text.MarkdownText wrapMode: Text.WordWrap } Rectangle { Layout.fillWidth: true Layout.topMargin: 5 height: 1 color: #333 visible: index infoModel.count - 1 } } } Item { Layout.fillHeight: true } } } }4.2 页面结构BaseRect PageTitle页面继承BaseRect而非直接使用Rectangle。BaseRect提供了两个关键能力自动布局内部使用ColumnLayout子元素自动纵向排列页面激活感知暴露isCurrentItem属性由 StackLayout 自动赋值PageTitle是通用标题组件18px 白色加粗居中显示。4.3 粒子特效层粒子特效层的核心是ParticleSystem包含三个子组件running: root.isCurrentItem——这是整个项目的核心模式。当用户切换到其他页面时StackLayout 会将isCurrentItem设为false粒子系统自动停止避免不可见时浪费 GPU。切换回来时自动恢复。Emitter尺寸绑定infoColumn——发射区域不是固定大小而是绑定到下方文字区域的尺寸。这样粒子从文字区域的范围内涌出形成文字背后有粒子的视觉效果。这是一个常见的设计技巧让发射区域跟随内容自适应。alpha: 0.5——粒子半透明避免遮挡前景文字。粒子系统和 UI 内容共存时透明度控制是关键。Wander随机漂移——粒子不仅从中心扩散还带有布朗运动般的漂移效果。xVariance: 100和yVariance: 100控制漂移幅度pace: 100控制漂移频率。4.4 信息卡片层ListModel RepeaterColumnLayoutinfoColumn设为visible: false它本身不显示——只为Emitter提供尺寸参考width: infoColumn.width。实际的文字显示由Repeater的 delegate 完成。ListModel定义了四层架构的描述数据每条包含标题颜色和内容。Repeater为每条数据生成一个ColumnLayoutdelegate包含标题 Text、内容 Text 和分割线 Rectangle。4.5 入场动画延迟渐入效果每张卡片都有一个精心设计的入场动画分三个阶段延迟Timer的interval: 500 index * 120第一张卡片延迟 500ms第二张 620ms第三张 740ms……形成逐张依次出现的效果渐入entryAnim将opacity从 0 动画到 1上滑slideAnim将Translate.y从 20 动画到 0卡片从下方滑入Easing.OutCubic缓动曲线让动画有快速进入、缓慢停下的质感比线性动画更自然。4.6 动画生命周期管理动画必须和页面激活状态同步。Connections监听root.isCurrentItemChanged信号页面激活时重置 opacity 和位置重启延迟 Timer动画从头播放页面离开时停止所有动画和 Timer避免后台无意义执行Component.onCompleted处理首次加载的边界情况——如果页面在组件创建时就已经是当前页比如应用启动时默认显示第一页直接启动动画。4.7 文字渲染Text.MarkdownText让标题中的##语法被正确渲染为二级标题样式。每张卡片之间用 1px 的深灰分割线隔开最后一张卡片不显示分割线visible: index infoModel.count - 1。4.8 项目骨架导航与页面管理所有示例页面都在Main.qml中通过ListViewStackLayout组织Window { RowLayout { ListView { id: navList Layout.preferredWidth: 180 model: ListModel { ListElement { name: 系统概述; category: 基本概念 } ListElement { name: 系统控制; category: 基本概念 } // ... 更多页面 } } StackLayout { id: contentStack Concept_ParticleSystem {} // 第 0 页 Concept_SystemControl {} // 第 1 页 // ... 更多页面 } } }ListView提供左侧分类导航StackLayout管理右侧内容区。点击导航项时contentStack.currentIndex切换BaseRect 的isCurrentItem自动跟随变化粒子系统和动画的启停全部自动化——开发者在每个页面中只需声明running: root.isCurrentItem即可。4.9 通用组件复用项目定义了几个通用组件保证页面风格一致组件文件职责BaseRectcommon/BaseRect.qml页面基础容器管理 isCurrentItemPageTitlecommon/PageTitle.qml页面标题18px 白色加粗居中BottomNotecommon/BottomNote.qml底部说明文字深灰背景SubTitleRowcommon/SubTitleRow.qml小节副标题ColorButtoncommon/ColorButton.qml彩色按钮组件这些组件将布局和样式从业务逻辑中抽离让每个示例页面只需关注粒子系统本身的代码。五、运行效果运行项目后主界面如下左侧180px 宽的导航栏按分类分组基本概念、发射区域、扩散方向、粒子随机性、粒子影响器、尾迹发射器、粒子系统示例——规划分类目前还在做右侧StackLayout 内容区显示当前选中的粒子示例粒子行为切换页面时当前页的粒子系统自动运行离开时自动停止点击「系统概述」进入第一个示例页面可以看到四层架构的概览信息和背景粒子效果运行截图说明页面展示 ParticleSystem / Emitter / ParticlePainter / Affector 四层组件的职责说明背景有 Wander 随机漂移的星形粒子。项目规划有 7 个分类、30 个示例页面覆盖了 Qt Quick 粒子系统的完整功能。后续文章将逐个模块深入讲解。六、常见问题与边界条件QQt 5 项目能否使用粒子系统可以。Qt 5 的QtQuick.Particles模块功能基本一致但 import 语句需要指定版本号// Qt 5 写法 import QtQuick.Particles 2.15 // Qt 6 写法推荐 import QtQuick.ParticlesQParticleSystem 和 QML 的 Behavior/NumberAnimation 动画有什么区别Behavior和NumberAnimation适合单个元素的属性动画如按钮缩放、颜色渐变。ParticleSystem 适合大量独立运动对象的场景——它在引擎层面批量处理粒子数据避免了为每个粒子创建独立动画对象的开销。简单判断标准如果动画对象超过 20 个考虑 ParticleSystem。Q粒子系统的性能上限是多少取决于设备 GPU 能力、粒子大小和渲染方式。使用ImageParticleGPU 批量渲染时中端设备通常可支持数千个活跃粒子。使用ItemParticleQML 组件渲染时由于每个粒子都是独立的 QML Item建议控制在 100 个以内。性能优化的关键手段不可见时停止粒子系统running: isCurrentItem使用maximumEmitted限制最大活跃粒子数优先使用 ImageParticle 而非 ItemParticleQ粒子系统的running属性默认是什么默认为true。如果在页面切换场景中使用务必绑定为false或绑定到页面可见性属性避免不可见时仍消耗 GPU 资源。七、总结与下篇预告本文建立了 Qt Quick 粒子系统的四层架构认知ParticleSystem是容器和调度器管理全局生命周期Emitter是发射器决定粒子从哪来、怎么来ParticlePainter是渲染器决定粒子长什么样ImageParticle 批量渲染 / ItemParticle 组件渲染Affector是影响器决定粒子发射后受什么力记住这个四层模型容器、发、画、改——容器管理一切发射器创造粒子渲染器绘制粒子影响器改造粒子。下一篇将深入 ParticleSystem 的状态控制详细讲解start()/stop()/pause()/resume()/reset()/restart()六种方法的行为差异以及running/paused/empty三态模型的工作机制。资源下载qml_particlesystem —— 包含完整的、可运行的代码系列目录本文Qt Quick 粒子系统一架构总览与四层模型下一篇Qt Quick 粒子系统二系统控制与生命周期管理