QML 고급 튜토리얼 3 - 게임 로직 구현
플레이 가능한 게임 만들기
이제 모든 게임 구성 요소가 준비되었으므로, 플레이어가 블록과 상호작용하고 승패가 결정될 때까지 게임을 진행하는 방식을 규정하는 게임 로직을 추가할 수 있습니다.
이를 위해 ` samegame.js`에 다음과 같은 함수들을 추가했습니다:
handleClick(x,y)floodFill(xIdx,yIdx,type)shuffleDown()victoryCheck()floodMoveCheck(xIdx, yIdx, type)
이 튜토리얼은 게임 디자인이 아닌 QML에 관한 것이므로, 아래에서는 QML 타입과 직접 연동되는 ` handleClick() `와 ` victoryCheck() `에 대해서만 다룰 것입니다. 여기서 게임 로직은 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 값을 업데이트하고, 게임이 종료된 경우 "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.