QML高级教程4 - 画龙点睛#
增添一些趣味#
现在我们将进行两件事来使游戏更生动:为方块添加动画并添加高分系统。
为了期待新的方块动画,Block.qml
文件现更名为 BoomBlock.qml
。
动画化方块移动#
首先我们将动画化方块,以便它们以流畅的方式移动。QML有多种方法可以添加流畅的运动,在这种情况下,我们将使用 行为 类型来添加 弹簧动画 。在 BoomBlock.qml
中,我们为 x
和 y
属性应用了一个 弹簧动画 行为,以便方块可以跟随并以弹簧似的模式动画化其移动到指定的位置(其值将由 samegame.js
设置)。以下是添加到 BoomBlock.qml
中的代码
property bool spawned: false Behavior on x { enabled: block.spawned; SpringAnimation{ spring: 2; damping: 0.2 } } Behavior on y { SpringAnimation{ spring: 2; damping: 0.2 } }
可以更改 spring
和 damping
值以修改动画的弹簧效果。
enabled: spawned
设置指的是从 samegame.js
中的 createBlock()
中设置的 spawned
值。这确保了在 createBlock()
将方块设置到正确的位置后,只有在 x
上的 弹簧动画 才被启用。否则,方块将滑动出左上角(0,0),而不是以行的方式从顶部落下。(尝试取消注释 enabled: spawned
并亲自看看。)
动画化方块不透明度变化#
接下来,我们将添加一个平滑的退出动画。为此,我们将使用 行为 类型,它允许我们在属性更改时指定默认动画。在这种情况下,当 opacity
的值为 Block 改变时,我们将动画不透明度值,以便它逐渐淡入淡出,而不是在完全可见和不可见之间突然改变。为此,我们将在 BoomBlock.qml
中的 Image
类型的 opacity
属性上应用一个 行为。
Image { id: img anchors.fill: parent source: { if (block.type == 0) return "pics/redStone.png"; else if (block.type == 1) return "pics/blueStone.png"; else return "pics/greenStone.png"; } opacity: 0 Behavior on opacity { NumberAnimation { properties:"opacity"; duration: 200 } } }
请注意 opacity: 0
,这意味着当块首次创建时是透明的。我们可以在 samegame.js
中设置透明度,在创建和销毁块时,但我们将使用 states,因为它对于我们将要添加的下一个动画很有用。最初,我们将这些状态添加到 BoomBlock.qml
的根类型。
property bool dying: false states: [ State{ name: "AliveState"; when: spawned == true && dying == false PropertyChanges { target: img; opacity: 1 } }, State{ name: "DeathState"; when: dying == true PropertyChanges { target: img; opacity: 0 } } ]
现在,块将自动淡入,因为我们已经在实现块动画时将 spawned
设置为 true。要淡出,当块被销毁时(在 floodFill()
函数中),我们将 dying
设置为 true 而不是将透明度设置为 0。
添加粒子效果#
最后,当块被销毁时,我们将为它们添加一个酷炫的粒子效果。要做到这一点,我们首先在 BoomBlock.qml
中添加一个 ParticleSystem,如下所示
ParticleSystem { id: sys anchors.centerIn: parent ImageParticle { source: { if (block.type == 0) return "pics/redStar.png"; else if (block.type == 1) return "pics/blueStar.png"; else return "pics/greenStar.png"; } rotationVelocityVariation: 360 } Emitter { id: particles anchors.centerIn: parent emitRate: 0 lifeSpan: 700 velocity: AngleDirection {angleVariation: 360; magnitude: 80; magnitudeVariation: 40} size: 16 } }
要充分理解这一点,你应该阅读 使用 Qt Quick 粒子系统,但重要的是要注意 emitRate
被设置为零,这样就不会正常发出粒子。此外,我们扩展了 dying
状态,通过调用粒子的 burst()
方法来创建粒子爆发。现在状态代码如下
states: [ State { name: "AliveState" when: block.spawned == true && block.dying == false PropertyChanges { img.opacity: 1 } }, State { name: "DeathState" when: block.dying == true StateChangeScript { script: particles.burst(50); } PropertyChanges { img.opacity: 0 } StateChangeScript { script: block.destroy(1000); } } ]
现在游戏得到了美丽的动画,为玩家的所有动作添加了细微(或不那么细微)的动画。最终结果如下所示,使用不同的一组图片来展示基本的主题设计
这里的主题更改很简单,只需替换块图片即可完成。这可以在运行时通过更改 Image 的 source
属性来实现,因此作为一个额外的挑战,你可以添加一个按钮,在具有不同图片的主题之间切换。
保持高分表#
我们可能还想要为游戏添加一种存储和检索高分的机制。
为此,当游戏结束时,我们将显示一个对话框,请求玩家的名字并将其添加到高分表中。这需要对 Dialog.qml
进行一些修改。除了一个 Text
类型外,它现在有一个 TextInput
子项,用于接收键盘文本输入
Rectangle { id: container ... TextInput { id: textInput anchors { verticalCenter: parent.verticalCenter; left: dialogText.right } width: 80 text: "" onAccepted: container.hide() // close dialog when Enter is pressed } ... }
我们还会添加一个 showWithInput()
函数。只有在调用此函数而不仅仅是 show()
时,文本输入才会可见。当对话框关闭时,它发出一个 closed()
信号,其他类型可以通过 inputText
属性检索用户输入的文本
Rectangle { id: container property string inputText: textInput.text signal closed function show(text) { dialogText.text = text; container.opacity = 1; textInput.opacity = 0; } function showWithInput(text) { show(text); textInput.opacity = 1; textInput.focus = true; textInput.text = "" } function hide() { textInput.focus = false; container.opacity = 0; container.closed(); } ... }
现在这个对话框可以用于 samegame.qml
Dialog { id: nameInputDialog anchors.centerIn: parent z: 100 onClosed: { if (nameInputDialog.inputText != "") SameGame.saveHighScore(nameInputDialog.inputText); } }
当对话框发出 closed
信号时,我们将调用 samegame.js
中的新 saveHighScore()
函数,它将高分数本地存储在 SQL 数据库中,如果可能,还会将分数发送到在线数据库。
nameInputDialog
组件在 samegame.js
文件中 victoryCheck()
函数中被激活。
function victoryCheck() { ... //Check whether game has finished if (deservesBonus || !(floodMoveCheck(0, maxRow - 1, -1))) { gameDuration = new Date() - gameDuration; nameInputDialog.showWithInput("You won! Please enter your name: "); } }
离线存储高分
现在我们需要实现保存高分表的函数功能。
以下是 samegame.js
中 saveHighScore()
函数的示例
function saveHighScore(name) { if (scoresURL != "") sendHighScore(name); var db = Sql.LocalStorage.openDatabaseSync("SameGameScores", "1.0", "Local SameGame High Scores", 100); var dataStr = "INSERT INTO Scores VALUES(?, ?, ?, ?)"; var data = [name, gameCanvas.score, maxColumn + "x" + maxRow, Math.floor(gameDuration / 1000)]; db.transaction(function(tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS Scores(name TEXT, score NUMBER, gridSize TEXT, time NUMBER)'); tx.executeSql(dataStr, data); var rs = tx.executeSql('SELECT * FROM Scores WHERE gridSize = "12x17" ORDER BY score desc LIMIT 10'); var r = "\nHIGH SCORES for a standard sized grid\n\n" for (var i = 0; i < rs.rows.length; i++) { r += (i + 1) + ". " + rs.rows.item(i).name + ' got ' + rs.rows.item(i).score + ' points in ' + rs.rows.item(i).time + ' seconds.\n'; } dialog.show(r); }); }
首先,如果有可能将高分发送到在线数据库中,我们将调用 sendHighScore()
函数(在下面的章节中解释)。
然后,我们使用 Local Storage API 来维护一个针对本应用唯一的持久SQLite数据库。我们使用 openDatabaseSync()
创建一个用于高分存储的离线存储数据库,并准备好要使用的数据和SQL查询。离线存储API使用SQL查询进行数据操纵和检索,我们在 db.transaction()
调用中使用三个SQL查询来初始化数据库(如果需要),然后添加和检索高分。为了使用返回的数据,我们将它转换成一个以每行返回一个的字符串,并显示包含该字符串的对话框。
这是本地存储和显示高分的一种方法,但当然不是唯一的方法。更复杂的替代方法是创建一个高分对话框组件,并将处理和显示结果传递给它(而不是重复使用 Dialog
)。这将允许更具主题性的对话框,能够更好地展示高分。如果你的QML是C++应用程序的UI,你也可以将分数传递给一个C++函数,以多种方式在本地存储它,包括无SQL的简单格式或在其他SQL数据库中。
在线存储高分
你已经看到了如何本地存储高分,但也很容易将具有Web功能的分数存储集成到您的QML应用程序中。我们在这里实现的实现非常简单:将高分数据发送到一个远程服务器上运行的PHP脚本中,然后该服务器将其存储并显示给访客。你也可以从那个服务器请求一个XML或QML文件,其中包含和显示分数,但这超出了本教程的范畴。我们使用的PHP脚本位于 examples
目录中。
如果玩家输入了他们的名字,我们可以将数据发送到我们的Web服务
如果玩家输入了名字,我们使用 samegame.js
中的此代码将数据发送到服务
function sendHighScore(name) { var postman = new XMLHttpRequest() var postData = "name=" + name + "&score=" + gameCanvas.score + "&gridSize=" + maxColumn + "x" + maxRow + "&time=" + Math.floor(gameDuration / 1000); postman.open("POST", scoresURL, true); postman.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); postman.onreadystatechange = function() { if (postman.readyState == postman.DONE) { dialog.show("Your score has been uploaded."); } } postman.send(postData); }
此代码中的XMLHttpRequest与标准浏览器JavaScript中的XMLHttpRequest()相同,并可以以相同的方式用于从Web服务动态获取XML或QML以显示高分。在这个情况下,我们不担心响应 - 我们只是将高分数据发送到Web服务器。如果它返回了一个QML文件(或指向QML文件的URL),你就可以像处理块一样实例化它。
访问和提交基于Web的数据的另一种方式是使用为该目的设计的QML类型。XmlListModel在QML应用程序中检索和显示基于XML的数据(如RSS)变得非常容易。
完成了!#
通过本教程,您已经了解到如何使用QML编写一个功能齐全的应用程序
使用QML类型构建您的应用程序
使用JavaScript代码添加应用程序逻辑
使用,例如,QtQuick.LocalStorage或XMLHttpRequest来存储持久的应用程序数据
关于QML还有很多内容值得学习,我们在这篇教程中无法全部涵盖。请查看所有示例和 expectedResult