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 的值,并在游戏结束时显示“Game Over”对话框。
“Game Over”对话框是使用在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。
一点色彩
如果所有方块都是同一种颜色,玩“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 专家,本可以在第一次迭代中就实现这些功能,但在本教程中,这些内容将留到下一章——届时你的应用程序将焕发活力!
© 2026 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.