目录
- 一、问题:从"固定点"到"发射区域"
- 1.1 固定点发射的局限
- 1.2 Emitter 默认的矩形区域
- 1.3 探索路径:需求递进
- 二、开发环境与版本说明
- 三、原理:五种方案如何解决这个需求
- 3.1 五种方案对比
- 3.2 RectangleShape:最基础的区域控制
- 3.3 EllipseShape:从矩形到曲线
- 3.4 LineShape:从面到线
- 3.5 MaskShape:从预定义到任意形状
- 3.6 移动发射区域:从静态到动态
- 四、代码实现与运行效果
- 4.1 Shape_Rectangle.qml:矩形发射区域
- 4.2 Shape_Ellipse.qml:椭圆发射区域(fill 对比)
- 4.3 Shape_Line.qml:线性发射区域(mirrored 对比)
- 4.4 Shape_Mask.qml:图像遮罩发射区域
- 4.5 Shape_Moving.qml:移动发射区域
- 五、运行效果
- 六、适用边界与限制条件
- 6.1 组合使用规则
- 6.2 前置条件
- 6.3 安全阀
- 6.4 边界值
- 6.5 性能建议
- 七、总结
一、问题:从"固定点"到"发射区域"
1.1 固定点发射的局限
前面五篇中,Emitter 的粒子都从一个固定位置(或很小的矩形区域)发射。这种方式适合简单的粒子源效果,但真实场景往往需要更精确的控制:
- 雪花飘落——粒子应该从屏幕顶部的一条线上均匀落下
- 光环扩散——粒子应该从圆环的边界向外喷射,而不是从圆心
- Logo 动效——粒子应该从特定图案的轮廓中飞出
固定点发射无法满足这些需求,我们需要一种机制来定义"粒子从哪里诞生"。
1.2 Emitter 默认的矩形区域
其实在不设置 Shape 的情况下,Emitter 已经有一种"发射区域"——它的整个矩形边界。粒子从这个矩形内的任意位置随机发射。
但这个默认行为有一个明显的问题:矩形太粗糙。圆形、椭圆形、线形、任意自定义形状,矩形都表达不了。
1.3 探索路径:需求递进
从"矩形太粗糙"出发,我们可以沿着需求逐步探索:
Qt Quick 粒子系统内置了四种 Shape 组件来覆盖这些需求:RectangleShape(矩形)、EllipseShape(椭圆)、LineShape(线段)、MaskShape(图像遮罩)。加上通过动画驱动 Emitter 位置实现的第五种——移动发射区域。
Shape 不决定粒子往哪飞(那是 Direction 的事),只决定粒子从哪里诞生。粒子诞生后,Shape 就不再影响它了。
二、开发环境与版本说明
本文所有代码基于以下环境验证(最后验证于 2026-06-10):
- Qt 版本:6.8.2
- 编译器:MinGW 64-bit
- 操作系统:Windows 11
- 构建工具:CMake 3.29
Shape 组件的 API 从 Qt 5 起稳定,Qt 6.5+ 均可直接运行本文代码。
本文中的代码都只展示了核心配置部分,为的是方便讲解,完整代码见项目中的对应 QML 文件。
三、原理:五种方案如何解决这个需求
3.1 五种方案对比
| 方案 | 发射区域 | 关键属性 | 适用需求 |
|---|---|---|---|
| RectangleShape | 矩形内部 | 无额外属性 | 通用背景粒子 |
| EllipseShape | 椭圆内部或边界 | fill: true/false | 环形粒子、光环效果 |
| LineShape | 线段上 | mirrored: true/false | 雨帘、激光、河流 |
| MaskShape | 图像轮廓 | source: "图片路径" | 自定义形状发射 |
| 移动区域 | 动态位置 | Emitter 绑定动画 | 跟随鼠标、运动轨迹 |
3.2 RectangleShape:最基础的区域控制
RectangleShape 是最简单的方案,没有额外属性。Emitter 的shape属性默认值就是一个填充的矩形——即 Emitter 的整个边界区域。
它比"固定点"进步的地方在于:你可以通过设置 Emitter 的width和height来控制发射区域的大小和形状。但本质上仍然是矩形,无法表达曲线边界。
3.3 EllipseShape:从矩形到曲线
当需要圆形或椭圆形的发射区域时,EllipseShape 解决了矩形无法表达曲线的问题。核心属性fill控制粒子的发射位置:
fill: true(默认):粒子从椭圆内部任意位置发射,像在椭圆区域内撒豆子fill: false:粒子仅从椭圆边界发射,像从圆环上喷射
fill: false是关键——它让椭圆从"面"变成了"环",适用于光环效果、环形爆炸、漩涡边缘。
3.4 LineShape:从面到线
当需要粒子从一条线上发射时(比如雨帘从天空顶部落下),LineShape 提供了线段发射区域。它继承自 ParticleExtruder,在 Emitter 的宽度或高度范围内生成一条线段。
核心属性mirrored控制线段方向:
mirrored: false(默认):线从左上(0,0)到右下(width, height)mirrored: true:沿 y 轴镜像,线从左下(0,height)到右上(width, 0)
线段的具体位置和长度由 Emitter 的width和height共同决定——当width > height时,线段沿宽度方向延伸。
3.5 MaskShape:从预定义到任意形状
当内置的矩形、椭圆、线段都无法满足需求时(比如需要从一个五角星或文字轮廓中发射粒子),MaskShape 提供了最终方案——用一张图片的轮廓作为发射区域。
核心属性source指向一张图片,图片中opacity 非零的像素都会发射粒子,透明像素不发射。
MaskShape 继承自 ParticleExtruder(与 LineShape 相同的基类),性能开销比其他 Shape 大——需要逐像素判断透明度。
3.6 移动发射区域:从静态到动态
前四种 Shape 的发射区域都是静态的——粒子从固定位置的区域中诞生。但如果需要发射区域本身在移动(比如跟随鼠标、沿路径运动),就需要第五种方案:通过动画驱动 Emitter 的位置。
粒子诞生后独立于 Emitter 运动,Emitter 的后续移动不影响已诞生的粒子。这与 TrailEmitter(尾迹发射器)的行为不同——TrailEmitter 会在运动路径上持续发射粒子。
四、代码实现与运行效果
4.1 Shape_Rectangle.qml:矩形发射区域
Emitter { anchors.centerIn: parent width: 200; height: 100 emitRate: 200 lifeSpan: 2000 size: 8 shape: RectangleShape {} velocity: PointDirection { y: -50; yVariation: 20 } }与"不设 Shape"的对比:不设 Shape 时,粒子从 Emitter 的整个矩形边界发射——如果 Emitter 填满父容器,粒子会从整个页面随机位置飞出,效果混乱。设置shape: RectangleShape {}并配合明确的width/height,发射区域被限制在 200×100 的小矩形内,效果可控。
代码中用一个半透明的黄色边框 Rectangle 可视化了发射区域的范围。
4.2 Shape_Ellipse.qml:椭圆发射区域(fill 对比)
这个页面用 Repeater 并排展示fill: true和fill: false的效果差异:
Repeater { model: [ { color: "#4ECDC4", fill: true }, { color: "#FF6B6B", fill: false } ] // ... ParticleSystem 内 Emitter { anchors.centerIn: parent width: 180; height: 120 emitRate: 150 lifeSpan: 3000 size: 10 shape: EllipseShape { fill: modelData.fill } velocity: AngleDirection { angle: 270; angleVariation: 30; magnitude: 60 } } }左栏(fill: true):青色星形粒子从椭圆内部任意位置发射,向上扩散(angle: 270)。发射区域覆盖整个椭圆面积。
右栏(fill: false):红色星形粒子仅从椭圆边界发射,形成清晰的环形粒子源。发射区域只有椭圆的边缘线条。
关键对比:fill: true是"面发射",fill: false是"线发射"。同一个 EllipseShape,仅通过一个属性就切换了发射模式——这是 EllipseShape 最实用的特性。
两个象限都用半透明的椭圆边框可视化了发射区域。
4.3 Shape_Line.qml:线性发射区域(mirrored 对比)
这个页面用 Repeater 并排展示mirrored: false和mirrored: true的效果差异:
Emitter { x: 50 y: parent.height / 2 - 75 width: parent.width - 100 height: 150 emitRate: 100 lifeSpan: 3000 size: 8 shape: LineShape { mirrored: modelData.mirrored } velocity: PointDirection { y: -80; yVariation: 30 } }Emitter 的width: parent.width - 100, height: 150定义了线段的范围。LineShape 在这个范围内生成一条对角线——mirrored: false时从左上到右下,mirrored: true时从左下到右上。
粒子从线段上的随机位置发射,向上运动(y: -80),形成"雨帘"效果。代码中用一条半透明的彩色线条可视化了线段的位置和方向。
与 EllipseShape fill:false 的区别:两者都是"线发射",但 EllipseShape 是曲线(环形),LineShape 是直线(对角线)。需要圆形边界用 EllipseShape,需要直线边界用 LineShape。
4.4 Shape_Mask.qml:图像遮罩发射区域
Emitter { anchors.centerIn: parent width: 200; height: 200 emitRate: 100 lifeSpan: 3000 size: 10 shape: MaskShape { source: "qrc:/images/mask.png" } velocity: AngleDirection { angle: 270; angleVariation: 45; magnitude: 50 } }MaskShape 使用一张图片的非透明区域作为发射区域——任何 opacity 非零的像素都会发射粒子,透明像素不发射。代码中用一个半透明的 Image 组件可视化了遮罩图片的内容。
与前三种 Shape 的区别:MaskShape 是唯一不受几何约束的方案——矩形、椭圆、线段都是数学定义的规则形状,MaskShape 是像素定义的任意形状。代价是性能开销更大(逐像素判断)。
注意事项:
- MaskShape 的
source必须是图片路径(PNG 格式最佳,支持 alpha 通道) - 图片会被缩放到 Emitter 的
width × height范围内 - MaskShape 继承自 ParticleExtruder(与 LineShape 相同的基类),不是 Shape
4.5 Shape_Moving.qml:移动发射区域
这个页面展示了第五种方案——通过动画驱动 Emitter 的位置,实现"移动的发射源":
Emitter { id: movingEmitter width: 80; height: 80 emitRate: 80 lifeSpan: 1500 size: 10 shape: EllipseShape { fill: true } velocity: AngleDirection { angle: 0; angleVariation: 360; magnitude: 40 } SequentialAnimation on x { running: root.isCurrentItem loops: Animation.Infinite NumberAnimation { from: 40 to: parent.width - movingEmitter.width - 40 duration: 3000 easing.type: Easing.InOutQuad } NumberAnimation { from: parent.width - movingEmitter.width - 40 to: 40 duration: 3000 easing.type: Easing.InOutQuad } } SequentialAnimation on y { running: root.isCurrentItem loops: Animation.Infinite NumberAnimation { from: 40 to: parent.height - movingEmitter.height - 40 duration: 4000 easing.type: Easing.InOutSine } NumberAnimation { from: parent.height - movingEmitter.height - 40 to: 40 duration: 4000 easing.type: Easing.InOutSine } } }两个独立的 SequentialAnimation分别驱动 Emitter 的x和y坐标。x方向周期 6 秒(3000×2),y方向周期 8 秒(4000×2),两个方向的周期不同步,形成类似"李萨如曲线"的运动轨迹。
为什么 x 和 y 用不同的缓动曲线:Easing.InOutQuad和Easing.InOutSine都是"缓入缓出"曲线,但加速度曲线不同。两个方向使用不同的缓动,让轨迹更自然、更有机,避免机械的对称运动。
running: root.isCurrentItem——动画和粒子系统同步启停。切换到其他页面时,动画暂停,粒子系统停止,避免后台消耗。
与前四种静态 Shape 的区别:静态 Shape 的发射区域是固定的,移动方案的发射区域在空间中运动。粒子诞生后独立于 Emitter——Emitter 的移动不会拖拽已发射的粒子。
五、运行效果
运行项目后,点击左侧导航栏对应条目进入各示例页面。
Shape_Rectangle.qml:200×100 的矩形区域内,黄色小点均匀向上发射,半透明边框标示发射区域。
Shape_Ellipse.qml:左栏青色粒子从椭圆内部发射,右栏红色粒子从椭圆边界发射。对比清晰展示fill属性对面发射和线发射的切换。
Shape_Line.qml:左栏粒子从左上到右下的斜线上发射,右栏粒子从左下到右上的斜线上发射。粒子向上运动形成"雨帘"效果。
Shape_Mask.qml:蓝色星形粒子从 mask.png 图像的非透明区域发射,半透明的遮罩图片叠加在背景上作为视觉参考。
Shape_Moving.qml:红色星形粒子从一个沿李萨如曲线运动的椭圆区域中持续发射,形成运动轨迹上的粒子拖尾。
六、适用边界与限制条件
6.1 组合使用规则
一个 Emitter 只能设置一个shape。如果需要多种形状同时发射,使用多个 Emitter,各自设置不同的 Shape。
Shape 只决定粒子的出生位置,Direction 决定运动方向,两者独立配置、互不干扰。
6.2 前置条件
MaskShape 的source必须指向有效的图片文件。建议使用 PNG 格式以确保 alpha 通道正确。如果图片路径无效,MaskShape 不会发射任何粒子。
LineShape 的线段方向受 Emitter 的width和height比例影响。当width > height时线段主要沿水平方向延伸,反之沿垂直方向。
6.3 安全阀
MaskShape 的图片分辨率不宜过高(建议 200×200 以内),高分辨率会增加逐像素判断的开销。RectangleShape 性能最好,MaskShape 性能相对最差。
6.4 边界值
移动发射区域中,粒子诞生后就独立于 Emitter 运动——Emitter 的后续移动不影响已诞生的粒子。如果需要粒子跟随 Emitter 移动,需要使用 TrailEmitter(尾迹发射器,第十二篇详解)。
6.5 性能建议
- 如果存在多个页面,尽量使用
running: root.isCurrentItem控制启停 - 矩形和椭圆 Shape 性能最好,优先使用
- 多个 Emitter 同时发射时,注意总粒子数对帧率的影响
七、总结
本文从"固定点发射不够用"这个问题出发,讲解了五种发射区域方案。选择策略:
简单形状用内置 Shape,复杂轮廓用 MaskShape,动态位置用动画驱动。至此,粒子发射区域的五种形态全部覆盖。下一篇讲解粒子的运动方向——AngleDirection、PointDirection、TargetDirection。
资源下载:qml_particlesystem —— 包含完整的、可运行的代码
系列目录:
- 上一篇:Qt Quick 粒子系统(五):粒子组与状态管理
- 本文:Qt Quick 粒子系统(六):五种发射区域的精确控制
- 下一篇:Qt Quick 粒子系统(七):扩散方向模型