このページでは

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を追加しました。これは、ブロックを含むアイテムとして背景に取って代わるものです。また、ユーザーからのマウス入力も受け付けます。アイテムのコードは以下の通りです:

        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のエキスパートであれば、最初のイテレーションでこれらを実装できたかもしれませんが、このチュートリアルでは、アプリケーションが生き生きと動き出す次の章まで、これらの実装は後回しにされています!

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

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