QML 고급 튜토리얼 4 - 마무리 작업
게임에 개성을 더하기
이제 게임을 더욱 생동감 있게 만들기 위해 두 가지 작업을 수행할 것입니다. 블록에 애니메이션을 적용하고 최고 점수 시스템을 추가하는 것입니다.
새로운 블록 애니메이션을 반영하기 위해, ` Block.qml ` 파일의 이름을 ` BoomBlock.qml`로 변경합니다.
블록 이동 애니메이션 적용
먼저 블록이 부드럽게 움직이도록 애니메이션을 적용해 보겠습니다. QML에는 부드러운 움직임을 구현하는 여러 가지 방법이 있으며, 이번에는 Behavior 타입을 사용하여 SpringAnimation 를 추가할 것입니다. BoomBlock.qml 에서, SpringAnimation 동작을 x 및 y 속성에 적용하여 블록이 지정된 위치(값은 samegame.js 에서 설정됨)를 향해 스프링처럼 움직이며 애니메이션을 따르도록 합니다. 다음은 BoomBlock.qml 에 추가된 코드입니다:
property bool spawned: false
Behavior on x {
enabled: block.spawned;
SpringAnimation{ spring: 2; damping: 0.2 }
}
Behavior on y {
SpringAnimation{ spring: 2; damping: 0.2 }
}spring 및 damping 값을 변경하면 애니메이션의 탄성 효과를 조절할 수 있습니다.
enabled: spawned 설정은 samegame.js 의 createBlock() 에서 설정된 spawned 값을 참조합니다. 이를 통해 x 의 SpringAnimation 가 createBlock() 가 블록을 올바른 위치에 배치한 후에만 활성화되도록 보장합니다. 그렇지 않으면 게임이 시작될 때 블록들이 위에서 줄을 지어 떨어지는 대신 (0,0) 모서리 밖으로 미끄러져 나가게 됩니다. ( enabled: spawned 를 주석 처리해 보고 직접 확인해 보세요.)
블록 불투명도 변화 애니메이션 적용
다음으로, 부드러운 퇴장 애니메이션을 추가하겠습니다. 이를 위해 속성 변경 시 기본 애니메이션을 지정할 수 있는 ` Behavior ` 유형을 사용할 것입니다. 이 경우, 블록(Block)의 투명도( opacity )가 변경될 때, 완전히 보이거나 보이지 않는 상태로 갑작스럽게 전환되는 대신 투명도 값이 서서히 나타나고 사라지도록 애니메이션을 적용하겠습니다. 이를 위해 ` BoomBlock.qml` 파일 내 ` Image ` 타입의 ` opacity ` 속성에 ` Behavior `을 적용합니다:
Image {
id: img
anchors.fill: parent
source: {
if (block.type == 0)
return "pics/redStone.png";
else if (block.type == 1)
return "pics/blueStone.png";
else
return "pics/greenStone.png";
}
opacity: 0
Behavior on opacity {
NumberAnimation { properties:"opacity"; duration: 200 }
}
}opacity: 0 는 블록이 처음 생성될 때 투명함을 의미합니다. 블록을 생성하고 소멸할 때 samegame.js 에서 불투명도를 설정할 수도 있지만, 대신 상태를 사용할 것입니다. 이는 다음에 추가할 애니메이션에 유용하기 때문입니다. 먼저, BoomBlock.qml 의 루트 타입에 다음과 같은 상태를 추가합니다:
property bool dying: false
states: [
State{ name: "AliveState"; when: spawned == true && dying == false
PropertyChanges { target: img; opacity: 1 }
},
State{ name: "DeathState"; when: dying == true
PropertyChanges { target: img; opacity: 0 }
}
]이제 블록 애니메이션 구현 시 이미 ` spawned `를 `true`로 설정해 두었기 때문에, 블록이 자동으로 페이드 인됩니다. 페이드 아웃을 위해 블록이 소멸될 때(` floodFill() ` 함수 내에서) 불투명도를 0으로 설정하는 대신 ` dying `를 `true`로 설정합니다.
입자 효과 추가
마지막으로, 블록이 파괴될 때 멋진 파티클 효과를 추가해 보겠습니다. 이를 위해 먼저 BoomBlock.qml 에 다음과 같이 ParticleSystem 을 추가합니다:
ParticleSystem {
id: sys
anchors.centerIn: parent
ImageParticle {
// ![0]
source: {
if (block.type == 0)
return "pics/redStar.png";
else if (block.type == 1)
return "pics/blueStar.png";
else
return "pics/greenStar.png";
}
rotationVelocityVariation: 360
// ![0]
}
Emitter {
id: particles
anchors.centerIn: parent
emitRate: 0
lifeSpan: 700
velocity: AngleDirection {angleVariation: 360; magnitude: 80; magnitudeVariation: 40}
size: 16
}
}이를 완전히 이해하려면 ‘ Qt Quick 입자 시스템 사용’ 문서를 읽어보셔야 하지만, 입자가 정상적으로 방출되지 않도록 emitRate 가 0으로 설정되어 있다는 점을 유의해야 합니다. 또한, dying 상태를 확장하여 입자 유형에 대해 burst() 메서드를 호출함으로써 입자 폭발 효과를 생성합니다. 이제 상태의 코드는 다음과 같습니다:
states: [
State {
name: "AliveState"
when: block.spawned == true && block.dying == false
PropertyChanges { img.opacity: 1 }
},
State {
name: "DeathState"
when: block.dying == true
StateChangeScript { script: particles.burst(50); }
PropertyChanges { img.opacity: 0 }
StateChangeScript { script: block.destroy(1000); }
}
]이제 게임은 플레이어의 모든 동작에 미묘한(혹은 그다지 미묘하지 않은) 애니메이션이 추가되어 아름답게 움직입니다. 기본 테마 적용을 보여주기 위해 다른 이미지 세트를 사용한 최종 결과는 아래와 같습니다:
여기서 테마 변경은 단순히 블록 이미지를 교체함으로써 이루어집니다. 이는 런타임에 ` Image `( source ) 속성을 변경하여 수행할 수 있으므로, 더 어려운 과제로는 서로 다른 이미지를 가진 테마 간을 전환하는 버튼을 추가해 볼 수 있습니다.
최고 점수 기록 관리
게임에 추가하고 싶은 또 다른 기능은 최고 점수를 저장하고 불러오는 방법입니다.
이를 위해 게임이 끝날 때 대화 상자를 표시하여 플레이어의 이름을 요청하고, 이를 최고 점수 표에 추가하도록 하겠습니다. 이를 위해서는 Dialog.qml 에 몇 가지 변경이 필요합니다. Text 유형 외에도, 이제 키보드 텍스트 입력을 받기 위한 TextInput 자식 항목이 추가되었습니다:
Rectangle {
id: container
...
TextInput {
id: textInput
anchors { verticalCenter: parent.verticalCenter; left: dialogText.right }
width: 80
text: ""
onAccepted: container.hide() // close dialog when Enter is pressed
}
...
}또한 ` showWithInput() ` 함수도 추가하겠습니다. 이 함수가 ` show()` 대신 호출될 때만 텍스트 입력란이 표시됩니다. 대화 상자가 닫히면 ` closed() ` 신호를 발생시키며, 다른 타입들은 ` inputText ` 속성을 통해 사용자가 입력한 텍스트를 가져올 수 있습니다:
Rectangle {
id: container
property string inputText: textInput.text
signal closed
function show(text) {
dialogText.text = text;
container.opacity = 1;
textInput.opacity = 0;
}
function showWithInput(text) {
show(text);
textInput.opacity = 1;
textInput.focus = true;
textInput.text = ""
}
function hide() {
textInput.focus = false;
container.opacity = 0;
container.closed();
}
...
}이제 이 대화 상자를 ` samegame.qml`에서 사용할 수 있습니다:
Dialog {
id: nameInputDialog
anchors.centerIn: parent
z: 100
onClosed: {
if (nameInputDialog.inputText != "")
SameGame.saveHighScore(nameInputDialog.inputText);
}
}대화 상자가 closed 신호를 발생시키면, samegame.js 에 있는 새로운 saveHighScore() 함수를 호출합니다. 이 함수는 최고 점수를 로컬 SQL 데이터베이스에 저장하고, 가능한 경우 온라인 데이터베이스에도 점수를 전송합니다.
nameInputDialog 는 samegame.js 의 victoryCheck() 함수에서 활성화됩니다:
function victoryCheck() {
...
//Check whether game has finished
if (deservesBonus || !(floodMoveCheck(0, maxRow - 1, -1))) {
gameDuration = new Date() - gameDuration;
nameInputDialog.showWithInput("You won! Please enter your name: ");
}
}오프라인으로 최고 점수 저장하기
이제 High Scores 테이블을 실제로 저장하는 기능을 구현해야 합니다.
다음은 파일 내 samegame.js 함수에 정의된 saveHighScore() 함수입니다:
function saveHighScore(name) {
if (gameCanvas.score == 0)
return;
if (scoresURL != "")
sendHighScore(name);
var db = Sql.LocalStorage.openDatabaseSync("SameGameScores", "1.0", "Local SameGame High Scores", 100);
var dataStr = "INSERT INTO Scores VALUES(?, ?, ?, ?)";
var data = [name, gameCanvas.score, maxColumn + "x" + maxRow, Math.floor(gameDuration / 1000)];
db.transaction(function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS Scores(name TEXT, score NUMBER, gridSize TEXT, time NUMBER)');
tx.executeSql(dataStr, data);
var rs = tx.executeSql('SELECT * FROM Scores WHERE gridSize = "12x17" ORDER BY score desc LIMIT 10');
var r = "\nHIGH SCORES for a standard sized grid\n\n"
for (var i = 0; i < rs.rows.length; i++) {
r += (i + 1) + ". " + rs.rows.item(i).name + ' got ' + rs.rows.item(i).score + ' points in ' + rs.rows.item(i).time + ' seconds.\n';
}
dialog.show(r);
});
}먼저, 최고 점수를 온라인 데이터베이스로 전송할 수 있는 경우 sendHighScore() (아래 섹션에서 설명)를 호출합니다.
그런 다음, ` Local Storage API `를 사용하여 이 애플리케이션 전용의 영구적인 SQL 데이터베이스를 관리합니다. ` openDatabaseSync() `를 사용하여 최고 점수용 오프라인 저장소 데이터베이스를 생성하고, 이를 저장하는 데 사용할 데이터와 SQL 쿼리를 준비합니다. 오프라인 저장소 API는 데이터 조작 및 검색을 위해 SQL 쿼리를 사용하며, ` db.transaction() ` 호출에서는 세 개의 SQL 쿼리를 사용하여 데이터베이스를 초기화하고(필요한 경우), 이후 최고 점수를 추가 및 조회합니다. 반환된 데이터를 사용하기 위해, 반환된 각 행을 한 줄씩 담은 문자열로 변환한 뒤, 해당 문자열이 포함된 대화 상자를 표시합니다.
이는 최고 점수를 로컬에 저장하고 표시하는 한 가지 방법일 뿐, 유일한 방법은 아닙니다. 더 복잡한 대안으로는 최고 점수 대화 상자 컴포넌트를 생성하고, 처리 및 표시를 위해 그 컴포넌트에 결과를 전달하는 방법( Dialog 를 재사용하는 대신)이 있습니다. 이렇게 하면 최고 점수를 더 잘 표현할 수 있는, 테마 적용이 용이한 대화 상자를 만들 수 있습니다. QML이 C++ 애플리케이션의 UI인 경우, 점수를 C++ 함수에 전달하여 SQL을 사용하지 않는 간단한 형식이나 다른 SQL 데이터베이스 등 다양한 방법으로 로컬에 저장할 수도 있습니다.
온라인으로 최고 점수 저장하기
최고 점수를 로컬에 저장하는 방법은 이미 살펴보았지만, 웹 기반의 최고 점수 저장 기능을 QML 애플리케이션에 통합하는 것도 쉽습니다. 여기서 구현한 방식은 매우 간단합니다. 최고 점수 데이터를 어딘가의 서버에서 실행 중인 PHP 스크립트로 전송하면, 해당 서버가 이를 저장하고 방문자에게 표시합니다. 또한, 점수를 포함하고 표시하는 XML 또는 QML 파일을 동일한 서버에서 요청할 수도 있지만, 이는 이 튜토리얼의 범위를 벗어납니다. 여기서 사용하는 PHP 스크립트는 examples 디렉터리에 있습니다.
플레이어가 이름을 입력한 경우, 해당 데이터를 웹 서비스로 전송할 수 있습니다.
플레이어가 이름을 입력하면, samegame.js 에 있는 다음 코드를 사용하여 데이터를 서비스로 전송합니다:
function sendHighScore(name) {
var postman = new XMLHttpRequest()
var postData = "name=" + name + "&score=" + gameCanvas.score + "&gridSize=" + maxColumn + "x" + maxRow + "&time=" + Math.floor(gameDuration / 1000);
postman.open("POST", scoresURL, true);
postman.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
postman.onreadystatechange = function() {
if (postman.readyState == postman.DONE) {
dialog.show("Your score has been uploaded.");
}
}
postman.send(postData);
}이 코드의 XMLHttpRequest는 표준 브라우저 JavaScript에서 볼 수 있는 것과 동일하며, XMLHttpRequest() 에서와 마찬가지로 웹 서비스로부터 XML 또는 QML을 동적으로 가져와 최고 점수를 표시하는 데 사용할 수 있습니다. 이 경우 응답에 대해서는 신경 쓸 필요가 없습니다. 단순히 최고 점수 데이터를 웹 서버로 전송하기만 하면 됩니다. 만약 QML 파일(또는 QML 파일의 URL)이 반환되었다면, 블록을 다룰 때와 거의 동일한 방식으로 이를 인스턴스화할 수 있습니다.
웹 기반 데이터에 접근하고 전송하는 또 다른 방법은 이 목적을 위해 설계된 QML 유형을 사용하는 것입니다. ` XmlListModel `를 사용하면 QML 애플리케이션에서 RSS와 같은 XML 기반 데이터를 매우 쉽게 가져와 표시할 수 있습니다.
이것으로 끝입니다!
이 튜토리얼을 따라오면서 QML로 모든 기능을 갖춘 애플리케이션을 작성하는 방법을 확인하셨습니다:
- 다음 명령어로 애플리케이션을 빌드하세요 QML types
- JavaScript 코드를 사용하여 애플리케이션 로직을 추가하고
- Behaviors 와 상태를 사용하여 애니메이션 추가
- QtQuick.LocalStorage 나 XMLHttpRequest 등을 사용하여 애플리케이션 데이터를 영구적으로 저장하기
이 튜토리얼에서 다룰 수 없었던 QML에 대해 배울 내용은 아직 많이 남아 있습니다. 모든 예제와 문서를 확인하여 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.