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
移动到 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(); }
请注意,如果 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.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 专家,你可以在第一次迭代中写下这些内容,但在本教程中,它们已保存到下一章 - 在那里你的应用程序将变得栩栩如生!