QML上級者向けチュートリアル4 - 最後の仕上げ

派手さを加える

ブロックをアニメーションさせることと、ハイスコア・システムを追加することです。

新しいブロックアニメーションを想定して、Block.qml ファイルの名前をBoomBlock.qml に変更しました。

ブロックの動きをアニメーションさせる

まず、ブロックの動きをアニメーションさせます。QMLには流動的な動きを追加するためのメソッドがいくつかありますが、今回はBehavior タイプを使ってSpringAnimation を追加します。BoomBlock.qml では、xy プロパティに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 }
}

springdamping の値は、アニメーションのバネのような効果を変更するために変更することができます。

enabled: spawned の設定は、createBlock() からsamegame.js に設定されるspawned の値を参照する。これにより、createBlock() がブロックを正しい位置にセットした後にのみ、xSpringAnimation が有効になります。そうしないと、ゲームが始まったときに、ブロックが列をなして上から落ちてくるのではなく、角(0,0)から滑り落ちてしまいます。(enabled: spawned をコメントアウトしてみてください。)

ブロックの不透明度の変化のアニメーション

次に、スムーズな終了アニメーションを追加します。これには、Behavior タイプを使います。このタイプでは、プロパティが変更されたときのデフォルトのアニメーションを指定できます。こ の場合、 ブ ロ ッ ク のopacity が変化 し た と き に、 その不透明度の値をアニ メ テーシ ョ ン し て、 急に完全可視 と 不可視 と を切 り 替え る のではな く 、 徐々に フ ェ ード イ ン ・ フ ェ ード ア ウ ト す る よ う に し ま し ょ う 。こ れを実現す る には、BoomBlock.qmlImage 型の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.qmlParticleSystem を追加します:

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 をゼロに設定し、パーティクルが正常に放出されないようにすることが重要です。また、dying Stateを拡張し、パーティクルタイプの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 プロパティを変更することで、実行時に行うことができます。さらなる挑戦として、異なる画像でテーマを切り替えるボタンを追加することもできます。

ハイスコア表の保持

ゲームに追加したいもう1つの機能は、ハイスコアを保存して取り出す方法です。

そのために、ゲーム終了時にダイアログを表示してプレイヤーの名前を要求し、ハイスコア表に追加する。これには、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データベースにローカルに保存し、可能であればオンラインデータベースにもスコアを送信します。

nameInputDialogsamegame.jsvictoryCheck() 関数で起動されます:

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: ");
    }
}
ハイスコアをオフラインで保存する

次に、ハイスコア・テーブルを実際に保存する機能を実装する必要があります。

以下はsamegame.jssaveHighScore() 関数です:

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データベースなど、さまざまな方法でローカルに保存することもできます。

ハイスコアをオンラインに保存する

ハイスコアをローカルに保存する方法について見てきましたが、ウェブ対応のハイスコア保存を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() と同じです。同じように使用して、WebサービスからXMLまたはQMLを動的に取得し、ハイスコアを表示することができます。この場合、レスポンスは気にしません。ハイスコアのデータをウェブサーバーにポストするだけです。もしQMLファイル(またはQMLファイルへのURL)を返すのであれば、ブロックと同じようにインスタンス化することができます。

ウェブベースのデータにアクセスして送信する別の方法として、この目的のために設計されたQMLタイプを使うこともできます。XmlListModel 、RSSのようなXMLベースのデータをQMLアプリケーションで取得したり表示したりするのがとても簡単になります。

以上です!

このチュートリアルに従うことで、QMLで完全な機能を持つアプリケーションを書くことができることがお分かりいただけたと思います:

このチュートリアルで紹介しきれなかったQMLの魅力はまだまだたくさんあります。QMLでできることをすべて見るために、すべてのサンプルとドキュメントをチェックしてください!

サンプルプロジェクト @ code.qt.io

© 2025 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.