Tutorial avanzado de QML 3 - Implementación de la lógica del juego
Crear un juego jugable
Ahora que tenemos todos los componentes del juego, podemos añadir la lógica del juego que dicta cómo un jugador interactúa con los bloques y juega la partida hasta que la gana o la pierde.
Para ello, hemos añadido las siguientes funciones a samegame.js:
handleClick(x,y)floodFill(xIdx,yIdx,type)shuffleDown()victoryCheck()floodMoveCheck(xIdx, yIdx, type)
Como este es un tutorial sobre QML, no sobre diseño de juegos, sólo hablaremos de handleClick() y victoryCheck(), ya que interactúan directamente con los tipos QML. Ten en cuenta que aunque la lógica del juego está escrita en JavaScript, podría haberse escrito en C++ y luego expuesto a QML.
Activación de la interacción con el clic del ratón
Para facilitar que el código JavaScript interactúe con los tipos QML, hemos añadido un elemento llamado gameCanvas a samegame.qml. Sustituye al fondo como elemento que contiene los bloques. También acepta la entrada del ratón del usuario. Este es el código del elemento:
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) } }
El elemento gameCanvas es del tamaño exacto del tablero, y tiene una propiedad score y una MouseArea para manejar los clics del ratón. Los bloques se crean ahora como sus hijos, y sus dimensiones se utilizan para determinar el tamaño del tablero para que la aplicación se adapte al tamaño de pantalla disponible. Dado que su tamaño está ligado a un múltiplo de blockSize, blockSize se ha movido fuera de samegame.js y dentro de samegame.qml como una propiedad QML. Nótese que todavía se puede acceder a ella desde el script.
Cuando se hace clic, MouseArea llama a handleClick() en samegame.js, que determina si el clic del jugador debe hacer que se elimine algún bloque, y actualiza gameCanvas.score con la puntuación actual si es necesario. Aquí está la función 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();
}Tenga en cuenta que si score fuera una variable global en el archivo samegame.js no podría vincularse a ella. Sólo se puede enlazar a propiedades QML.
Actualización de la puntuación
Cuando el jugador hace clic en un bloque y activa handleClick(), handleClick() también llama a victoryCheck() para actualizar la puntuación y comprobar si el jugador ha completado el juego. Este es el código de 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);
}Esto actualiza el valor de gameCanvas.score y muestra un diálogo "Game Over" si el juego ha terminado.
El diálogo "Game Over" se crea usando un tipo Dialog que se define en Dialog.qml. Aquí está el código Dialog.qml. Observe cómo está diseñado para ser utilizado imperativamente desde el archivo de script, a través de las funciones y señales:
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(); } }
Y así es como se utiliza en el archivo principal samegame.qml:
Dialog { id: dialog anchors.centerIn: parent z: 100 }
Damos al diálogo un valor z de 100 para asegurarnos de que se muestra encima de nuestros otros componentes. El valor por defecto de z para un elemento es 0.
Un toque de color
No es muy divertido jugar al Mismo Juego si todos los bloques son del mismo color, así que hemos modificado la función createBlock() en samegame.js para crear aleatoriamente un tipo diferente de bloque (ya sea rojo, verde o azul) cada vez que se llama. Block.qml también ha cambiado para que cada bloque contenga una imagen diferente dependiendo de su tipo:
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"; } } }
Un juego que funciona
Ya tenemos un juego que funciona. Se puede hacer clic en los bloques, el jugador puede marcar, y el juego puede terminar (y entonces puedes empezar uno nuevo). Aquí hay una captura de pantalla de lo que se ha logrado hasta ahora:

Este es el aspecto actual de 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?" } } }
El juego funciona, pero ahora mismo es un poco aburrido. ¿Dónde están las suaves transiciones animadas? ¿Dónde están las puntuaciones altas? Si fueras un experto en QML podrías haber escrito esto en la primera iteración, pero en este tutorial se han guardado hasta el próximo capítulo - ¡donde tu aplicación cobra vida!
© 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.