QML 上級チュートリアル 4 - 仕上げ
ゲームに彩りを加える
ここでは、ゲームをより盛り上げるために、ブロックのアニメーションとハイスコアシステムの追加という2つの作業を行います。
新しいブロックのアニメーションに備えて、Block.qml ファイルの名前をBoomBlock.qml に変更します。
ブロックの動きにアニメーションを付ける
まず、ブロックが滑らかに動くようにアニメーションを適用します。QMLには滑らかな動きを実現するためのメソッドがいくつかありますが、今回はBehavior タイプを使用してSpringAnimation を追加します。BoomBlock.qml では、x およびy プロパティにSpringAnimation 挙動を適用し、ブロックが指定された位置(その値は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 型を使用します。これにより、プロパティが変更された際にデフォルトのアニメーションを指定できるようになります。 この場合、ブロックの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 ` で不透明度を設定することも可能ですが、代わりに「状態(states)」を使用します。これは、これから追加する次のアニメーションに役立つためです。まず、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() の呼び出しでは、3つのSQLクエリを使用してデータベースを初期化(必要な場合)し、その後、ハイスコアを追加および取得します。返されたデータを使用するには、返された行ごとに1行ずつ文字列に変換し、その文字列を含むダイアログを表示します。
これはハイスコアをローカルに保存・表示する一つの方法ですが、もちろん唯一の方法ではありません。より複雑な代替案としては、ハイスコア用ダイアログコンポーネントを作成し、処理と表示のために結果をそこに渡す方法があります(Dialog を再利用する代わりに)。これにより、ハイスコアをより効果的に表示できる、テーマ対応性の高いダイアログを実現できます。 もしQMLがC++アプリケーションのUIであるならば、スコアをC++関数に渡して、SQLを使わないシンプルな形式や別のSQLデータベースなど、さまざまな方法でローカルに保存することも可能です。
ハイスコアのオンライン保存
ハイスコアをローカルに保存する方法については見てきましたが、Web対応のハイスコア保存機能をQMLアプリケーションに組み込むことも簡単です。ここで実装した方法は非常にシンプルです。ハイスコアデータをどこかのサーバー上で実行されているPHPスクリプトに送信し、そのサーバーがデータを保存して訪問者に表示します。 また、同じサーバーからスコアを含むXMLやQMLファイルをリクエストして表示することも可能ですが、それは本チュートリアルの範囲を超えています。ここで使用しているPHPスクリプトは、examples ディレクトリにあります。
プレイヤーが名前を入力した場合は、そのデータをWebサービスに送信できます。
プレイヤーが名前を入力した場合、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() にあるものと同様です。これを使用して、Web サービスから XML や QML を動的に取得し、ハイスコアを表示することができます。 このケースではレスポンスについては気にせず、単にハイスコアデータをWebサーバーにPOSTするだけです。もしQMLファイル(またはQMLファイルへのURL)が返されてきた場合は、ブロックの場合とほぼ同じ方法でそれをインスタンス化することができます。
Web ベースのデータにアクセスして送信する別の方法として、この目的のために設計された QML タイプを使用する方法があります。XmlListModel を使用すると、RSS などの XML ベースのデータを QML アプリケーションで非常に簡単に取得・表示することができます。
以上です!
このチュートリアルを通じて、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.