QML上級者向けチュートリアル3 - ゲームロジックの実装

プレイ可能なゲームを作る

ゲームコンポーネントがそろったので、次はゲームロジックを追加します。

そのために、samegame.js に以下の関数を追加します:

  • handleClick(x,y)
  • floodFill(xIdx,yIdx,type)
  • shuffleDown()
  • victoryCheck()
  • floodMoveCheck(xIdx, yIdx, type)

これはQMLについてのチュートリアルであって、ゲームデザインについてのチュートリアルではないので、handleClick()victoryCheck() については、QMLの型と直接インタフェースをとるため、以下で説明します。なお、ここでのゲームロジックはJavaScriptで記述されていますが、C++で記述してQMLに公開することも可能です。

マウスクリックによるインタラクションの有効化

JavaScript コードが QML タイプとのインタフェースを容易にするために、samegame.qmlgameCanvas というアイテムを追加しました。また、ユーザーからのマウス入力を受け付けます。以下がそのアイテムコードです:

        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 の倍数にバインドされているので、blockSizesamegame.js から移動され、QML プロパティとしてsamegame.qml に追加されました。なお、スクリプトからのアクセスは可能です。

クリックされると、MouseArea は、samegame.jshandleClick() を呼び出します。 は、プレイヤーのクリックによってブロックが削除されるべきかどうかを判断し、必要であれば、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();
}

scoresamegame.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です。

色のダッシュ

すべてのブロックが同じ色だと、セイムゲームをプレイするのが楽しくありません。そこで、samegame.jscreateBlock() 関数を変更し、呼び出されるたびに、ランダムに異なるタイプのブロック(赤、緑、青のいずれか)を作成するようにしました。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

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