8.5 粒子绘制器(Particle Painters)
到目前为止,我们只使用基于图像的粒子绘制器来显示粒子。Qt 还有其他的粒子绘制器:
- ItemParticle —— 基于粒子绘制器的代理
- CustomParticle —— 基于粒子绘制器的着色器
ItemParticle 可用于将 QML 元素作为粒子发出。为此,我们需要指定自己的代理粒子。
ItemParticle {
id: particle
system: particleSystem
delegate: itemDelegate
}
在这种情况下,我们的代理是一个随机图像(使用Math.random()),可视化为白色边框和随机大小。
Component {
id: itemDelegate
Item {
id: container
width: 32*Math.ceil(Math.random()*3); height: width
Image {
anchors.fill: parent
anchors.margins: 4
source: 'assets/'+images[Math.floor(Math.random()*9)]
}
}
}
我们每秒发射 4 张图像,每张 4 秒。粒子自动进出。
对于更多的动态情况,也可以自己创建一个元素,让粒子通过 take(item, priority) 来控制它。通过这个粒子模拟可以控制我们的粒子,像普通粒子那样处理物体。我们可以通过使用 give(item) 来获取对元素的控制。我们可以通过使用 freeze(item) 停止其生命进程来影响元素粒子,并使用 unfreeze(item) 恢复元素粒子的生命进程。
8.6 影响粒子(Affecting Particles)
发射器发射了粒子。发射粒子后,发射器不能再发生变化。影响器可以让我们在发射后对其造成影响。
每种类型的影响器以不同的方式影响粒子:
- Age(生命周期) —— 改变粒子在其生命周期中的位置
- Attractor(吸引器) —— 将粒子吸引到特定点
- Friction(摩擦) —— 减慢与粒子当前速度成比例的运动
- Gravity(重力) —— 设置一个角度的加速度
- Turbulence(湍流) —— 强制基于噪声图像的方式流动
- Wander(游移) —— 随机变化轨迹
- GroupGoal(目标集) —— 改变一组粒子的状态
- SpriteGoal(精灵目标) —— 改变精灵粒子的状态
Age(生命周期)
加速粒子的消失。lifeLeft 属性指定粒子剩余的显示时间。
Age {
anchors.horizontalCenter: parent.horizontalCenter
width: 240; height: 120
system: particleSystem
advancePosition: true
lifeLeft: 1200
once: true
Tracer {}
}
在这个例子中,我们缩短上层颗粒的寿命,当他们到达时间为 1200 毫秒时。由于我们将 advancePosition 设置为 true,所以当粒子剩下 1200 毫秒时,我们会看到颗粒再次出现在位置上。
Attractor(引力)
吸引器将粒子吸引到特定点。该点使用 pointX 和 pointY 指定,它与吸引器的大小相关。力量规定了吸引力的值。在我们的例子中,我们让粒子从左到右。吸引器放置在顶部,一半的颗粒通过吸引子行进。吸引器只影响粒子在它们的边界框中。这个区分允许我们同时看到正常流和受影响的流。
Attractor {
anchors.horizontalCenter: parent.horizontalCenter
width: 160; height: 120
system: particleSystem
pointX: 0
pointY: 0
strength: 1.0
Tracer {}
}
很容易看出,上半部分的颗粒受到吸引到顶部的影响。吸引点设置为吸引器的左上(0/0点),力为1.0。
Friction(摩擦)
摩擦影响器是将粒子减慢的一个因素,直到达到一定的阈值。
Friction {
anchors.horizontalCenter: parent.horizontalCenter
width: 240; height: 120
system: particleSystem
factor : 0.8
threshold: 25
Tracer {}
}
在上部摩擦区域,粒子减速了0.8倍,直到粒子达到每秒 25 像素的速度。阈值行为就像一个过滤器。行程超过阈值速度的颗粒会减慢给定的因子。
Gravity(重力)
重力影响器应用加速度在本例中,我们使用角度方向将粒子从底部流向顶部。右侧不受影响,左侧应用重力影响。重力倾斜到 90 度(底部方向),大小为 50。
Gravity {
width: 240; height: 240
system: particleSystem
magnitude: 50
angle: 90
Tracer {}
}
左侧的颗粒试图爬升,但向底部稳定施加的加速度将它们拖到重力的方向。
Turbulence(湍流)
湍流影响器将粒子的力矢量的混沌映射应用于该粒子。混沌映射由噪声图像定义,噪声图像可以用 noiseSource 属性定义。强度定义了矢量应用于粒子运动的强度。
Turbulence {
anchors.horizontalCenter: parent.horizontalCenter
width: 240; height: 120
system: particleSystem
strength: 100
Tracer {}
}
在该示例的上部区域中,颗粒受到湍流的影响。 他们的运动更不稳定。与原始路径不一致的偏差量由强度定义。
Wander(游移)
游移操纵轨迹。可以指定属性 affectedParameter,参数(速度,位置或加速度)被游移所覆盖。pace 属性指定每秒属性更改的最大值。 xVariance 和 yVariance 指定了对粒子轨迹的 x 和 y 分量的影响。
Wander {
anchors.horizontalCenter: parent.horizontalCenter
width: 240; height: 120
system: particleSystem
affectedParameter: Wander.Position
pace: 200
yVariance: 240
Tracer {}
}
在顶端的游移影响器中,粒子被随机的轨迹变化围绕着。在这种情况下,位置在 y 方向每秒更改 200 次。
8.7 粒子群(Particle Groups)
在本章开头,我们指出,粒子是分组的,默认情况下是空组('')。 使用 GroupGoal 影响器可以让粒子更改组。为了可视化,我们想创建一个小火焰秀,火箭射向空中,在空中爆炸成一个壮观的烟花。
该例分为两部分。第一部分称为“发射阶段”,关于设置场景并引入粒子群,第二部分称为“烟花爆破”重点关注群组变化。
现在就我们开始行动吧。
发射阶段
要做到这一点,我们创造一个典型的黑暗场景:
import QtQuick 2.5
import QtQuick.Particles 2.0
Rectangle {
id: root
width: 480; height: 240
color: "#1F1F1F"
property bool tracer: false
}
示踪器属性将用于打开和关闭示踪场景。接下来是声明我们的粒子系统:
ParticleSystem {
id: particleSystem
}
和我们的两个图像粒子(一个为火箭,一个为排出来的烟雾):
ImageParticle {
id: smokePainter
system: particleSystem
groups: ['smoke']
source: "assets/particle.png"
alpha: 0.3
entryEffect: ImageParticle.None
}
ImageParticle {
id: rocketPainter
system: particleSystem
groups: ['rocket']
source: "assets/rocket.png"
entryEffect: ImageParticle.None
}
您可以在上面的代码中看到,他们使用 groups 属性来声明粒子属于哪个组。只需声明名称就足够了,Qt Quick 将创建一个隐式组。
现在是时候向空中发射一些火箭了。为此,我们在场景底部创建一个发射器,并将速度设置为向上的方向。为了模拟一些重力效果,我们设置一个加速度向下:
Emitter {
id: rocketEmitter
anchors.bottom: parent.bottom
width: parent.width; height: 40
system: particleSystem
group: 'rocket'
emitRate: 2
maximumEmitted: 4
lifeSpan: 4800
lifeSpanVariation: 400
size: 32
velocity: AngleDirection { angle: 270; magnitude: 150; magnitudeVariation: 10 }
acceleration: AngleDirection { angle: 90; magnitude: 50 }
Tracer { color: 'red'; visible: root.tracer }
}
发射器在“火箭”组中,与我们的火箭粒子绘制器一样。通过组名,他们绑在一起。 发射器将颗粒发射到“火箭”组中,而火箭粒子绘制器会绘制它们以完成后续工作。
对于排出的气体,我们使用跟踪火箭的跟踪发射器。它声明一个自己的组称为“烟”,并遵循“火箭”组的粒子的轨迹:
TrailEmitter {
id: smokeEmitter
system: particleSystem
emitHeight: 1
emitWidth: 4
group: 'smoke'
follow: 'rocket'
emitRatePerParticle: 96
velocity: AngleDirection { angle: 90; magnitude: 100; angleVariation: 5 }
lifeSpan: 200
size: 16
sizeVariation: 4
endSize: 0
}
烟雾指示向下模拟从火箭中喷射出来的烟雾。emitHeight 与 emitWidth 指定了围绕跟随在烟雾粒子发射后的粒子。如果不指定这个值,跟随的粒子将会被拿掉,但是对于这个例子,我们想要提升显示效果,粒子流从一个接近于火箭尾部的中间点发射出。
如果你现在开始这个例子,你会看到火箭飞起来,有些甚至飞出了场景。因为这不是真的想要的,我们需要在他们离开屏幕之前慢下来。这里可以使用一个摩擦力来将颗粒减慢到最小阈值:
Friction {
groups: ['rocket']
anchors.top: parent.top
width: parent.width; height: 80
system: particleSystem
threshold: 5
factor: 0.9
}
在摩擦影响器中,我们还需要声明它会影响哪些粒子组。摩擦将使所有从屏幕顶部向下 80 像素的火箭减少 0.9 因子(尝试一下 100,我们将看到它们几乎立即停止),直到它们达到每秒 5 像素的速度。随着粒子的加速度仍然下降,火箭将在其使用寿命结束后开始向下坠落。
在空中爬升的情况是艰难和非常不稳定的,我们想在火箭上升的时候模拟一些湍流效果:
Turbulence {
groups: ['rocket']
anchors.bottom: parent.bottom
width: parent.width; height: 160
system: particleSystem
strength: 25
Tracer { color: 'green'; visible: root.tracer }
}
此外,湍流需要声明哪些组将受到影响。其自身是从底部 160 像素向上的湍流,直到其到达摩擦边界。他们也可以重叠。
当我们开始这个例子,你会看到火箭正在爬上,然后会被摩擦减慢,并且依然应用向下的加速度而回落到地面上。接下来的事情就是开始烟火表演了。
** 注意: **
图像显示了启用示踪器显示不同区域的场景。火箭粒子在红色区域发射,然后受到蓝色区域的湍流的影响。最后,由于稳定的下行加速度,它们被绿色区域的摩擦影响器放慢,并开始下降。
来点烟火秀
为了能够将火箭变成美丽的烟火,我们需要添加一个 ParticleGroup 来封装这些变化:
ParticleGroup {
name: 'explosion'
system: particleSystem
}
我们使用 GroupGoal 影响器更改为粒子组。集团目标影响器会被放置在屏幕的垂直中心附近,并将影响“rocket”组。使用 groupGoal 属性,我们将更改的目标组设置为“explosion”,这是我们之前定义的粒子组:
GroupGoal {
id: rocketChanger
anchors.top: parent.top
width: parent.width; height: 80
system: particleSystem
groups: ['rocket']
goalState: 'explosion'
jump: true
Tracer { color: 'blue'; visible: root.tracer }
}
jump 属性表示组的变化应立即执行而不是在一定的持续时间之后。
由于火箭粒子变为我们的爆炸粒子,当火箭粒子进入 GroupGoal 控制器区域时,我们需要在粒子组中添加一个烟花:
// inside particle group
TrailEmitter {
id: explosionEmitter
anchors.fill: parent
group: 'sparkle'
follow: 'rocket'
lifeSpan: 750
emitRatePerParticle: 200
size: 32
velocity: AngleDirection { angle: -90; angleVariation: 180; magnitude: 50 }
}
爆炸将颗粒发射到 “sparkle” 组中。我们将很快为这个组定义一个粒子绘制器。轨迹发射器跟随火箭粒子每秒发射 200 个火箭爆炸粒子。粒子的方向向上,并改变 180 度。
当颗粒被发射到 “sparkle” 组中时,我们还需要为颗粒定义一个粒子绘制器:
ImageParticle {
id: sparklePainter
system: particleSystem
groups: ['sparkle']
color: 'red'
colorVariation: 0.6
source: "assets/star.png"
alpha: 0.3
}
我们的烟花的闪闪发光将是一个几乎透明的小红色星星,有一些闪耀的效果。
为了使烟花更加壮观,我们还向我们的粒子群添加了第二个试射发射体,这将会将窄锥体中的颗粒向下发射:
// inside particle group
TrailEmitter {
id: explosion2Emitter
anchors.fill: parent
group: 'sparkle'
follow: 'rocket'
lifeSpan: 250
emitRatePerParticle: 100
size: 32
velocity: AngleDirection { angle: 90; angleVariation: 15; magnitude: 400 }
}
否则设置与其他爆炸轨迹发射器相似。这就是整个示例了。
下面就是最后的结果。
这是火箭烟花的完整源代码。
import QtQuick 2.5
import QtQuick.Particles 2.0
Rectangle {
id: root
width: 480; height: 240
color: "#1F1F1F"
property bool tracer: false
ParticleSystem {
id: particleSystem
}
ImageParticle {
id: smokePainter
system: particleSystem
groups: ['smoke']
source: "assets/particle.png"
alpha: 0.3
}
ImageParticle {
id: rocketPainter
system: particleSystem
groups: ['rocket']
source: "assets/rocket.png"
entryEffect: ImageParticle.Fade
}
Emitter {
id: rocketEmitter
anchors.bottom: parent.bottom
width: parent.width; height: 40
system: particleSystem
group: 'rocket'
emitRate: 2
maximumEmitted: 8
lifeSpan: 4800
lifeSpanVariation: 400
size: 128
velocity: AngleDirection { angle: 270; magnitude: 150; magnitudeVariation: 10 }
acceleration: AngleDirection { angle: 90; magnitude: 50 }
Tracer { color: 'red'; visible: root.tracer }
}
TrailEmitter {
id: smokeEmitter
system: particleSystem
group: 'smoke'
follow: 'rocket'
size: 16
sizeVariation: 8
emitRatePerParticle: 16
velocity: AngleDirection { angle: 90; magnitude: 100; angleVariation: 15 }
lifeSpan: 200
Tracer { color: 'blue'; visible: root.tracer }
}
Friction {
groups: ['rocket']
anchors.top: parent.top
width: parent.width; height: 80
system: particleSystem
threshold: 5
factor: 0.9
}
Turbulence {
groups: ['rocket']
anchors.bottom: parent.bottom
width: parent.width; height: 160
system: particleSystem
strength:25
Tracer { color: 'green'; visible: root.tracer }
}
ImageParticle {
id: sparklePainter
system: particleSystem
groups: ['sparkle']
color: 'red'
colorVariation: 0.6
source: "assets/star.png"
alpha: 0.3
}
GroupGoal {
id: rocketChanger
anchors.top: parent.top
width: parent.width; height: 80
system: particleSystem
groups: ['rocket']
goalState: 'explosion'
jump: true
Tracer { color: 'blue'; visible: root.tracer }
}
ParticleGroup {
name: 'explosion'
system: particleSystem
TrailEmitter {
id: explosionEmitter
anchors.fill: parent
group: 'sparkle'
follow: 'rocket'
lifeSpan: 750
emitRatePerParticle: 200
size: 32
velocity: AngleDirection { angle: -90; angleVariation: 180; magnitude: 50 }
}
TrailEmitter {
id: explosion2Emitter
anchors.fill: parent
group: 'sparkle'
follow: 'rocket'
lifeSpan: 250
emitRatePerParticle: 100
size: 32
velocity: AngleDirection { angle: 90; angleVariation: 15; magnitude: 400 }
}
}
}
8.8 总结一下
粒子是一种非常强大而有趣的表达烟雾、烟火、随机的视觉元素等图形和现象的方式。 Qt 5 中扩展的 API 非常强大,我们刚刚演示的只是比较表面和浅显的功能。还有几个元素,比如:我们还没有使用像精灵、大小表或颜色表。此外,粒子看起来非常有趣时,明智地在用户界面中创建一些粒子效果,会使界面具有很大的吸引力。但是,在用户界面中使用过多粒子效果,一定会导致用户觉得这是一个游戏的印象,所以不建议这么做。事实也确实如此,在游戏中粒子才能发挥它们真正实力。