QML高级教程3 - 实现游戏逻辑

制作可玩游戏

现在我们已经拥有了所有的游戏组件,我们可以添加处理玩家如何与方块交互以及游戏如何赢得或输掉的条件逻辑。

为此,我们已在samegame.js中添加了以下函数

  • handleClick(x,y)
  • floodFill(xIdx,yIdx,type)
  • shuffleDown()
  • victoryCheck()
  • floodMoveCheck(xIdx, yIdx, type)

由于这是一个有关QML而不是游戏设计的教程,以下只讨论handleClick()victoryCheck(),因为它们直接与QML类型交互。请注意,尽管这里的游戏逻辑是用JavaScript编写的,但它本可以用C++编写然后暴露给QML。

启用鼠标点击交互

为了使JavaScript代码更容易与QML类型交互,我们在samegame.qml中添加了一个名为gameCanvas的Item。它替换了作为包含方块的背景的Item。它还接受用户的鼠标输入。以下是该项代码

        Item {
            id: gameCanvas

            property int score: 0
            property int blockSize: 40

            width: parent.width - (parent.width % blockSize)
            height: parent.height - (parent.height % blockSize)
            anchors.centerIn: parent

            MouseArea {
                anchors.fill: parent
                onClicked: (mouse)=> SameGame.handleClick(mouse.x, mouse.y)
            }
        }

gameCanvas项恰好与棋盘大小相同,并且具有score属性和一个MouseArea来处理鼠标点击。方块现在作为其子项创建,并使用其尺寸来确定棋盘大小,以便应用程序缩放到屏幕大小。由于其大小绑定到blockSize的倍数,因此将blockSizesamegame.js移动到samegame.qml中作为QML属性。注意,它仍然可以从脚本中访问。

当点击时,MouseArea会调用samegame.js中的handleClick(),以确定玩家的点击是否会导致删除任何方块,并在必要时更新gameCanvas.score。以下是handleClick()函数

function handleClick(xPos, yPos) {
    var column = Math.floor(xPos / gameCanvas.blockSize);
    var row = Math.floor(yPos / gameCanvas.blockSize);
    if (column >= maxColumn || column < 0 || row >= maxRow || row < 0)
        return;
    if (board[index(column, row)] == null)
        return;
    //If it's a valid block, remove it and all connected (does nothing if it's not connected)
    floodFill(column, row, -1);
    if (fillFound <= 0)
        return;
    gameCanvas.score += (fillFound - 1) * (fillFound - 1);
    shuffleDown();
    victoryCheck();
}

请注意,如果scoresamegame.js文件中是全局变量,您就无法绑定它。您只能绑定到QML属性。

更新分数

当玩家点击一个方块并触发handleClick()时,handleClick()也会调用victoryCheck()来更新分数并检查玩家是否完成了游戏。以下是victoryCheck()代码

function victoryCheck() {
    //Award bonus points if no blocks left
    var deservesBonus = true;
    for (var column = maxColumn - 1; column >= 0; column--)
        if (board[index(column, maxRow - 1)] != null)
        deservesBonus = false;
    if (deservesBonus)
        gameCanvas.score += 500;

    //Check whether game has finished
    if (deservesBonus || !(floodMoveCheck(0, maxRow - 1, -1)))
        dialog.show("Game Over. Your score is " + gameCanvas.score);
}

这更新了gameCanvas.score值,并在游戏结束时显示“游戏结束”对话框。

游戏结束对话框是使用在Dialog.qml中定义的Dialog类型创建的。以下是Dialog.qml的代码。注意它是如何被设计成可以通过脚本文件中的函数和信号使用命令式方式的。

import QtQuick

Rectangle {
    id: container

    function show(text) {
        dialogText.text = text;
        container.opacity = 1;
    }

    function hide() {
        container.opacity = 0;
    }

    width: dialogText.width + 20
    height: dialogText.height + 20
    opacity: 0

    Text {
        id: dialogText
        anchors.centerIn: parent
        text: ""
    }

    MouseArea {
        anchors.fill: parent
        onClicked: hide();
    }
}

这是它在主samegame.qml文件中的用法。

    Dialog {
        id: dialog
        anchors.centerIn: parent
        z: 100
    }

我们给对话框赋予了一个z值为100,以确保它显示在其他组件之上。默认情况下项目的z值是0。

一点色彩

如果所有方块都是相同的颜色,那么玩同样的游戏不是很有趣。因此,我们已经修改了samegame.js中的createBlock()函数,以便每次调用时随机创建一种不同类型的方块(红色、绿色或蓝色)。Block.qml也已经更改,以便每个方块都包含一个根据其类型不同的图像。

import QtQuick

Item {
    id: block

    property int type: 0

    Image {
        id: img

        anchors.fill: parent
        source: {
            if (type == 0)
                return "pics/redStone.png";
            else if (type == 1)
                return "pics/blueStone.png";
            else
                return "pics/greenStone.png";
        }
    }
}
一个有效的游戏

现在我们已经有一个有效的游戏了!方块可以被点击,玩家可以获得分数,游戏可以结束(然后您可以开始一个新游戏)。以下是到目前为止所完成的内容的截图

这是现在的samegame.qml的样子

import QtQuick
import "samegame.js" as SameGame

Rectangle {
    id: screen

    width: 490; height: 720

    SystemPalette { id: activePalette }

    Item {
        width: parent.width
        anchors { top: parent.top; bottom: toolBar.top }

        Image {
            id: background
            anchors.fill: parent
            source: "pics/background.jpg"
            fillMode: Image.PreserveAspectCrop
        }

        Item {
            id: gameCanvas

            property int score: 0
            property int blockSize: 40

            width: parent.width - (parent.width % blockSize)
            height: parent.height - (parent.height % blockSize)
            anchors.centerIn: parent

            MouseArea {
                anchors.fill: parent
                onClicked: (mouse)=> SameGame.handleClick(mouse.x, mouse.y)
            }
        }
    }

    Dialog {
        id: dialog
        anchors.centerIn: parent
        z: 100
    }

    Rectangle {
        id: toolBar
        width: parent.width; height: 30
        color: activePalette.window
        anchors.bottom: screen.bottom

        Button {
            anchors { left: parent.left; verticalCenter: parent.verticalCenter }
            text: "New Game"
            onClicked: SameGame.startNewGame()
        }

        Text {
            id: score
            anchors { right: parent.right; verticalCenter: parent.verticalCenter }
            text: "Score: Who knows?"
        }
    }
}

游戏正在运行,但目前有点无聊。平滑的动画转换在哪里?高分在哪里?如果您是一位QML专家,您可以在首次迭代中写入这些内容,但在本教程中,这些内容被保存到了下一章——此时您的应用程序变得生动起来!

项目示例 @ code.qt.io

© 2024 Qt公司 Ltd. 所包含的文档贡献是各自所有者的版权。此处提供的文档根据自由软件基金会发布的版本1.3的GNU自由文档许可证的条款进行授权。Qt及其标志是芬兰和/或其他国家的Qt公司的商标。所有其他商标都是其各自所有者的财产。