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 非常強大,我們剛剛演示的只是比較表面和淺顯的功能。還有幾個元素,比如:我們還沒有使用像精靈、大小表或顏色表。此外,粒子看起來非常有趣時,明智地在用戶界面中創建一些粒子效果,會使界面具有很大的吸引力。但是,在用戶界面中使用過多粒子效果,一定會導致用戶覺得這是一個游戲的印象,所以不建議這么做。事實也確實如此,在游戲中粒子才能發揮它們真正實力。