首页 > 代码库 > Qt5官方demo解析集14——Qt Quick Particles Examples - System

Qt5官方demo解析集14——Qt Quick Particles Examples - System

本系列所有文章可以在这里查看http://blog.csdn.net/cloud_castle/article/category/2123873

接上文Qt5官方demo解析集13——Qt Quick Particles Examples - Image Particles


一转眼就到了我们粒子系列的最后一个demo了,既然是System,第一个小例子就给我们介绍了“模拟”一个粒子系统的方式,接着又向我们介绍了running属性的应用,然后是粒子群组等十分实用的技术。

来看看我们熟悉的选择框:



不多说,进主题~

(1)Dynamic Comparison

左边是由我们的粒子系统产生的1000个粒子,右边是我们使用Image模拟的粒子,在运行时动态创建,尺寸为32X32,数量也是1000个。这个小例子名为动态比较,因为这里的ImageParticle和Image都是动态创建的,但是性能差异却很大。

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属性。



点击左键我们可以 停止/重新开始 渲染,点击右键我们可以 暂停/继续 渲染。

代码也很简短,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 -> ""


(3)Timed group changes

这个例子主要展示了ParticleGroup的用法,还记不记得我们曾经在Affectors中的GroupGoal例子中接触过这个ParticleGroup。

这个例子展示了升起的烟花:



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。



当我们点击屏幕时,会有几束粒子向四周发散。当然我们可以使用多个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

待更新