本系列所有文章可以在这里查看[http://blog.csdn.net/cloud_castle/article/category/2123873](http://blog.csdn.net/cloud_castle/article/category/2123873) 接上文[Qt5官方demo解析集10——Qt Quick Particles Examples - Emitters](http://blog.csdn.net/cloud_castle/article/details/33306133) Affectors是Qt官方粒子系统demo中的第二个例程,它是在Emitters上的进一步扩展。我们将看到,通过使用Affectors,我们能够创造更加灵活的粒子显示以及交互行为。 首先还是看下介绍:This is a collection of small QML examples relating to using Affectors in the particle system. Each example is a small QML file emphasizing a particular type or feature. 很简短,告诉我们这个demo依然是由多个使用Affectors的小例子构成。运行后是同样的选择框: ![](https://box.kancloud.cn/2016-01-18_569cbd0273b26.jpg) 一共有10个例子,我们还是从第一个开始: (1)Age 来看看是个什么效果:进入图中矩形框的雪花都变小并逐渐消失了。 ![](https://box.kancloud.cn/2016-01-18_569cbd0292d42.jpg) 来看看这个小例子是怎么写的吧~ age.qml: ~~~ import QtQuick 2.0 import QtQuick.Particles 2.0 Rectangle { id: root width: 360 height: 600 color: "white" ParticleSystem { id: particles } ImageParticle { // 这里向我们展示另外一种图像粒子的设置 system: particles sprites: Sprite { // sprites属性用来定义一组帧图片来作为粒子,这样粒子可以像GIF一样拥有自己的动画 name: "snow" source: "../../images/snowflake.png" // 这是一张具有51帧的雪花图形 frameCount: 51 frameDuration: 40 // 帧动画的基本设置 frameDurationVariation: 8 } } Emitter { system: particles emitRate: 20 lifeSpan: 8000 velocity: PointDirection { y:80; yVariation: 40; } // 加速度下落 acceleration: PointDirection { y: 4 } size: 36 endSize: 12 sizeVariation: 8 width: parent.width height: 100 } MouseArea { id: ma anchors.fill: parent hoverEnabled: true } Rectangle { // 这里使用Rectangle作为Age的父类,当然Age可以定义自己的坐标以及区域,但是加入Rectangle可视化效果更好 color: "#803333AA" // 半透明的湛蓝色 border.color: "black" x: ma.mouseX - 36 // 这里用到属性绑定使得该矩形可以跟随鼠标的移动,并以鼠标为中心点 y: ma.mouseY - 36 width: 72 height: 72 //! [0] Age { // Age继承自Affector,其实在上一篇Emitters中我们就接触了一个Affector:Turbulence,它可以提供一个气流的效果,而这里的Age则允许我们改变粒子的生命周期。 anchors.fill: parent system: particles once: true // 每个粒子只影响一次 lifeLeft: 1200 // 粒子剩下的时间 advancePosition: false // 退化是否影响位置、速度、和加速度 } //! [0] } } ~~~ 雪花图太长,截一部分好了: ![](https://box.kancloud.cn/2016-01-18_569cbd02b2a6f.jpg) (2)Attractor 这个小例子使用Affector中的Attractor(吸引者)向我们展示了如何使用粒子系统模拟一个黑洞。 ![](https://box.kancloud.cn/2016-01-18_569cbd02c713c.jpg) 可以看到图中心有一个“黑洞”,靠近黑洞的粒子会被改变运行轨迹,太近的粒子会被吸进去。如果需要我们自己来写这种速度改变的代码可能会相当繁琐,好在QtQuick给我们提供了Attractor这个Affector,来看看它怎么使用的~attractor.qml ~~~ import QtQuick 2.0 import QtQuick.Particles 2.0 Rectangle { id: root width: 360 height: 540 color: "black" Image { source: "qrc:/images/finalfrontier.png" anchors.centerIn:parent } ParticleSystem { id: particles anchors.fill: parent Emitter { // 星星发射器 group: "stars" emitRate: 40 lifeSpan: 4000 enabled: true size: 30 sizeVariation: 10 velocity: PointDirection { x: 220; xVariation: 40 } height: parent.height // height定义了发射发射区域的高度,否则粒子从(0,0)发出 } Emitter { // 陨石发射器 group: "roids" emitRate: 10 lifeSpan: 4000 enabled: true size: 30 sizeVariation: 10 velocity: PointDirection { x: 220; xVariation: 40 } height: parent.height } ImageParticle { // 星星 id: stars groups: ["stars"] source: "qrc:///particleresources/star.png" color: "white" colorVariation: 0.5 alpha: 0 } ImageParticle { // 陨石 id: roids groups: ["roids"] sprites: Sprite { // 这里再次使用了帧动画,由于没有定义frameDurationVariation,所有陨石的旋转速度都是相同的 id: spinState name: "spinning" source: "qrc:/images/meteor.png" frameCount: 35 frameDuration: 60 } } ImageParticle { // 飞船子弹 id: shot groups: ["shot"] source: "qrc:///particleresources/star.png" color: "#0FF06600" colorVariation: 0.3 } ImageParticle { // 尾气 id: engine groups: ["engine"] source: "qrc:///particleresources/fuzzydot.png" color: "orange" SequentialAnimation on color { // 属性动画 loops: Animation.Infinite ColorAnimation { from: "red" to: "cyan" duration: 1000 } ColorAnimation { from: "cyan" to: "red" duration: 1000 } } colorVariation: 0.2 } //! [0] Attractor { // Affector家族中的一员,可以形成吸引其他粒子的效果 id: gs; pointX: root.width/2; pointY: root.height/2; strength: 4000000; // pointX,pointY是其作为目标点,同其他Affector一样,设置其x,y,height,weidth改变的是其影响区域 affectedParameter: Attractor.Acceleration // 设置为影响加速度 proportionalToDistance: Attractor.InverseQuadratic // 影响效果与距离的比例关系 } //! [0] Age { // 在Attractor周围再安装一个Age,因为这里没有设置lifeLeft,粒子进入该区域变消失了 x: gs.pointX - 8; // Age的影响区域 y: gs.pointY - 8; width: 16 height: 16 } Rectangle { // 用矩形画圆的方法 color: "black" width: 8 height: 8 radius: 4 x: gs.pointX - 4 y: gs.pointY - 4 } Image { // 飞行器 source:"qrc:/images/rocket2.png" id: ship width: 45 height: 22 //Automatic movement SequentialAnimation on x { // 属性动画,这里使用了弹线轨迹 loops: -1 NumberAnimation{to: root.width-45; easing.type: Easing.InOutQuad; duration: 2000} NumberAnimation{to: 0; easing.type: Easing.OutInQuad; duration: 6000} } SequentialAnimation on y { loops: -1 NumberAnimation{to: root.height-22; easing.type: Easing.OutInQuad; duration: 6000} NumberAnimation{to: 0; easing.type: Easing.InOutQuad; duration: 2000} } } Emitter { // 尾气粒子 group: "engine" emitRate: 200 lifeSpan: 1000 size: 10 endSize: 4 sizeVariation: 4 velocity: PointDirection { x: -128; xVariation: 32 } height: ship.height y: ship.y x: ship.x width: 20 } Emitter { // 子弹粒子 group: "shot" emitRate: 32 lifeSpan: 1000 enabled: true size: 40 velocity: PointDirection { x: 256; } x: ship.x + ship.width y: ship.y + ship.height/2 } } } ~~~ (3)Custom Affector 在这个例子中我们将了解到如何实现一个自定义的Affector,以及通过这个Affector实现落叶飘落的效果。当Affector的子类都不能满足我们的需求的时候,这种方式就显得尤为重要了。 ![](https://box.kancloud.cn/2016-01-18_569cbd02ebbdd.jpg) 直接上代码,由于我会调试这些代码因此其图片的路径被我改成了资源路径,希望没有影响到大家。customaffector.qml: ~~~ import QtQuick 2.0 import QtQuick.Particles 2.0 Item { // 如果这个文件作为一个组件,Image作为根项目将使使用这个组件的人可以对其任意修改 width: 360 height: 600 Image { // 因此不推荐将Image作为根目录,而是以Item作为替代,而嵌套Image source: "qrc:/images/backgroundLeaves.jpg" anchors.fill: parent } ParticleSystem { anchors.fill: parent Emitter { width: parent.width // 粒子出现的点为(0,0)到(360,0) emitRate: 4 lifeSpan: 14000 size: 80 velocity: PointDirection { y: 60 } // 初始速度 } Wander { // 一个系统自带的Affector:Wander(漫步者),它可以用来提供随机的粒子轨迹,这样形成了左右晃动的叶子 anchors.fill: parent anchors.bottomMargin: 100 // 与设置Affector的height类似,确定Wander的影响区域 xVariance: 60 // x方向上的变化率 pace: 60 // 最大步长 } //! [0] Affector { // 基本的Affector类不会改变粒子任何属性,但我们可以在合适的时候发出信号来做出相应的处理 property real coefficient: 0.1 // 自定义属性“同步系数”和“速度” property real velocity: 1.5 width: parent.width height: parent.height - 100 // 底部100像素不再产生影响 onAffectParticles: { // 只要有粒子被该Affector影响,这个handler就被触发。通过它我们可以定义自己的Affector行为。类似onEmitterParticles,由于使用了javaScript数组以及计算,我们同样不推荐在包含大量粒子的系统中使用它。 //Linear movement // 这一段是在源码中被注释的,它提供了线性摇动的计算 // if (particle.r == 0) { // particle.r = Math.random() > 0.5 ? -1 : 1; // } else if (particle.r == 1) { // particle.rotation += velocity * dt; // 不知道这个dt是什么,只知道是一个比较小的小数... // if (particle.rotation >= maxAngle) // particle.r = -1; // } else if (particle.r == -1) { // particle.rotation -= velocity * dt; // if (particle.rotation <= -1 * maxAngle) // particle.r = 1; // } //Wobbly movement for (var i=0; i<particles.length; i++) { // 这是一个摇摆算法,相对上面的代码而言更加精妙 var particle = particles[i]; if (particle.r == 0.0) { // 在QML中我们可以将参数定义与赋值放在一起 particle.r = Math.random() + 0.01; // 将 0.01 定义为particle.r的最小值 } particle.rotation += velocity * particle.r * dt; // 随机的particle.r保证每片叶子的旋转角度都是随机的 particle.r -= particle.rotation * coefficient; // 然后这里通过“同步系数”适当改变particle.r,系数越大,叶片晃动越剧烈。根据QML属性绑定的原则,当particle.r被改变,particle.rotation随之改变。正向的旋转角度使particle.r变小,导致particle.rotation变小,叶片方向旋转,反之亦然,得到晃动效果 if (particle.r == 0.0) // 如果为0给其一个改变量 particle.r -= particle.rotation * 0.000001; particle.update = 1; } } } //! [0] //! [1] Affector { // 定义“地面”的摩擦减速效果 x: -60 width: parent.width + 120 height: 100 anchors.bottom: parent.bottom onAffectParticles: { for (var i=0; i<particles.length; i++) { var particle = particles[i]; var pseudoRand = (Math.floor(particle.t*1327) % 10) + 1; // Math.floor取到一个整数,并对10取余。叶子生命周期越长,这个数会越大,也就更容易被“减速” var yslow = dt * pseudoRand * 0.5 + 1; var xslow = dt * pseudoRand * 0.05 + 1; if (particle.vy < 1) // 速度低于 1 则停止 particle.vy = 0; else particle.vy = (particle.vy / yslow); // 否则除以摩擦系数 if (particle.vx < 1) particle.vx = 0; else particle.vx = (particle.vx / xslow); particle.update = true; } } } //! [1] ImageParticle { anchors.fill: parent id: particles sprites: [Sprite { // 将多个png赋予图像粒子的方法 source: "qrc:/images/realLeaf1.png" frameCount: 1 frameDuration: 1 // 类似“导入者”,其生命周期很短,1ms后它将变成后面的图像 to: {"a":1, "b":1, "c":1, "d":1} // 有1/4的概率变成"a",1/4的概率变成"b"...后面类似 }, Sprite { // 当该图像没有可转变的内容,它将重复播放自己 name: "a" source: "qrc:/images/realLeaf1.png" frameCount: 1 // 我们的单帧静态图也就是仅有一帧的连续图 frameDuration: 10000 }, Sprite { name: "b" source: "qrc:/images/realLeaf2.png" frameCount: 1 frameDuration: 10000 }, Sprite { name: "c" source: "qrc:/images/realLeaf3.png" frameCount: 1 frameDuration: 10000 }, Sprite { name: "d" source: "qrc:/images/realLeaf4.png" frameCount: 1 frameDuration: 10000 } ] z:4 // 在图像中的层次 } } } ~~~ (4)Friction 在上面的例子中我们看到了如何使用代码来模拟一个摩擦效果,但是Qt Quick已经为我们提供了一个模拟摩擦效果的Affector,它就是Friction。 这个例子与上面的例子类似: ![](https://box.kancloud.cn/2016-01-18_569cbd032ca52.jpg) 代码十分简练。friction.qml: ~~~ import QtQuick 2.0 import QtQuick.Particles 2.0 Item { width: 360 height: 600 Image { source: "qrc:/images/backgroundLeaves.jpg" anchors.fill: parent } ParticleSystem { anchors.fill: parent Emitter { width: parent.width emitRate: 4 lifeSpan: 14000 size: 80 velocity: PointDirection { y: 160; yVariation: 80; xVariation: 20 } // xVariation给了叶子水平方向上移动的能力,但是达不到wander的“摆动”效果 } ImageParticle { // 图像粒子同上 anchors.fill: parent id: particles sprites: [Sprite { source: "qrc:/images/realLeaf1.png" frameCount: 1 frameDuration: 1 to: {"a":1, "b":1, "c":1, "d":1} }, Sprite { name: "a" source: "qrc:/images/realLeaf1.png" frameCount: 1 frameDuration: 10000 }, Sprite { name: "b" source: "qrc:/images/realLeaf2.png" frameCount: 1 frameDuration: 10000 }, Sprite { name: "c" source: "qrc:/images/realLeaf3.png" frameCount: 1 frameDuration: 10000 }, Sprite { name: "d" source: "qrc:/images/realLeaf4.png" frameCount: 1 frameDuration: 10000 } ] width: 100 height: 100 x: 20 y: 20 z:4 } //! [0] Friction { // Friction为粒子带来摩擦效果,我们可以设置一个阈值,使Friction只影响速度大于该阈值的粒子。该阈值默认为0 anchors.fill: parent anchors.margins: -40 factor: 0.4 // 摩擦系数,值越大摩擦力越大 } //! [0] } } ~~~ (5)Gravity 类似的,除了摩擦力,我们还有一个Affector用来模拟万有引力。它展示了叶片向地面加速飘落的效果。 ![](https://box.kancloud.cn/2016-01-18_569cbd035ecbf.jpg) 图中的绿色是"地面",可以拖动它360度旋转,叶片始终向“地面”的中心加速下落。gravity.aml: ~~~ import QtQuick 2.0 import QtQuick.Particles 2.0 Item { id: window width: 320; height: 480 Rectangle { id: sky anchors.fill: parent // 蓝色的背景覆盖了整个矩形范围 gradient: Gradient { GradientStop { position: 0.0 color: "DeepSkyBlue" } GradientStop { position: 1.0 color: "SkyBlue" } } } Rectangle { // 因为在后面要实现“地面”的旋转,所以设置了较大的尺寸 id: ground width: parent.height * 2 height: parent.height y: parent.height/2 x: parent.width/2 - parent.height transformOrigin: Item.Top // 用来设置旋转和缩放的中心点 rotation: 0 gradient: Gradient { GradientStop { position: 0.0; color: "ForestGreen"; } GradientStop { position: 1.0; color: "white"; } } } MouseArea { anchors.fill: parent onPositionChanged: { // 该信号在鼠标按下并移动位置时放出,如果不需要按下鼠标,可设置hoverEnabled为true var rot = Math.atan2(mouseY - window.height/2,mouseX - window.width/2) * 180/Math.PI; // 返回当前鼠标方向矢量与X 轴正方向的夹角 ground.rotation = rot; // 以该角度旋转 } } ParticleSystem { id: sys } //! [0] Gravity { // 当使用Gravity时,要注意它对整个场景的吸引力都是相同的,如果角度和加速度恒定,最好直接在Emitter中设置 system: sys // 但在此例中如果直接设置Emitter,角度的计算会比较复杂 magnitude: 32 // 强度 angle: ground.rotation + 90 // 运动方向 } //! [0] Emitter { system: sys anchors.centerIn: parent emitRate: 1 lifeSpan: 10000 size: 64 } ImageParticle { anchors.fill: parent system: sys source: "qrc:/images/realLeaf1.png" } } ~~~ (6)GroupGoal 在前面我们学习到我们可以设置ImageParticle的groups属性,从而让不同的Emitter发送不同的粒子。更进一步,使用ParticleGroup和GroupGoal可以实现粒子在特定状态下的跳变。 ![](https://box.kancloud.cn/2016-01-18_569cbd0370afe.jpg) 可以看到,这些红色的小光点在经过蓝色火焰后被点燃成火苗,同时被鼠标滑过的也将被点燃。界面的右上角还有个数字用来记录被点燃的火苗数。 代码如下,groupgoal.qml: ~~~ import QtQuick 2.0 import QtQuick.Particles 2.0 Rectangle { id: root width: 360 height: 600 color: "black" property int score: 0 // 设置一个属性用来记录分数 Text { color: "white" anchors.right: parent.right text: score } ParticleSystem { id: particles anchors.fill: parent // ![unlit] ParticleGroup { // 这个元素有点类似状态机的概念,它将一种状态下的粒子群以打包的形式放在一起,然后通过name跳转 name: "unlit" duration: 1000 // 1s后进入下一个状态 to: {"lighting":1, "unlit":99} // 设置百分之一的光球可以自燃 ImageParticle { source: "qrc:/images/particleA.png" // 资源文件中的一个光点,有点类似常用的glowdot,但是更大一些 colorVariation: 0.1 color: "#2060160f" // 光点的颜色为红褐色 } GroupGoal { // 继承自Affector,提供特定条件满足下的状态跳转 whenCollidingWith: ["lit"] // 当碰撞到正在燃烧的火苗时 goalState: "lighting" // 跳转为"lighting"状态 jump: true // 设置为立刻跳转 } } // ![unlit] // ![lighting] ParticleGroup { // 一个过渡状态,正在被点亮的状态 name: "lighting" duration: 100 // 0.1秒后跳转到"lit" to: {"lit":1} } // ![lighting] // ![lit] ParticleGroup { // 被点亮状态 name: "lit" duration: 10000 // 终态粒子的生命周期 onEntered: score++; // 分数加一 TrailEmitter { // 使用TrailEmitter构建尾部火焰 id: fireballFlame group: "flame" // 粒子"flame"是基于下方定义的ImageParticle emitRatePerParticle: 48 // 每个lit后跟随48玫"火焰" lifeSpan: 200 // 生命周期与焰尾长度成正比 emitWidth: 8 emitHeight: 8 size: 24 sizeVariation: 8 endSize: 4 // 尾部体积更小 } TrailEmitter { // 另一个TrailEmitter用来构建烟雾 id: fireballSmoke group: "smoke" // smoke在下方定义 // ![lit] emitRatePerParticle: 120 lifeSpan: 2000 // 较长的生命周期用来进行自己的动画 emitWidth: 16 emitHeight: 16 velocity: PointDirection {yVariation: 16; xVariation: 16} acceleration: PointDirection {y: -16} // 烟雾首先向下运动,随之向上升腾 size: 24 sizeVariation: 8 endSize: 8 } } ImageParticle { // 灰色烟雾粒子 id: smoke anchors.fill: parent groups: ["smoke"] source: "qrc:///particleresources/glowdot.png" colorVariation: 0 color: "#00111111" } ImageParticle { // 蓝色闫焰苗粒子 id: pilot anchors.fill: parent groups: ["pilot"] source: "qrc:///particleresources/glowdot.png" redVariation: 0.01 blueVariation: 0.4 // 设置RGB中蓝色的变化率 color: "#0010004f" } ImageParticle { // 红色火焰粒子 id: flame anchors.fill: parent groups: ["flame", "lit", "lighting"] source: "qrc:/images/particleA.png" colorVariation: 0.1 color: "#00ff400f" } Emitter { // 用来发射易燃小球 height: parent.height/2 emitRate: 4 lifeSpan: 4000//TODO: Infinite & kill zone // demo中的注释,TODO表示还要做的事,FIXME表示代码待修改,XXX表示有待商榷 size: 24 sizeVariation: 4 velocity: PointDirection {x:120; xVariation: 80; yVariation: 50} acceleration: PointDirection {y:120} group: "unlit" } Emitter { // 用来构建焰苗 id: flamer x: 100 y: 300 group: "pilot" emitRate: 80 lifeSpan: 600 size: 24 sizeVariation: 2 endSize: 0 velocity: PointDirection { y:-100; yVariation: 4; xVariation: 4 } // 粒子向上移动形成焰苗的升腾感 // ![groupgoal-pilot] GroupGoal { groups: ["unlit"] // 设置被影响的粒子群 goalState: "lit" jump: true // 直接跳转,否则默认为过渡时间结束后再跳转 system: particles x: -15 y: -55 height: 75 width: 30 shape: MaskShape {source: "qrc:/images/matchmask.png"} // 这张图片是一个焰苗的图形,使用它可以使Affector影响一个非矩形区域 } // ![groupgoal-pilot] } // ![groupgoal-ma] //Click to enflame GroupGoal { groups: ["unlit"] // 设置其可以影响的粒子群 goalState: "lighting" // 目标状态 jump: true enabled: ma.pressed // 按下事件使能 width: 18 // 作用区域 height: 18 x: ma.mouseX - width/2 y: ma.mouseY - height/2 } // ![groupgoal-ma] MouseArea { id: ma anchors.fill: parent } } } ~~~ (7)Move 这个例子展示了直接使用Affector影响粒子运动(位置、速度、加速度)的方法。 ![](https://box.kancloud.cn/2016-01-18_569cbd0387fc5.jpg) 代码很简单,我们大致看一下好了,move.qml: ~~~ import QtQuick 2.0 import QtQuick.Particles 2.0 Rectangle { width: 360 height: 540 color: "black" ParticleSystem { // 第一束红色粒子 anchors.fill: parent ImageParticle { groups: ["A"] anchors.fill: parent source: "qrc:///particleresources/star.png" color:"#FF1010" redVariation: 0.8 // 形成"明红"到"暗红"的颜色差异 } Emitter { group: "A" emitRate: 100 lifeSpan: 2800 size: 32 sizeVariation: 8 velocity: PointDirection{ x: 66; xVariation: 20 } width: 80 // 产生粒子的区域是(0,0)到(80,80)的矩形范围 height: 80 } //! [A] Affector { groups: ["A"] // Affector作用于A x: 120 // 影响区域 width: 80 height: 80 once: true position: PointDirection { x: 120; } // x 增加120 } //! [A] ImageParticle { // 第二束绿色粒子 groups: ["B"] anchors.fill: parent source: "qrc:///particleresources/star.png" color:"#10FF10" greenVariation: 0.8 } Emitter { group: "B" emitRate: 100 lifeSpan: 2800 size: 32 sizeVariation: 8 velocity: PointDirection{ x: 240; xVariation: 60 } y: 260 width: 10 height: 10 } //! [B] Affector { groups: ["B"] x: 120 y: 240 width: 80 height: 80 once: true velocity: AngleDirection { angleVariation:360; magnitude: 72 } // 角度变化范围和强度 } //! [B] ImageParticle { // 第三束蓝色粒子 groups: ["C"] anchors.fill: parent source: "qrc:///particleresources/star.png" color:"#1010FF" blueVariation: 0.8 } Emitter { group: "C" y: 400 emitRate: 100 lifeSpan: 2800 size: 32 sizeVariation: 8 velocity: PointDirection{ x: 80; xVariation: 10 } acceleration: PointDirection { y: 10; x: 20; } width: 80 height: 80 } //! [C] Affector { groups: ["C"] x: 120 y: 400 width: 80 height: 120 once: true relative: false acceleration: PointDirection { y: -80; } // 在y方向的加速度下降80 } //! [C] } } ~~~ (8)SpriteGoal 这个例子向我们展示了如何对使用sprites的ImageParticle做特殊的处理,使其在我们想要它改变时进行状态的跳转。 如图是“星际迷航”中的飞船,它将撞毁其接触到的陨石。 ![](https://box.kancloud.cn/2016-01-18_569cbd03a6c04.jpg) spritegoal.qml: ~~~ import QtQuick 2.0 import QtQuick.Particles 2.0 Item { id: root width: 360 height: 540 MouseArea { id: ma anchors.fill: parent } ParticleSystem { id: sys } Image { source: "qrc:/images/finalfrontier.png" // 星际迷航 transformOrigin: Item.Center // 以中心点旋转,一共有9个点可选,四个边角,四个边线中心,以及中心点 anchors.centerIn: parent NumberAnimation on rotation { // 背景缓慢旋转 from: 0 to: 360 duration: 200000 loops: Animation.Infinite } } ImageParticle { // 星星粒子 system: sys groups: ["starfield"] source: "qrc:///particleresources/star.png" colorVariation: 0.3 color: "white" } Emitter { id: starField system: sys group: "starfield" emitRate: 80 lifeSpan: 2500 anchors.centerIn: parent //acceleration: AngleDirection {angleVariation: 360; magnitude: 200}//Is this a better effect, more consistent velocity? acceleration: PointDirection { xVariation: 200; yVariation: 200; } // 上面是源码中的注释,作者留给我们一个问题,这个从中心点向外散射的粒子,是使用AngleDirection还是PointDirection?笔者想了下,以第一行代码发射的话,所有粒子的速度都将是相同的,而第二行代码则具有更大的随机性。以星星的散射而言,第二行代码更合理。 size: 0 endSize: 80 sizeVariation: 10 } Emitter { // 陨石的发射器 system: sys group: "meteor" emitRate: 12 lifeSpan: 5000 acceleration: PointDirection { xVariation: 80; yVariation: 80; } // 与星星的发射类似 size: 15 endSize: 300 // 增大的endSize形成由远及进感 anchors.centerIn: parent } ImageParticle { // 陨石粒子,由sprites的多帧图像构成 system: sys groups: ["meteor"] sprites:[Sprite { id: spinState // 自旋陨石 name: "spinning" source: "qrc:/images/meteor.png" frameCount: 35 frameDuration: 40 randomStart: true // 从随意的一帧开始 to: {"explode":0, "spinning":1} // 由于"explode"为0,因此spinning实际上是无限循环。"explode": 0可以不写。但为了逻辑清楚,加上更好 },Sprite { // 碎裂陨石 name: "explode" source: "qrc:/images/_explo.png" frameCount: 22 frameDuration: 40 to: {"nullFrame":1} // 去到一个空白图像 },Sprite {//Not sure if this is needed, but seemed easiest // 作者称不确定这个空白图像是否需要,但是带上它似乎更好 name: "nullFrame" source: "qrc:/images/nullRock.png" frameCount: 1 frameDuration: 1000 } ] } //! [0] SpriteGoal { // 这就是Affector中的SpriteGoal了 groups: ["meteor"] // 与groupGoal不同,GroupGoal影响的ParticleGroup,而SpriteGoal影响的是这里使用Sprites的粒子 system: sys goalState: "explode" // 目标状态 jump: true // 立刻跳转 anchors.fill: rocketShip // 作用范围跟随飞船 width: 60 height: 60 } //! [0] Image { // 企业号飞船,因为要使飞船绕一个固定的中心点旋转,坐标与旋转的计算全部放在Image中比较麻烦,我们可以使用两个Item来进行逻辑上的圆周计算 id: rocketShip source: "qrc:/images/rocket.png" anchors.centerIn: holder rotation: (circle.percent+0.25) * 360 // 随着所在圆周位置的不同对自身进行旋转。由于原图飞船是向上的,因此将其初始旋转90度 z: 2 } Item { // 通过下面的圆心和连续变化的百分比,这个Item用来得到实际的坐标 id: holder x: circle.x - Math.sin(circle.percent * 6.28316530714)*200 // 百分比乘以2π,200为半径 y: circle.y + Math.cos(circle.percent * 6.28316530714)*200 z: 1 } Item { id: circle x: root.width / 1.2 // 圆心的位置 y: root.height / 1.7 property real percent: 0 // 定义一个百分比属性 SequentialAnimation on percent { // 4秒的1到0循环 id: circleAnim1 loops: Animation.Infinite running: true NumberAnimation { duration: 4000 from: 1 to: 0 } } } ImageParticle { // 飞船的尾气粒子 z:0 // 其z值比飞船小,这样这些粒子不会覆盖在飞船上面 system: sys groups: ["exhaust"] source: "qrc:///particleresources/fuzzydot.png" color: "orange" SequentialAnimation on color { loops: Animation.Infinite ColorAnimation { from: "red" to: "cyan" duration: 1000 } ColorAnimation { from: "cyan" to: "red" duration: 1000 } } colorVariation: 0.2 } Emitter { // 喷气粒子发射器 id: trailsNormal2 system: sys group: "exhaust" emitRate: 300 lifeSpan: 500 y: holder.y x: holder.x velocity: PointDirection { xVariation: 40; yVariation: 40; } velocityFromMovement: 16 acceleration: PointDirection { xVariation: 10; yVariation: 10; } size: 4 sizeVariation: 4 } } ~~~ (9)Turbulence 在上篇博文的最后一个小例子——飞翔的火焰 中我们其实已经接触到了Turbulence,它用来为粒子提供一个气流的效果。在这个例子中我们可以更清晰地看到它的用法。 可以看到Turbulence为火苗和烟雾带来的效果: ![](https://box.kancloud.cn/2016-01-18_569cbd03ca434.jpg) Turbulence.qml: ~~~ import QtQuick 2.0 import QtQuick.Particles 2.0 Rectangle { width: 320 height: 480 color: "#222222" id: root Image { source: "qrc:/images/candle.png" // 一根空白的蜡烛 anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: -60 // 这张图下面有一段空白 anchors.horizontalCenterOffset: 2 // 水平中心向右平移2个像素 } ParticleSystem { anchors.fill: parent MouseArea { // 点击后关闭/打开Turbulence效果 anchors.fill: parent onClicked: turb.enabled = !turb.enabled } //! [0] Turbulence { id: turb enabled: true height: (parent.height / 2) - 4 width: parent.width x: parent. width / 4 anchors.fill: parent strength: 32 // 可以为strength添加一个NumberAnimation,然后通过设置Easing,可以达到更逼近现实的气流效果 NumberAnimation on strength{from: 16; to: 64; easing.type: Easing.InOutBounce; duration: 1800; loops: -1} } //! [0] ImageParticle { // 烟雾 groups: ["smoke"] source: "qrc:///particleresources/glowdot.png" color: "#11111111" colorVariation: 0 } ImageParticle { // 火苗 groups: ["flame"] source: "qrc:///particleresources/glowdot.png" color: "#11ff400f" colorVariation: 0.1 } Emitter { // 火苗粒子由窗口中心发出 anchors.centerIn: parent group: "flame" emitRate: 120 lifeSpan: 1200 size: 20 endSize: 10 sizeVariation: 10 acceleration: PointDirection { y: -40 } velocity: AngleDirection { angle: 270; magnitude: 20; angleVariation: 22; magnitudeVariation: 5 } } TrailEmitter { id: smoke1 width: root.width height: root.height/2 group: "smoke" follow: "flame" emitRatePerParticle: 1 lifeSpan: 2400 lifeSpanVariation: 400 size: 16 endSize: 8 sizeVariation: 8 acceleration: PointDirection { y: -40 } velocity: AngleDirection { angle: 270; magnitude: 40; angleVariation: 22; magnitudeVariation: 5 } } TrailEmitter { // 第二个TrailEmitter用来在更高一点的地方释放出更浓郁的烟雾 id: smoke2 width: root.width height: root.height/2 - 20 group: "smoke" follow: "flame" emitRatePerParticle: 4 lifeSpan: 2400 size: 36 endSize: 24 sizeVariation: 12 acceleration: PointDirection { y: -40 } velocity: AngleDirection { angle: 270; magnitude: 40; angleVariation: 22; magnitudeVariation: 5 } } } } ~~~ (10)Wander 同样我们在本文第三个小例子中已经接触过wander了,在那我们使用wander为落叶添加了摇摆的飘落效果。在这个例子中我们将了解到,除了速度,wander还可以进一步作用于位置和加速度。 ![](https://box.kancloud.cn/2016-01-18_569cbd03dd66d.jpg) 可以看到在飘落的雪花背景中,有三个按钮分别用来选择位置,速度,以及加速度。通过点击这些按钮,可以改变这些雪花在x方向上的不同运动效果。这些按钮是在另一个Qml文件中定义的,代码比较简单,贴在下面,就不一句句介绍了。 GreyButton.qml: ~~~ import QtQuick 2.0 Item { id: container property string text: "Button" property string subText: "" signal clicked width: buttonLabel.width + 20; height: col.height + 12 MouseArea { id: mouseArea; anchors.fill: parent; onClicked: container.clicked(); onPressed: background.color = Qt.darker("lightgrey"); onReleased: background.color="lightgrey"; } Rectangle { id: background anchors.fill: parent color: "lightgrey" radius: 4 border.width: 1 border.color: Qt.darker(color) } Column { spacing: 2 id: col x: 10 y: 6 Text { id: buttonLabel; text: container.text; color: "black"; font.pixelSize: 24 } Text { id: buttonLabel2; text: container.subText; color: "black"; font.pixelSize: 12 } } } ~~~ wander.qml: ~~~ import QtQuick 2.0 import QtQuick.Particles 2.0 Rectangle { width: 360 height: 540 ParticleSystem { id: particles } ImageParticle { // 雪花粒子 system: particles sprites: Sprite { name: "snow" source: "../../images/snowflake.png" frameCount: 51 frameDuration: 40 frameDurationVariation: 8 } } //! [0] Wander { // wander id: wanderer system: particles anchors.fill: parent xVariance: 360/(wanderer.affectedParameter+1); // xVariance与pace必须都定义,由于没有定义yVariance因此不会影响y方向的运动 pace: 100*(wanderer.affectedParameter+1); // 这里wanderer.affectedParameter实际等于0,不太懂这里的意思 } //! [0] Emitter { system: particles emitRate: 20 lifeSpan: 7000 velocity: PointDirection { y:80; yVariation: 40; } acceleration: PointDirection { y: 4 } size: 20 sizeVariation: 10 width: parent.width height: 100 } Row { // 这里使用了一个布局器 anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter spacing: 4 GreyButton { text:"dx/dt" onClicked: wanderer.affectedParameter = Wander.Position; // 点击改变Wander的影响属性 } GreyButton { text:"dv/dt" onClicked: wanderer.affectedParameter = Wander.Velocity; } GreyButton { text:"da/dt" onClicked: wanderer.affectedParameter = Wander.Acceleration; } } } ~~~