本系列所有文章可以在这里查看[http://blog.csdn.net/cloud_castle/article/category/2123873](http://blog.csdn.net/cloud_castle/article/category/2123873) 接上文[Qt5官方demo解析集13——Qt Quick Particles Examples - Image Particles](http://blog.csdn.net/cloud_castle/article/details/35786691) 一转眼就到了我们粒子系列的最后一个demo了,既然是System,第一个小例子就给我们介绍了“模拟”一个粒子系统的方式,接着又向我们介绍了running属性的应用,然后是粒子群组等十分实用的技术。 来看看我们熟悉的选择框: ![](https://box.kancloud.cn/2016-01-18_569cbd067a81e.jpg) 不多说,进主题~ (1)Dynamic Comparison 左边是由我们的粒子系统产生的1000个粒子,右边是我们使用Image模拟的粒子,在运行时动态创建,尺寸为32X32,数量也是1000个。这个小例子名为动态比较,因为这里的ImageParticle和Image都是动态创建的,但是性能差异却很大。 ![](https://box.kancloud.cn/2016-01-18_569cbd068d464.jpg) dynamiccomparison.qml: ~~~ import QtQuick 2.0 import QtQuick.Particles 2.0 Rectangle { id: root color: "black" width: 640 height: 480 ParticleSystem { // 首先是我们的粒子系统 id: sys } ImageParticle { // 图像粒子 system: sys source: "qrc:///particleresources/glowdot.png" color: "white" colorVariation: 1.0 // 多彩化 alpha: 0.1 entryEffect: ImageParticle.None // 默认值为ImageParticle.Fade,即粒子在进入与消失时透明度为0 } // 这里取消了这种默认设置 // 还可以设置为ImageParticle.Scale,使粒子在进入与消失时尺寸为0 Emitter { id: emitter system: sys width: parent.width/2 velocity: PointDirection {y: 72; yVariation: 24} lifeSpan: 10000 emitRate: 1000 enabled: false // 先关闭这个Emitter使其在我们需要的时候进行发射 size: 32 } //! [fake] Item { // 下面使用Item和Component来模拟我们的Emitter与ImageParticle id: fakeEmitter function burst(number) { // 使用JavaScript函数拟Emitter while (number > 0) { var item = fakeParticle.createObject(root); // 动态创建fakeParticle的实例化对象,并将Rectangle作为其父对象 item.lifeSpan = Math.random() * 5000 + 5000; // 取值范围为 (0.5,1)*10000 的生命周期 item.x = Math.random() * (root.width/2) + (root.width/2); // (root.width/2 - root.width)矩形右半部分 item.y = 0; number--; // 循环创建number个实例 } } Component { // 使用组件模拟粒子 id: fakeParticle Image { id: container property int lifeSpan: 10000 // 为了实现透明度以及下落动画(如果在createObject时带上初始化属性,这里的值可以随便设置) width: 32 height: 32 source: "qrc:///particleresources/glowdot.png" // 我们使用Image也可以使用这张图 y: 0 PropertyAnimation on y {from: -16; to: root.height-16; duration: container.lifeSpan; running: true} // 实现匀速下落,如果加入缓和曲线可实现更复杂的下落效果 SequentialAnimation on opacity { running: true NumberAnimation { from:0; to: 1; duration: 500} // 前0.5秒由透明变得不透明 PauseAnimation { duration: container.lifeSpan - 1000} // 暂停动画 NumberAnimation { from:1; to: 0; duration: 500} // 最后0.5秒由不透明变成透明 ScriptAction { script: container.destroy(); } // 我们可以使用ScriptAction为动画加入一段脚本,这里单纯地释放了这个组件 } } } } //! [fake] //Hooked to a timer, but click for extra bursts that really stress performance Timer { // 按作者的说法,这里使用定时器而不是响应点击是因为那样实在是太伤性能 interval: 10000 // 因此我在这里尝试了一下,如果点击后使用我们自定义的“fakeEmitter”发射1000个粒子,卡顿明显 triggeredOnStart: true // 但是如果使用粒子系统的Emitter来发射,十分流畅 repeat: true // 可以看出Qt在粒子系统性能优化上所做的工作 running: true onTriggered: { emitter.burst(1000); fakeEmitter.burst(1000); } } Text { anchors.left: parent.left anchors.bottom: parent.bottom text: "1000 particles" color: "white" MouseArea { anchors.fill: parent onClicked: emitter.burst(1000); } } Text { anchors.right: parent.right anchors.bottom: parent.bottom text: "1000 items" color: "white" MouseArea { anchors.fill: parent onClicked: fakeEmitter.burst(1000); } } } ~~~ (2)StartStop 这个例子向我们展示了ParticleSystem的running与pause属性。 ![](https://box.kancloud.cn/2016-01-18_569cbd06aaed7.jpg) 点击左键我们可以 停止/重新开始 渲染,点击右键我们可以 暂停/继续 渲染。 代码也很简短,startstop.qml: ~~~ import QtQuick 2.0 import QtQuick.Particles 2.0 Rectangle { width: 360 height: 540 color: "black" Text { text: "Left click to start/stop\nRight click to pause/unpause" color: "white" font.pixelSize: 24 } MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton // 由于默认只响应左键,我们需要设置该属性接受2个按键 onClicked: { if (mouse.button == Qt.LeftButton) particles.running = !particles.running // running为停止和重新开始 else particles.paused = !particles.paused; // paused为暂停和继续 } } ParticleSystem { id: particles running: false // 初始化将ParticleSystem停止 } ImageParticle { anchors.fill: parent system: particles source: "qrc:///particleresources/star.png" sizeTable: "qrc:/images/sparkleSize.png" // 这个我们接触过了,使用一维图像的透明度来决定粒子生命周期内的尺寸变化 alpha: 0 colorVariation: 0.6 } Emitter { anchors.fill: parent system: particles emitRate: 2000 lifeSpan: 2000 size: 30 sizeVariation: 10 } } ~~~ sparkleSize.png -> "![](https://box.kancloud.cn/2016-01-18_569cbd06da239.jpg)" (3)Timed group changes 这个例子主要展示了ParticleGroup的用法,还记不记得我们曾经在[Affectors](http://blog.csdn.net/cloud_castle/article/details/33723715)中的GroupGoal例子中接触过这个ParticleGroup。 这个例子展示了升起的烟花: ![](https://box.kancloud.cn/2016-01-18_569cbd06eafec.jpg) timedgroupchanges.qml: ~~~ import QtQuick 2.0 import QtQuick.Particles 2.0 Rectangle { width: 360 height: 600 color: "black" ParticleSystem { anchors.fill: parent id: syssy //! [0] ParticleGroup { // 将下面定义的图像粒子"fire"添加进一个粒子组 name: "fire" duration: 2000 durationVariation: 2000 // 经过(0,4)秒后,进入"splode"状态 to: {"splode":1} } //! [0] //! [1] ParticleGroup { name: "splode" // "splode"同样在下方定义 duration: 400 // 0.4秒后进入"dead"状态 to: {"dead":1} TrailEmitter { // 该粒子带有一个TrailEmitter,用来发射"works"粒子以跟随"splode"粒子,形成烟花的尾焰效果 group: "works" emitRatePerParticle: 100 // 跟随比例 lifeSpan: 1000 maximumEmitted: 1200 size: 8 velocity: AngleDirection {angle: 270; angleVariation: 45; magnitude: 20; magnitudeVariation: 20;} acceleration: PointDirection {y:100; yVariation: 20} // 向四周扩散并向下飘落 } } //! [1] //! [2] ParticleGroup { // 在"dead"状态调用worksEmitter向四周发射爆裂的烟花 name: "dead" duration: 1000 Affector { once: true onAffected: worksEmitter.burst(400,x,y) // 这里的x,y是当前这个ParticleGroup的坐标值 } } //! [2] Timer { // 间隔6秒的定时器,用来调用第一个Emitter interval: 6000 running: true triggeredOnStart: true repeat: true onTriggered:startingEmitter.pulse(100); // burst为一次使能,而pulse为一段时间使能 } Emitter { id: startingEmitter // 上升火焰发射器 group: "fire" width: parent.width y: parent.height enabled: false emitRate: 80 lifeSpan: 6000 velocity: PointDirection {y:-100;} size: 32 } Emitter { // 爆裂火焰发射器 id: worksEmitter group: "works" enabled: false emitRate: 100 lifeSpan: 1600 maximumEmitted: 6400 size: 8 velocity: CumulativeDirection { PointDirection {y:-100} AngleDirection {angleVariation: 360; magnitudeVariation: 80;} } acceleration: PointDirection {y:100; yVariation: 20} } ImageParticle { groups: ["works", "fire", "splode"] source: "qrc:///particleresources/glowdot.png" entryEffect: ImageParticle.Scale // 为粒子的进入与消失添加尺寸的变化,进入与消失时尺寸为0 } } } ~~~ (4)Dynamic Emitters 当我们的程序运行在条件比较苛刻的平台时,可以将Emitter定义在一个组件中,并在这个组件中加入一个定时器,使得它在工作一段时间后释放掉。另一方面,无论何时当我们觉得QML提供的类型或者属性都不能满足特定需要的时候,我们都可以尝试使用JavaScript进行扩展。这个例子就向我们展示了一个使用JavaScript扩展的Emitter。 ![](https://box.kancloud.cn/2016-01-18_569cbd070a86a.jpg) 当我们点击屏幕时,会有几束粒子向四周发散。当然我们可以使用多个Emitter并定义不同的速度方向来达到此效果,不过这样未免繁琐。 dynamicemitters.qml: ~~~ import QtQuick 2.0 import QtQuick.Particles 2.0 Rectangle { id: root color: "black" width: 640 height: 480 ParticleSystem { id: sys } ImageParticle { system: sys source: "qrc:///particleresources/glowdot.png" color: "white" colorVariation: 1.0 alpha: 0.1 } Component { // 我们将Emitter定义在一个组件中,以对其进行扩展 id: emitterComp Emitter { // 这个Emitter为根项目 id: container Emitter { // 这个Emitter有些类似TrailEmitter,但它不是跟随每个粒子,而是每次父对象触发时被触发一次 id: emitMore // 这样为父对象的每个光束上添加一个散射效果 system: sys // 要注意它的x ,y等基本属性是由父对象传递的 emitRate: 128 lifeSpan: 600 size: 16 endSize: 8 velocity: AngleDirection {angleVariation:360; magnitude: 60} } property int life: 2600 // 定义了Emitter的生命周期。注意lifeSpan是粒子的生命周期,别弄混了 property real targetX: 0 // 目标坐标 property real targetY: 0 function go() { // 定义一个函数用来调用该Emitter xAnim.start(); yAnim.start(); container.enabled = true } system: sys // 以下是Emitter的常规属性 emitRate: 32 lifeSpan: 600 size: 24 endSize: 8 NumberAnimation on x { // 为x添加动画,从当前坐标x移动到targetX id: xAnim; to: targetX duration: life running: false } NumberAnimation on y { id: yAnim; to: targetY duration: life running: false } Timer { // 最后添加一个定时器,在Emitter结束生命周期后释放 interval: life running: true onTriggered: container.destroy(); } } } function customEmit(x,y) { // 这个JavaScript函数用来对组件的属性赋值,以及目标坐标的计算 //! [0] for (var i=0; i<8; i++) { // 一共创建了8个Emitter的实例化对象 var obj = emitterComp.createObject(root); obj.x = x obj.y = y obj.targetX = Math.random() * 240 - 120 + obj.x // 目标坐标在以当前坐标为中心的边长为240的矩形内 obj.targetY = Math.random() * 240 - 120 + obj.y obj.life = Math.round(Math.random() * 2400) + 200 // 给每个Emitter一个相对随机的生命周期 obj.emitRate = Math.round(Math.random() * 32) + 32 // Math.round()四舍五入 obj.go(); // 调用其内部的go()函数 } //! [0] } Timer { // 每10秒在屏幕的任意地方触发一次 interval: 10000 triggeredOnStart: true running: true repeat: true onTriggered: customEmit(Math.random() * 320, Math.random() * 480) } MouseArea { // 点击触发,将mouse.x,mouse.y赋值给Emitter的x与y anchors.fill: parent onClicked: customEmit(mouse.x, mouse.y); } Text { anchors.horizontalCenter: parent.horizontalCenter text: "Click Somewhere" color: "white" font.pixelSize: 24 } } ~~~ (5)Multiple Painters 通过这个例子,Qt向我们展示了同一个Emitter发射多个Particles的情况。 ![](https://box.kancloud.cn/2016-01-18_569cbd071e662.jpg) 该例子最先使用一张黑框里面带光点的粒子作为图像粒子展示,接着使用纯黑框加glowdot模拟了之前的效果。 multiplepainters.qml: ~~~ import QtQuick 2.0 import QtQuick.Particles 2.0 Rectangle { id: root width: 360 height: 600 color: "darkblue" property bool cloneMode: false // 定义了一个克隆模式的属性 ParticleSystem { id: sys } MouseArea { anchors.fill: parent onClicked: cloneMode = !cloneMode; // 点击切换 } Text { anchors.horizontalCenter: parent.horizontalCenter text: "Click to Toggle" color: "white" font.pixelSize: 24 } Emitter { system: sys y:root.height + 20 // 由于默认的粒子产生带有一个透明度的变化过程,+ 20就看不到这个效果了 width: root.width // 当然我们设置粒子的entryEffect也是可以实现上面的要求,但这里就需要设置三次 emitRate: 200 lifeSpan: 4000 startTime: 4000 velocity: PointDirection { y: -120; } } ImageParticle { // 首先仅显示这个粒子,它是一个黑框中带光点的图片 system: sys visible: !cloneMode // 初始化设置为可见 source: "qrc:/images/particle2.png" } ImageParticle { // 一个纯黑框 system: sys visible: cloneMode // 初始化不可见 z: 0 source: "qrc:/images/particle3.png" } ImageParticle { // 光点,由于这三个ImageParticle都没有指定group,那么Emitter会在每个释放粒子的位置上释放3个粒子 system: sys clip: true // 特定作用范围的粒子需要设置该属性,实现有无光点的对比 visible: cloneMode // 初始化不可见 y: 120 // 作用范围 height: 240 width: root.width z: 1 // 为了不被上面的粒子覆盖 source: "qrc:///particleresources/glowdot.png" } } ~~~