Qt开发案例实战:QML高级教程4-让游戏程序活跃起来

Qt是一个跨平台框架,通常用作图形工具包,它不仅创建CLI应用程序中非常有用。而且它也可以在三种主要的台式机操作系统以及移动操作系统(如Symbian,Nokia Belle,Meego Harmattan,MeeGo或BB10)以及嵌入式设备,Android(Necessitas)和iOS的端口上运行。现在我们为你提供了免费的试用版。

下载Qt6最新试用版

Qt组件推荐:

  • QtitanRibbon下载试用: 遵循Microsoft Ribbon UI Paradigm for Qt技术的Ribbon UI组件,致力于为Windows、Linux和Mac OS X提供功能完整的Ribbon组件。
  • QtitanChart | 下载试用 :是一个C ++库,代表一组控件,这些控件使您可以快速地为应用程序提供漂亮而丰富的图表。并且支持所有主要的桌面。
  • QtitanNavigation | 下载试用 :模拟Microsoft Dynamics CRM-2016/Office 365导航界面和一组控件改善Qt.C ++应用程序用户体验的QtitanNavigation组件。

现在我们要做两件事来活跃游戏:给积木做动画,并添加一个高分系统。

我们还清理了应用程序文件的目录结构。我们现在有很多文件,所以所有samegame.qml以外的JavaScript和QML文件都被移到了一个名为 "content "的新子目录中。

为了迎接新的块状动画,Block.qml文件现在改名为BoomBlock.qml。

动画块运动

首先我们将对块进行动画,使它们以流体的方式移动。QML有很多方法来添加流体运动,在这种情况下,我们将使用Behavior类型来添加一个SpringAnimation。在BoomBlock.qml中,我们将SpringAnimation行为应用到x和y属性中,这样块体将以类似弹簧的方式跟随并向指定的位置移动(其值将由samegame.js设置)。以下是添加到BoomBlock.qml中的代码。

property bool spawned: false

Behavior on x {
    enabled: 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上的SpringAnimation。否则,当游戏开始时,块会从角落(0,0)滑出,而不是成行地从顶部落下。试着注释出enabled: spawned,然后自己看看)。

动画块不透明度变化

接下来,我们将添加一个平滑的退出动画。为此,我们将使用一个Behavior类型,它允许我们在发生属性变化时指定一个默认的动画。在这种情况下,当一个Block的不透明度变化时,我们将对不透明度值进行动画处理,使其逐渐淡入和淡出,而不是在完全可见和不可见之间突然变化。要做到这一点,我们将在 BoomBlock.qml 中的 Image 类型的不透明度属性上应用一个 Behavior。

Image {
    id: img

    anchors.fill: parent
    source: {
        if (type == 0)
            return "../../shared/pics/redStone.png";
        else if (type == 1)
            return "../../shared/pics/blueStone.png";
        else
            return "../../shared/pics/greenStone.png";
    }
    opacity: 0

    Behavior on opacity {
        NumberAnimation { properties:"opacity"; duration: 200 }
    }
}

请注意不透明度:0,这意味着块在第一次创建时是透明的。我们可以在创建和销毁块时在samegame.js中设置不透明度,但我们将使用状态,因为这对我们将要添加的下一个动画很有用。最初,我们将这些状态添加到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。为了淡出,我们将死亡设置为true,而不是在块被破坏时将不透明度设置为0(在floodFill()函数中)。

添加粒子效果

最后,我们将在积木被破坏时为它们添加一个看起来很酷的粒子效果。要做到这一点,我们首先在BoomBlock.qml中添加一个粒子系统,像这样。

 

要想完全理解这一点,你应该阅读《使用Qt快速粒子系统》,但需要注意的是,emitRate被设置为零,这样粒子就不会正常发射了。另外,我们还扩展了垂死状态,它通过调用粒子类型上的burst()方法来创建粒子的爆发。现在状态的代码是这样的。

states: [
    State {
        name: "AliveState"
        when: spawned == true && dying == false
        PropertyChanges { target: img; opacity: 1 }
    },

    State {
        name: "DeathState"
        when: dying == true
        StateChangeScript { script: particles.burst(50); }
        PropertyChanges { target: 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);
    }
}

当对话框发出关闭信号时,我们调用samegame.js中新的saveHighScore()函数,将高分存储在本地的SQL数据库中,如果可能的话还可以将分数发送到在线数据库中。

在samegame.js中的victoryCheck()函数中,nameInputDialog被激活。

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()(在下面的章节中解释),如果可以将高分发送到在线数据库。

然后,我们使用本地存储API来维护这个应用程序特有的持久化SQL数据库。我们使用openDatabaseSync()为高分创建一个离线存储数据库,并准备好我们要用来保存高分的数据和SQL查询。离线存储API使用SQL查询来进行数据操作和检索,在db.transaction()调用中,我们使用三个SQL查询来初始化数据库(如果需要的话),然后添加到和检索高分。为了使用返回的数据,我们将其变成一个字符串,每行返回一行,并显示一个包含该字符串的对话框。

这是本地存储和显示高分的一种方式,但肯定不是唯一的方式。一个更复杂的选择是创建一个高分对话框组件,并将结果传递给它进行处理和显示(而不是重复使用Dialog)。这将允许一个更有主题的对话框,可以更好地展示高分。如果你的QML是一个C++应用的UI,你也可以将分数传递给一个C++函数,以多种方式将其存储在本地,包括不用SQL的简单格式或另一个SQL数据库中。

在线存储高分

你已经看到了如何在本地存储高分,但也很容易在你的QML应用程序中集成一个支持网络的高分存储。我们对她的实现非常简单:将高分数据发布到某个服务器上运行的php脚本中,然后该服务器将其存储并显示给访问者。你也可以从同一个服务器上请求一个XML或QML文件,其中包含并显示分数,但这超出了本教程的范围。我们在这里使用的php脚本可以在examples目录下找到。

如果玩家输入了他们的名字,我们可以将数据发送到网络服务中,我们

如果玩家输入一个名字,我们就用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),你可以用与块相同的方式来实例化它。

猜你喜欢

转载自blog.csdn.net/qq_42444778/article/details/115299731