Slide 1

Slide 1 text

QML で Flappy Bird を 作ろう 1 / 59

Slide 2

Slide 2 text

QML の紹介 UI記述用言語 Component を組み合わせてUIを組み上げていく Animation, State などを 宣言的に 記述できる プロパティバインディングによる状態の管理 書いたアプリは Windows, Mac, Linux, Android, iOS どこでも動 く QMLの日本語のイントロダクション QMLプログラミング入門へよ うこそ! — Getting started QML programming Japanese translation 2014.04.05 ドキュメント 2 / 59

Slide 3

Slide 3 text

QML 実行環境の用意 1. http://www.qt.io/download-open-source/ からQtのインス トーラをダウンロードし、インストールする. 2. https://goo.gl/NNWeq6 からZIPファイルをダウンロード し、解凍 3 / 59

Slide 4

Slide 4 text

QML編集環境の用意 Sublime TextではQMLパッケージをインストールするとシンタッ クスハイライトされます Qtをインストールされた方はqmlproject ファイルをQtCreatorで開 いてください 4 / 59

Slide 5

Slide 5 text

Flappy Bird を作ろう 5 / 59

Slide 6

Slide 6 text

こんなゲーム 参考: あまりの難易度で人気爆発のFlappy Bird。制作者によるゲ ーム3本が一気にトップ10入り | TechCrunch Japan 6 / 59

Slide 7

Slide 7 text

サンプルのダウンロード https://goo.gl/1hSiDG からダウンロード /qml/main.qml をqmlscene.exe にドラッグ・アンド・ドロップして起 動. /step[2-5]/ はスライドの各ステップに対応しています./qml/ は完 成形のソースコードが入っています. 自分で一から作りたい場合は/qml/ と同じ階層に新しくフォルダを 作成してください. 7 / 59

Slide 8

Slide 8 text

8 / 59

Slide 9

Slide 9 text

9 / 59

Slide 10

Slide 10 text

Flappy Bird のつくりかた 1. リソース(鳥とか障害物の画像)を用意する 2. アプリケーションの基本部分を作る 3. 鳥を飛ばす 4. 障害物を作る・当たりを判定する 5. 鳥を作りこむ 6. ゲームのシーンを作る 10 / 59

Slide 11

Slide 11 text

Step1 リソースの用意 assets フォルダに用意してあります. 11 / 59

Slide 12

Slide 12 text

Step2 アプリケーションの基本部分を作る QMLのアプリで一番基本の部分を作る.main.qml に以下の内容を書 く. import QtQuick 2.2 import QtQuick.Controls 1.3 ApplicationWindow { title: "Flappy Bird" width: 288 height: 512 visible: true Image { anchors.fill: parent source: "../assets/bg_day.png" } } 12 / 59

Slide 13

Slide 13 text

13 / 59

Slide 14

Slide 14 text

QMLの基本文法 コンポーネントをネストして画面を作り上げる コンポーネントにはプロパティがある コンポーネントには親子関係がある 基本的に子の要素は親の内側にある この場合Image がApplicationWindow の子になっている 14 / 59

Slide 15

Slide 15 text

anchors について アンカーを用いて親に対する子の位置を決める. 15 / 59

Slide 16

Slide 16 text

anchors.fill: parent 子が親を覆う anchors.centerIn: parent 子が親の中央に位置する anchors.right: parent.right 親の右端に子が位置する anchors.right: item1.left item1の左に位置する(右にitem1がく る) 16 / 59

Slide 17

Slide 17 text

マージンについて コンポーネント同士の間隔を開けたい時,マージンを使う. 17 / 59

Slide 18

Slide 18 text

anchors.fill: parent; anchors.margins: 50 四方の間隔を50px空けて 親の中央に位置する anchors.right: parent.right; anchors.rightMargin: 50 間隔を50px空け て親の右端に位置する anchors.right: item1.left; anchors.rightMargin: 50 item1との間隔を 50px空ける 18 / 59

Slide 19

Slide 19 text

問題 19 / 59

Slide 20

Slide 20 text

import QtQuick 2.2 Rectangle { width: 500; height: 500 color: "red" Rectangle { width: 200; height: 200 color: "blue" anchors { left: parent.left bottom: parent.bottom leftMargin: 50 bottomMargin: 100 } } } リサイズしてもアンカーで定義した位置関係が変わらないことが 確認できる 20 / 59

Slide 21

Slide 21 text

QMLにおけるコンポーネントの切り分け ゲームに含まれる全てのコンポーネントを一つのファイルに記述 すると管理が大変なので,複数のコンポーネントに切り分けてい く. main.qml と同じ階層にGame.qml を作成する. import QtQuick 2.2 Item { id: game Image { id: land anchors.bottom: parent.bottom width: sourceSize.width * 2 height: sourceSize.height * 2 source: "../assets/land.png" } } 21 / 59

Slide 22

Slide 22 text

Item は全ての目に見えるコンポーネントの派生元.全ての目に見 えるコンポーネントはItem から派生している. QMLではこのように,もともとあるコンポーネントから派生した コンポーネントを作り出すことができる(オブジェクト指向にお ける継承みたいな感じ).派生させるときに新たにプロパティを 追加したり,メソッドを追加したりもできる(オブジェクト指向 で継承させるときに変数や関数を追加するのと同じような感 じ). コンポーネントを切り分けておくと,一回定義したコンポーネン トを繰り返し使うことができるようになる. 例)ボタン // Button.qml Rectangle { color: "gray"; width: 150; height: 90 Text { anchors.centerIn: parent; text: "label" } MouseArea { anchors.fill: parent; onClicked: ... } } 22 / 59

Slide 23

Slide 23 text

作成したGameコンポーネントをゲームに追加する.main.qml に以 下を子として追加する. Game { id: game anchors.fill: parent Keys.onEscapePressed: close() focus: true } 23 / 59

Slide 24

Slide 24 text

Step3 鳥を飛ばす 1. 鳥を作る 2. ぴょんぴょんさせる 24 / 59

Slide 25

Slide 25 text

鳥を作ろう main.qml と同じ階層にEntity フォルダを作り,以下をEntity/Bird.qml として保存. import QtQuick 2.2 AnimatedSprite { id: bird width: 34; height: 24 property real v: 0 source: "../../assets/bird_yellow.png" frameDuration: 600 frameWidth: 17 frameHeight: 12 frameCount: 3 } AnimatedSprite については後で説明します. 25 / 59

Slide 26

Slide 26 text

ぴょんぴょんさせる スペースキーが押されたらぴょんぴょんするようにしたい. 時間,位置,速度,加速度の変数を用意して,一定時間ごとにそ れらの変数を更新する. 速度に重力加速度を足す,位置に速度を足す スペースキーを押されたら速度を変化させる じつに原始的 26 / 59

Slide 27

Slide 27 text

変数を追加する QMLではプロパティが変数にあたる. 以下をGame に追加. property int t: 0 // 時間 property real g: 0.5 // 重力加速度 property real pyonV: 8 // スペースキーを押した時に加えられる速度 27 / 59

Slide 28

Slide 28 text

一定時間ごとに処理を行う QMLのTimer コンポーネントを利用する.これは指定した時間ごと に指定された処理を行う. Timer { id: timer running: true interval: 33 // 33msecごとに処理を行う repeat: true onTriggered: { // 実行される処理 update() t++ // 時間の変数を更新 } } 28 / 59

Slide 29

Slide 29 text

鳥を落下させる 一定時間ごとに鳥を下方向に動かす. function update() { // fall down bird.v += g bird.y += bird.v } Bird { id: bird x: (parent.width - width) / 3 } y, v, g は下方向に正の値を取る. 29 / 59

Slide 30

Slide 30 text

ゲームを起動すると鳥が下に落ちていく様子が確認できる. 30 / 59

Slide 31

Slide 31 text

お気づきでしょうか,これはJavaScriptです. QMLにはJavaScript(ES5)のロジックを埋め込める.ゲームロジ ックはJavaScriptで記述していくことになる. QMLでは最初に紹介したアンカー以外にx y を指定してコンポー ネントを移動させることができる. QMLのid コンポーネントに名前を付けるにはid を使う.Bird コンポーネン トにbird というIDを付けておくと,例えば位置Yはbird.y というよ うに参照できる. 先ほどBird コンポーネントを作った時にv という実数型のプロパテ ィを作った.ここではGame からBird のv を参照し値を変更してい る. 31 / 59

Slide 32

Slide 32 text

プロパティに型が付いているのはなぜ? QMLはC++のクラスをベースに作られている.また,今回は紹介 しないがC++で定義したコンポーネントを利用することができた りする.端的に言えばC++との連携のために型付けが必要になっ ている. 32 / 59

Slide 33

Slide 33 text

スペースキーで速度を変える Keys.onSpacePressed: pyon() function pyon() { bird.v = -pyonV } キーボード入力に反応するにはKeys を使う. 33 / 59

Slide 34

Slide 34 text

Step4 障害物を作る・当たりを判定する パイプ(障害物)を作る パイプは上部と下部の間に隙間が開いていて,隙間には当たり判 定がない(というか隙間を通ればセーフという判定) ステージ上にパイプを適当に配置する パイプに鳥が衝突したらアウト 34 / 59

Slide 35

Slide 35 text

パイプを作る 隙間の上端と隙間部分の長さを指定することができるパイプのコ ンポーネント./step4/Entity/Pipe.qml を参照. ステージを作る パイプを何本かステージ上に用意しておく (clear ) 時間経過に伴い左にずれていく (update ) 画面の左端までパイプがずれて見えなくなったら右端に配置し直 す (update ) パイプの隙間は乱数で適当に設定する (setPipeRandom ) パイプと鳥が衝突していないか判定する (isClear ) パイプをくぐり抜けたか判定する (scoreUp ) /step4/Entity/Stage.qml を参照 35 / 59

Slide 36

Slide 36 text

Game にStage を配置して,これらの関数を呼び出す.start goOver を 追加,update を修正. 鳥の初期位置設定,ステージの初期化,フラグ変更 (start ) ステージ上の障害物や地面・天井に衝突すればゲーム終了 (update ) パイプの隙間を抜けたらスコアを上げる (update ) ゲームオーバーしたらフラグを戻し鳥を停止 (goOver ) /step4/Game.qml 参照 36 / 59

Slide 37

Slide 37 text

簡単な状態管理 isPlaying プロパティで鳥の飛行・静止を,score でスコアを管理す る property bool isPlaying: false property int score: 0 isPlaying がtrue の時のみタイマーを動かしたい. 37 / 59

Slide 38

Slide 38 text

プロパティバインディング プロパティを別のプロパティと連携して値を連動させる機能. QMLの一番の目玉機能といってもよい. import QtQuick 2.2 Rectangle { width: 300; height: 300 color: "red" Rectangle { width: parent.width / 2 height: parent.height / 3 color: "blue" } } ウィンドウをリサイズ(赤の四角の大きさを変える)と青の四角 の大きさが連動して変わることが確認できる. 38 / 59

Slide 39

Slide 39 text

Timer のrunning プロパティにisPlaying をバインディングする.start やgoOver でisPlaying を変更してやるとタイマーが連動する. Timer { id: timer running: isPlaying ... } 今まで使っていた: はバインディングの意味.JavaScriptコード内 で用いている= (代入)とは意味合いが異なる. 39 / 59

Slide 40

Slide 40 text

でばっぐ console.debug を使おう console.debug("update score: " + score); qmlscene を実行しているコンソールでログが確認できる. 40 / 59

Slide 41

Slide 41 text

Step5 鳥を作りこむ スペースキーを押した後と落下中で羽ばたきの速さを変えたい ゲームオーバーしたら死んだみたいな感じにしたい 羽ばたいているときは1-3の画像を使う,死んだら4つ目の画像. 41 / 59

Slide 42

Slide 42 text

AnimatedSprite Bird で使っていたもの.フレームが連続して並んだ画像を読み込 んで,アニメーションする. SpriteSequence AnimatedSprite に状態を管理できる機能を加えたもの. 42 / 59

Slide 43

Slide 43 text

import QtQuick 2.2 SpriteSequence { id: bird; width: 34; height: 24 property real v: 0 Sprite { name: 'flying' source: "../../assets/bird_yellow.png" frameDuration: 600 frameWidth: 17 frameHeight: 12 frameCount: 3 } Sprite { name: 'drafting' source: "../../assets/bird_yellow.png" frameDuration: 50 frameWidth: 17 frameHeight: 12 frameCount: 3 } Sprite { name: 'die' source: "../../assets/bird_yellow.png" frameX: 51 frameWidth: 17 frameHeight: 12 frameCount: 1 } } 43 / 59

Slide 44

Slide 44 text

Sprite が各状態に相当する.bird.jumpTo('flying') のようにして状態 遷移する. property bool isUpdrafting: false function start() { ... bird.jumpTo('flying') } function pyon() { bird.v = -pyonV isUpdrafting = true // updraftCount = 0; bird.jumpTo('drafting') } function update() { ... if (isUpdrafting && bird.v > 0) { isUpdrafting = false bird.jumpTo('flying') } } function goOver() { ... bird.jumpTo('die') } 44 / 59

Slide 45

Slide 45 text

45 / 59

Slide 46

Slide 46 text

Step6 ゲームのシーンを作る このゲームでは3つのシーンを用意する. 1. タイトル画面.ゲームをスタートするためのボタンがある. 2. ゲーム画面.さっき作ったやつ. 3. スコア確認画面.スコアボードとタイトルに戻るボタン. 46 / 59

Slide 47

Slide 47 text

こんな感じの構成にしたい. Game が各シーンを管理する. 子から親へとゲームの終了,タイトル画面に戻るなどの画面遷移 の要求を伝えるにはどうすればよいか? 47 / 59

Slide 48

Slide 48 text

シグナル・ハンドラ シグナルとは,QMLでイベントを通知するための仕組み.ハンド ラはイベントに反応して処理をするための仕組み. イベント(マウスでクリックされた,マウスが移動した,ウィン ドウのサイズが変わった,などなど)が発生した時に何らか処理 を行いたいが,プロパティバインディングではそれができない場 合,シグナル・ハンドラを使う. ハンドラにはJavaScriptのコードを指定できる. Button { onClicked: console.debug("clicked!") } 48 / 59

Slide 49

Slide 49 text

プロパティの変化を通知するシグナル プロパティを定義した際,自動的にシグナルが定義される.例え ばStep4で作成したisPlaying プロパティにもシグナルが割り当てら れている.この場合onIsPlayingChanged というシグナルが作成されて いる. onIsPlayingChanged: console.debug("isPlaying changed") 問題 プロパティバインディングの説明で作成したRectangle のonWidthChanged シグナルにデバッグメッセージを出力するハンド ラを割り当てて,ウィンドウをリサイズしてみよう. 49 / 59

Slide 50

Slide 50 text

任意の出来事を通知するシグナル プロパティの定義とは別にシグナルを作成することもできる. // ゲームオーバーを通知するためのシグナル signal gameOver(int score) シグナルには引数を指定できる.対応するハンドラはonGameOver と いう名称になる. onGameOver: { console.log("score: ", score); result.score = score; game.state = 'result'; } ハンドラ内で引数を利用することができる.プロパティの定義と 同様,引数には型付けが必要.(JavaScriptを使っていると不自然 に思うだろうけど) 50 / 59

Slide 51

Slide 51 text

タイトル画面 /step6/Scene/Title.qml 参照. スコアボード画面 /step6/Scene/Result.qml 参照. ゲームプレイ画面 /step6/Scene/Play.qml 参照.いままでGame.qml に書いていた内容をそ のまま移動している. 51 / 59

Slide 52

Slide 52 text

ボタンを作りたいときはQtQuick.Controls に含まれるButton を使うの が楽.ただスタイルの変更がちょっとややこしかったりする. Button { width: 104; height: 58 onClicked: beginPlay() style: ButtonStyle { background: Image { source: "../../assets/play.png" } } } 52 / 59

Slide 53

Slide 53 text

状態の管理 その2 ゲームの現在の状態によって表示する画面を切り替えたい. プロパティバインディングとJavaScriptコードでの条件分岐でも 対応できるが,管理したい状態が増えると式が煩雑になったり, 同じような条件分岐があちこち点在するようになり,管理が大変に なる. color: mouseArea.containsMouse ? "blue" : "red" (プロパティバインディングは非常に強力で,JavaScriptの式に も対応している.width: height + 30 などもそうだが,JavaScript の式中に含まれるプロパティの値が変わると,その式を再評価す る仕組みになっている.) 53 / 59

Slide 54

Slide 54 text

states state を用いた状態の管理 コンポーネントにもとから備わっているstates プロパティに各状態 を記述していく.State の中にはStateChangeScript PropertyChanges な ど,その状態に切り替わった時に実行する動作を記述できる. 初期状態はstate で定義する.game.state = 'play' とすることによ り,状態の切り替えを行える. state: 'title' states: [ State { name: "title" }, State { name: "play" StateChangeScript { script: play.start() } }, State { name: "result" } ] 54 / 59

Slide 55

Slide 55 text

最後にGame にTitle Play Resutl を配置して完了!お疲れ様でした. Title { id: title anchors.fill: parent visible: game.state === 'title' onBeginPlay: game.state = 'play' } Play { id: play anchors.fill: parent visible: game.state !== 'title' onGameOver: { console.log("score: ", score); result.score = score; game.state = 'result'; } } Result { id: result anchors.fill: parent visible: game.state === 'result' onBackToTitle: game.state = 'title' } 55 / 59

Slide 56

Slide 56 text

おまけ スペースキーだけでなくクリックにも対応しよう! ランダムに違う鳥が出るようにしてみよう! ランダムに違うパイプが出るようにしてみよう! 背景をランダムに変更しよう! スコアボードを作ろう! スコアが上がるにつれ難しくなるようにしよう! 衝突判定を厳しくしよう! 効果音を付けよう! 他思いつくこと何でも 56 / 59

Slide 57

Slide 57 text

まとめ コンポーネント プロパティ アンカー プロパティバインディング 状態管理 シグナル・ハンドラ 57 / 59

Slide 58

Slide 58 text

ほかにも アニメーションの宣言的記述 音声・動画の再生 ピクセルシェーダー,頂点シェーダーの使用 ウェブページの表示 C++での拡張 3次元的表現 パーティクル HTML5 Canvas などなど 58 / 59

Slide 59

Slide 59 text

References How to Make a Flappy Bird Game with V-Play | V-Play 2.4 | V- Play Game Engine Mobile - Flappy Bird - Version 1.2 Sprites - The Spriters Resource QMLプログラミング入門へようこそ! — Getting started QML programming Japanese translation 2014.04.05 ドキュメント Qt QuickではじめるクロスプラットフォームUIプログラミング 59 / 59