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 から移動され、samegame.qml に 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

©2024 The Qt Company Ltd. 本書に含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。