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.qml
にgameCanvas
というアイテムを追加しました。また、ユーザーからのマウス入力を受け付けます。以下がそのアイテムコードです:
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です。
色のダッシュ
すべてのブロックが同じ色だと、セイムゲームをプレイするのが楽しくありません。そこで、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の専門家であれば、最初の反復でこれらを書くことができたかもしれませんが、このチュートリアルでは、それらは次の章まで保存されています!
©2024 The Qt Company Ltd. 本書に含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。