QML-Tutorial für Fortgeschrittene 3 – Implementierung der Spielelogik
Ein spielbares Spiel erstellen
Da wir nun über alle Spielkomponenten verfügen, können wir die Spiellogik hinzufügen, die festlegt, wie ein Spieler mit den Blöcken interagiert und das Spiel spielt, bis es gewonnen oder verloren ist.
Dazu haben wir die folgenden Funktionen zu „ samegame.js “ hinzugefügt:
handleClick(x,y)floodFill(xIdx,yIdx,type)shuffleDown()victoryCheck()floodMoveCheck(xIdx, yIdx, type)
Da es sich hierbei um ein Tutorial zu QML und nicht zum Spieldesign handelt, werden wir im Folgenden nur auf „ handleClick() “ und „ victoryCheck() “ eingehen, da diese direkt mit den QML-Typen interagieren. Beachten Sie, dass die Spielelogik hier zwar in JavaScript geschrieben ist, sie jedoch auch in C++ hätte geschrieben und dann für QML verfügbar gemacht werden können.
Mausklick-Interaktion aktivieren
Um die Anbindung des JavaScript-Codes an die QML-Typen zu vereinfachen, haben wir dem „ samegame.qml “ ein Element namens „ gameCanvas “ hinzugefügt. Es ersetzt den Hintergrund als das Element, das die Blöcke enthält. Außerdem akzeptiert es Mauseingaben des Benutzers. Hier ist der Code für das Element:
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)
}
}Das Element „ gameCanvas “ hat genau die Größe des Spielbretts und verfügt über eine Eigenschaft „ score “ sowie eine „ MouseArea “ zur Verarbeitung von Mausklicks. Die Blöcke werden nun als seine untergeordneten Elemente erstellt, und seine Abmessungen werden zur Bestimmung der Spielfeldgröße herangezogen, sodass sich die Anwendung an die verfügbare Bildschirmgröße anpasst. Da seine Größe an ein Vielfaches von ` blockSize` gebunden ist, wurde ` blockSize ` aus ` samegame.js ` herausgenommen und als QML-Eigenschaft in ` samegame.qml ` verschoben. Beachten Sie, dass weiterhin über das Skript darauf zugegriffen werden kann.
Beim Anklicken ruft „ MouseArea “ die Funktion „ handleClick() “ in „ samegame.js “ auf, die ermittelt, ob der Klick des Spielers zum Entfernen von Blöcken führen soll, und „ gameCanvas.score “ bei Bedarf mit dem aktuellen Punktestand aktualisiert. Hier ist die Funktion „ 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();
}Beachten Sie, dass Sie keine Bindung zu „ score “ herstellen könnten, wenn es sich dabei um eine globale Variable in der Datei „ samegame.js “ handeln würde. Sie können nur Bindungen zu QML-Eigenschaften herstellen.
Aktualisieren der Punktzahl
Wenn der Spieler auf einen Block klickt und damit „ handleClick() “ auslöst, ruft „ handleClick() “ außerdem „ victoryCheck() “ auf, um den Punktestand zu aktualisieren und zu prüfen, ob der Spieler das Spiel abgeschlossen hat. Hier ist der Code für „ 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);
}Dadurch wird der Wert von „ gameCanvas.score “ aktualisiert und ein „Game Over“-Dialogfeld angezeigt, wenn das Spiel beendet ist.
Der „Game Over“-Dialog wird mithilfe eines Typs „ Dialog “ erstellt, der in „ Dialog.qml “ definiert ist. Hier ist der Code für „ Dialog.qml “. Beachten Sie, wie er so konzipiert ist, dass er imperativ aus der Skriptdatei heraus über die Funktionen und Signale verwendet werden kann:
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();
}
}Und so wird er in der Hauptdatei „ samegame.qml “ verwendet:
Dialog {
id: dialog
anchors.centerIn: parent
z: 100
}Wir weisen dem Dialog den Wert „ z “ von 100 zu, um sicherzustellen, dass er über unseren anderen Komponenten angezeigt wird. Der Standardwert für „ z “ eines Elements ist 0.
Ein Hauch von Farbe
Es macht nicht viel Spaß, „Same Game“ zu spielen, wenn alle Blöcke dieselbe Farbe haben. Deshalb haben wir die Funktion „ createBlock() “ in „ samegame.js “ so angepasst, dass bei jedem Aufruf zufällig ein anderer Blocktyp (entweder rot, grün oder blau) erzeugt wird. Auch „ Block.qml “ wurde so geändert, dass jeder Block je nach Typ ein anderes Bild enthält:
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";
}
}
}Ein funktionierendes Spiel
Jetzt haben wir ein funktionierendes Spiel! Die Blöcke lassen sich anklicken, der Spieler kann Punkte erzielen, und das Spiel kann enden (und dann kann man ein neues starten). Hier ist ein Screenshot dessen, was bisher erreicht wurde:

So sieht samegame.qml jetzt aus:
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?"
}
}
}Das Spiel funktioniert, ist aber im Moment noch etwas langweilig. Wo sind die flüssigen animierten Übergänge? Wo sind die Highscores? Wenn du ein QML-Experte wärst, hättest du diese Funktionen bereits in der ersten Iteration implementieren können, aber in diesem Tutorial wurden sie für das nächste Kapitel aufgehoben – dort wird deine Anwendung erst richtig lebendig!
© 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.