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