目录
-
- 一、发射器是粒子系统的源头
- 二、开发环境与版本说明
- 三、原理分析:三种发射模式
-
- [3.1 emitRate 连续发射](#3.1 emitRate 连续发射)
- [3.2 burst(count) 脉冲发射](#3.2 burst(count) 脉冲发射)
- [3.3 pulse(duration) 定时脉冲](#3.3 pulse(duration) 定时脉冲)
- [3.4 三种模式对比](#3.4 三种模式对比)
- [四、代码实现:Concept_EmitterMethods.qml 逐段解析](#四、代码实现:Concept_EmitterMethods.qml 逐段解析)
-
- [4.1 左栏:burst 脉冲发射](#4.1 左栏:burst 脉冲发射)
- [4.2 右栏:pulse 定时脉冲](#4.2 右栏:pulse 定时脉冲)
- [4.3 为什么用两个独立的 ParticleSystem](#4.3 为什么用两个独立的 ParticleSystem)
- 五、运行效果
-
- [左栏:burst 脉冲发射](#左栏:burst 脉冲发射)
- [右栏:pulse 定时脉冲](#右栏:pulse 定时脉冲)
- 六、适用边界与限制条件
- 七、总结与下篇预告
一、发射器是粒子系统的源头
粒子系统的所有视觉表现都始于 Emitter------它决定了粒子从哪里来、以什么频率出来、出来后往哪飞。如果把 ParticleSystem 比作一个舞台,Emitter 就是演员的入口;入口的大小、位置和开放方式,直接决定了演出的效果。
上一篇示例中展示了连续发射,但实际项目中的发射需求远不止这一种:爆炸效果需要"一次性涌出大量粒子",按钮点击反馈需要"点击后短暂发射",背景粒子需要"持续稳定输出"。这三种需求分别对应 Emitter 的三种发射模式:burst() 脉冲、pulse() 定时脉冲、emitRate 连续发射。
本文的目标是彻底理解这三种模式的行为差异、参数含义和适用场景,让你在面对任何发射需求时都能选择正确的方案。
二、开发环境与版本说明
本文所有代码基于以下环境验证(验证日期:2026-06-08):
- Qt 版本 :6.8.2(最低要求 Qt 6.5,参见 CMakeLists.txt 中
qt_standard_project_setup(REQUIRES 6.5)) - 编译器:MinGW 64-bit
- 操作系统:Windows 11
- 构建工具:CMake 3.29
三、原理分析:三种发射模式
3.1 emitRate 连续发射
emitRate 是 Emitter 最基础的属性,表示每秒发射的粒子数量。它的行为是:只要 enabled 为 true 且 ParticleSystem 处于 running 状态,就按照固定频率持续发射粒子。
qml
Emitter {
emitRate: 80 // 每秒发射 80 个粒子
lifeSpan: 2000 // 每个粒子存活 2 秒
size: 12
}
稳态粒子数的估算 :当发射和消亡达到平衡时,屏幕上的活跃粒子数约为 emitRate × lifeSpan / 1000。上面的例子中,稳态粒子数约为 80 × 2000 / 1000 = 160 个。这个公式在调参时非常有用------如果你想让屏幕上有 500 个粒子,可以设 emitRate: 100, lifeSpan: 5000 或 emitRate: 250, lifeSpan: 2000。
emitRate 为 0 时 :Emitter 不会自动发射任何粒子,但仍可通过 burst() 手动触发。这在"只在交互时发射"的场景中很常见。
3.2 burst(count) 脉冲发射
burst(count) 在调用瞬间一次性发射指定数量的粒子,不依赖 emitRate。它适合需要"爆发感"的场景:爆炸、点击光效、烟花绽放。
qml
Emitter {
id: emitter
emitRate: 0 // 默认不发射
lifeSpan: 2000
size: 12
velocity: AngleDirection {
angle: 0
angleVariation: 360 // 全方向扩散
magnitude: 100
}
}
// 用户点击时触发
MouseArea {
onClicked: emitter.burst(50) // 一次性发射 50 个粒子
}
burst 的关键特征:
- 瞬时性:所有粒子在同一帧诞生,视觉上是"同时涌出"
- 不干扰 emitRate :
burst()和emitRate互不影响,可以在连续发射的同时触发脉冲 - 可重复调用:每次调用都追加新粒子,不会清除之前的粒子
3.3 pulse(duration) 定时脉冲
pulse(duration) 在 Emitter 未启用时,将其启用指定毫秒数,到期后自动关闭。它是 emitRate 和 burst() 之间的折中方案------既有持续性,又有时间限制。注意:pulse() 只在 Emitter 当前未启用(enabled: false)时有效,如果 Emitter 已经启用,调用 pulse() 不会有额外效果。
qml
Emitter {
id: emitter
emitRate: 200 // 脉冲期间每秒发射 200 个
enabled: false // 默认禁用
lifeSpan: 2000
size: 12
}
// 用户点击时触发
MouseArea {
onClicked: emitter.pulse(500) // 启用 500ms,期间按 emitRate: 200 发射
}
pulse 的关键特征:
- 渐进性 :粒子在
duration毫秒内陆续出现,不像burst()那样同时涌出 - 自动关闭 :
pulse()结束后,Emitter 自动恢复为enabled: false - 受 emitRate 影响 :脉冲期间的发射量 =
emitRate × duration / 1000
pulse() 的效果由 emitRate 和 duration 共同决定。emitRate 控制粒子密度,duration 控制持续时间。调用 pulse(500) 时,Emitter 在 500ms 内按 emitRate 发射,到期后自动禁用。duration 太短(< 200ms)效果接近 burst(),太长(> 2000ms)就失去了"脉冲"的感觉。一般建议 300-1000ms。
3.4 三种模式对比
| 维度 | emitRate 连续 | burst(count) | pulse(duration) |
|---|---|---|---|
| 发射方式 | 持续均匀 | 瞬时爆发 | 限时持续 |
| 粒子出现节奏 | 陆续出现 | 同时涌出 | 陆续出现 |
| 是否受 emitRate 影响 | 是 | 否 | 是 |
| 是否自动停止 | 否(除非 disable) | 是(一次完成) | 是(duration 到期) |
| 典型场景 | 背景粒子、持续效果 | 爆炸、点击反馈 | 短暂喷射、呼吸效果 |
用一张决策流程图来总结:
#mermaid-svg-XL6zsikvHzFp6hLy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XL6zsikvHzFp6hLy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XL6zsikvHzFp6hLy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XL6zsikvHzFp6hLy .error-icon{fill:#552222;}#mermaid-svg-XL6zsikvHzFp6hLy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XL6zsikvHzFp6hLy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XL6zsikvHzFp6hLy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XL6zsikvHzFp6hLy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XL6zsikvHzFp6hLy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XL6zsikvHzFp6hLy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XL6zsikvHzFp6hLy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XL6zsikvHzFp6hLy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XL6zsikvHzFp6hLy .marker.cross{stroke:#333333;}#mermaid-svg-XL6zsikvHzFp6hLy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XL6zsikvHzFp6hLy p{margin:0;}#mermaid-svg-XL6zsikvHzFp6hLy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XL6zsikvHzFp6hLy .cluster-label text{fill:#333;}#mermaid-svg-XL6zsikvHzFp6hLy .cluster-label span{color:#333;}#mermaid-svg-XL6zsikvHzFp6hLy .cluster-label span p{background-color:transparent;}#mermaid-svg-XL6zsikvHzFp6hLy .label text,#mermaid-svg-XL6zsikvHzFp6hLy span{fill:#333;color:#333;}#mermaid-svg-XL6zsikvHzFp6hLy .node rect,#mermaid-svg-XL6zsikvHzFp6hLy .node circle,#mermaid-svg-XL6zsikvHzFp6hLy .node ellipse,#mermaid-svg-XL6zsikvHzFp6hLy .node polygon,#mermaid-svg-XL6zsikvHzFp6hLy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XL6zsikvHzFp6hLy .rough-node .label text,#mermaid-svg-XL6zsikvHzFp6hLy .node .label text,#mermaid-svg-XL6zsikvHzFp6hLy .image-shape .label,#mermaid-svg-XL6zsikvHzFp6hLy .icon-shape .label{text-anchor:middle;}#mermaid-svg-XL6zsikvHzFp6hLy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XL6zsikvHzFp6hLy .rough-node .label,#mermaid-svg-XL6zsikvHzFp6hLy .node .label,#mermaid-svg-XL6zsikvHzFp6hLy .image-shape .label,#mermaid-svg-XL6zsikvHzFp6hLy .icon-shape .label{text-align:center;}#mermaid-svg-XL6zsikvHzFp6hLy .node.clickable{cursor:pointer;}#mermaid-svg-XL6zsikvHzFp6hLy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XL6zsikvHzFp6hLy .arrowheadPath{fill:#333333;}#mermaid-svg-XL6zsikvHzFp6hLy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XL6zsikvHzFp6hLy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XL6zsikvHzFp6hLy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XL6zsikvHzFp6hLy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XL6zsikvHzFp6hLy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XL6zsikvHzFp6hLy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XL6zsikvHzFp6hLy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XL6zsikvHzFp6hLy .cluster text{fill:#333;}#mermaid-svg-XL6zsikvHzFp6hLy .cluster span{color:#333;}#mermaid-svg-XL6zsikvHzFp6hLy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XL6zsikvHzFp6hLy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XL6zsikvHzFp6hLy rect.text{fill:none;stroke-width:0;}#mermaid-svg-XL6zsikvHzFp6hLy .icon-shape,#mermaid-svg-XL6zsikvHzFp6hLy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XL6zsikvHzFp6hLy .icon-shape p,#mermaid-svg-XL6zsikvHzFp6hLy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XL6zsikvHzFp6hLy .icon-shape .label rect,#mermaid-svg-XL6zsikvHzFp6hLy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XL6zsikvHzFp6hLy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XL6zsikvHzFp6hLy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XL6zsikvHzFp6hLy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 持续不断的粒子
一次性爆发大量粒子
短暂持续发射
你需要什么效果?
emitRate: N
(连续发射)
emitRate: 0 + burst(N)
(脉冲发射)
emitRate: N + pulse(duration)
(定时脉冲)
背景雪花、星空、水面波纹
爆炸、点击光效、烟花绽放
短暂喷射、呼吸灯、触发效果
四、代码实现:Concept_EmitterMethods.qml 逐段解析
项目中 Concept_EmitterMethods.qml 并排展示了 burst() 和 pulse() 两种方法的效果对比。页面分为左右两栏,每栏各有一个粒子系统和一个触发按钮。
4.1 左栏:burst 脉冲发射
qml
ParticleSystem {
id: ps1
anchors.fill: parent
running: root.isCurrentItem
ImageParticle {
source: "qrc:/images/star.png"
color: "#FF6B6B"
alpha: 0.9
}
Emitter {
id: burstEmitter
anchors.centerIn: parent
width: 1
height: 1
emitRate: 0
lifeSpan: 2000
size: 12
velocity: AngleDirection {
angle: 0
angleVariation: 360
magnitude: 100
}
}
}
emitRate: 0 ------Emitter 默认不发射任何粒子。只有当用户点击按钮调用 burst(50) 时才会产生粒子。这是 burst() 模式的典型写法:把 emitRate 设为 0,完全由手动触发控制。
width: 1, height: 1 ------Emitter 的发射区域设为 1×1 像素,近似一个点。配合 angleVariation: 360,粒子从中心点向四面八方均匀扩散,形成爆炸般的视觉效果。
触发逻辑:
qml
MouseArea {
id: burstArea
anchors.fill: parent
hoverEnabled: true
onClicked: burstEmitter.burst(50)
}
每次点击调用 burst(50),瞬间发射 50 个粒子。由于 lifeSpan: 2000,点击后 2 秒内粒子会逐渐消亡。如果在粒子消亡前再次点击,新旧粒子会叠加,形成更密集的效果。
4.2 右栏:pulse 定时脉冲
qml
Emitter {
id: pulseEmitter
anchors.centerIn: parent
width: 1
height: 1
emitRate: 200
enabled: false
lifeSpan: 2000
size: 12
velocity: AngleDirection {
angle: 0
angleVariation: 360
magnitude: 100
}
}
emitRate: 200, enabled: false ------与 burst 模式的关键区别:这里 emitRate 设为 200(脉冲期间每秒发射 200 个),但 enabled 默认为 false(不发射)。pulse(500) 会在 500ms 内临时启用 Emitter,期间按 emitRate: 200 发射,500ms 后自动恢复 enabled: false。
触发逻辑:
qml
MouseArea {
onClicked: pulseEmitter.pulse(500)
}
pulse(500) 意味着每次点击后,Emitter 在 500ms 内发射约 200 × 0.5 = 100 个粒子。与 burst(50) 相比,粒子不是同时涌出,而是在半秒内陆续出现,视觉上更柔和。
duration 选择 500ms 的理由:太短(如 200ms)效果接近 burst,太长(如 2000ms)就失去了"脉冲"的感觉。500ms 是一个能体现"陆续出现"特性的中间值。
4.3 为什么用两个独立的 ParticleSystem
左右两栏各自拥有独立的 ParticleSystem(ps1 和 ps2),而不是共享一个。这是因为:
- 粒子系统的坐标是相对于 ParticleSystem 容器的,两个独立系统可以各自在自己的区域内发射
- 避免两个 Emitter 的粒子混在一起,影响对比效果
- 更清晰地展示
burst()和pulse()的行为差异
五、运行效果
运行项目后,点击左侧导航栏的「发射器」进入本示例页面。页面分为左右两栏,并排展示 burst() 和 pulse() 的效果对比。
左栏:burst 脉冲发射

点击红色圆形按钮触发 burst(50)。观察粒子的诞生方式:50 个红色星形粒子在同一帧同时从中心涌出,向四面八方均匀扩散(angleVariation: 360),形成"爆炸"般的瞬间扩散效果。2 秒后(lifeSpan: 2000)粒子逐渐消亡。
验证方式:连续快速点击多次,观察粒子叠加效果。由于 burst() 每次追加新粒子而不清除旧粒子,多次点击后屏幕上会同时存在多批粒子,密度明显增大。
右栏:pulse 定时脉冲

点击青色圆形按钮触发 pulse(500)。观察粒子的诞生方式:与 burst 不同,粒子不是同时涌出,而是在 500ms 内陆续从中心发射出来(emitRate: 200,500ms 内约发射 100 个粒子)。
验证方式:点击后观察粒子的出现节奏。前 500ms 内粒子持续涌出,500ms 后 Emitter 自动关闭(enabled 恢复为 false),不再有新粒子产生。已发射的粒子按 lifeSpan: 2000 自然消亡。
burst 与 pulse 的核心区别:
两者的差异体现在粒子的诞生方式上:
- burst:所有粒子在同一帧诞生,视觉上是"同时涌出"
- pulse :粒子在
duration毫秒内陆续诞生,视觉上是"逐渐流出"
六、适用边界与限制条件
burst() 和 emitRate 可以同时使用 :burst() 是额外追加粒子,不影响 emitRate 的连续发射。比如设 emitRate: 20 作为背景粒子,同时在用户点击时 burst(50) 追加爆发效果。
pulse() 的前置条件 :pulse() 只在 Emitter 当前未启用(enabled: false)时有效。如果 Emitter 已经处于启用状态,调用 pulse() 不会有额外效果。pulse() 结束后,Emitter 自动恢复为 enabled: false。
maximumEmitted 安全阀 :maximumEmitted 限制该 Emitter 的最大活跃粒子数。当活跃粒子数达到上限时,新的发射请求会被忽略。默认值为 -1(无限制)。在 burst() 大量发射时尤其有用,防止性能崩溃。
lifeSpan 的边界值:设为 0 时粒子立即消亡,视觉上看不到任何效果;设为 -1 时粒子永生(不会消亡),粒子数会无限增长直到性能崩溃。
emitRate 的性能上限 :设为极大值(如 10000)时,瞬间发射大量粒子可能导致帧率骤降。如果需要更密集的效果,优先增大 lifeSpan 而不是 emitRate------增大 lifeSpan 让粒子存活更久,自然积累更多;增大 emitRate 则会增加每秒的发射开销。
七、总结与下篇预告
本文讲解了 Emitter 的三种发射模式:
| 模式 | 核心特征 | 选择依据 |
|---|---|---|
emitRate 连续 |
持续均匀发射 | 需要不间断的粒子效果 |
burst(count) |
瞬时大量发射 | 需要爆发感的一次性效果 |
pulse(duration) |
限时持续发射 | 需要短暂但有持续性的效果 |
选择策略:持续效果用 emitRate,交互反馈用 burst,定时效果用 pulse,混合场景可以组合使用。
下一篇将讲解粒子的外观渲染------ImageParticle 的 GPU 批量渲染能力和 ItemParticle 的 QML 组件渲染能力,以及如何在两者之间做出正确的选型。
资源下载 :qml_particlesystem ------ 包含完整的、可运行的代码
系列目录:
- 上一篇:Qt Quick 粒子系统(二):系统控制与生命周期管理
- 本文:Qt Quick 粒子系统(三):发射器深度解析
- 下一篇:Qt Quick 粒子系统(四):渲染器对比与选型指南