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 { 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
的倍数,因此blockSize
被移出samegame.js
,作为 QML 属性移入samegame.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(); }
请注意,如果score
是samegame.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
类型创建,该类型在Dialog.qml
中定义。以下是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。
点缀色彩
如果所有图块的颜色都一样,玩 Same Game 就没什么意思了,因此我们修改了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 专家,你本可以在第一次迭代中编写这些内容,但在本教程中,这些内容被保留到了下一章--在这一章中,你的应用程序将变得生动活泼!
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.