読者です 読者をやめる 読者になる 読者になる

C++ ときどき ごはん、わりとてぃーぶれいく☆

Wonder Rabbit Projectのなかのひとのブログ。主にC++。

Qt5 / Qt Quick 2.0 第1回 はじめたよ : ParticleSystem

Qtまじやばい・x・

と、言う訳で先ずは簡単な触りから、この素晴らしく魅力的な世界に入ってみたいと思います。先ずは少し古いですがQt Quick 2.0について、2011年のデモ映像の真似っ子をしてみる事に。

本項の前提知識等

  • 適当なデータ記述言語に関する基礎知識(例えばHTMLとかXMLとか)
  • ECMAScriptの基礎知識
  • 基礎的な端末の操作 or IDEの使用経験

※今回はC++力は使いません。また、パーティクルシステムについては知らなくてもデモを見てパーティクルの意味(粒子)を考えれば分かる程度の浅い内容です。

教材

準備

openSUSEじゃない人はまあ…がんばって探したりビルドしてね!

参考(著者環境)

  << qmake -v      
QMake version 3.0
Using Qt version 5.0.0 in /usr/lib64

  << qmlviewer5 -v
Qml debugging is enabled. Only use this in a safe environment!
Qt QML Viewer version 5.0.0

  << qtcreator -version 2>&1 >/dev/null | grep "^[^ ].*"
Qt Creator 2.6.1 based on Qt 5.0.0
(C) 2012 Digia Plc

  << g++ --version | head -n1
g++ (SUSE Linux) 4.7.1 20120723 [gcc-4_7-branch revision 189773]

  << uname -a
Linux LH-MAIN 3.4.11-2.16-desktop #1 SMP PREEMPT Wed Sep 26 17:05:00 UTC 2012 (259fc87) x86_64 x86_64 x86_64 GNU/Linux

※" << "は著者環境のプロンプトです。

やってみる

Qt Quick 2.0 を遊ぶ雛形の作成

QtCreator -> File -> New file or project
 -> Projects::Applications::Qt Quick 2 UI

これでUI専用(QMLソースのみでC++部分が無い)のプロジェクトができます。構成ファイル群は、

  • hoge.qmlproject
  • hoge.qml

こんだけ・w・

どちらもテキストファイルなのでエディターで確認してみると良い。.qmlprojectも読んだら分かる程度。.qmlはこれから遊ぶQMLを書いていくファイル。どうやって動作させるかと言うと、qmlviewer(著者環境ではqmlviewer5)で.qmlをロードするだけ。

  << qmlviewer5 hoge.qml

実際アプリを作り込む訳で無くQMLで遊ぶ程度にもこれで十分だし、ちょっとQt/QMLを試したり調べた感じ何か作り込む場合も独自のQML要素をC++で実装したものダイナミックリンクライブラリーとして用意して置き、後はUIデザインしながら必要機能をQMLでまとめるなんて事も可能っぽい。もちろん、全てのQML要素はC++で実装されているのでQMLは一切書かずに全てのQMLの機能をC++レベルだけで実装させる事も可能。通常は都合の良い様に折り合いを付けてC++に支えられしQMLを有効活用するのが良いでしょう!

パーティクルシステムで遊ぶ為のQMLの基本

今回の教材にするビデオの11分くらいからQt Quick 2.0のお話になります。真面目に全部聞いてもいいけど、15分くらいからのQMLのデモを見て、そこから続くパーティクルシステムについて遊んでみる事にしましょう。17分20秒くらいからお待ち兼ね、QMLをリアルタイムに編集してのちょっとしたデモが始まります。

最初に提示されたQMLソースをそのまま打ち込むと

となります。importはC#JavaPythonRubyなどの経験者ならピンと来た、だいたいそんな様なものです。ECMAScriptしか経験した事が無い場合は、HTML側で事前にlinkで処理する.jsライブラリーよりもちょっとイケてる仕様の何かそんな様なものだと思えば良いでしょう。C++erはモジュールうらやましすーとか思えばいいと思うよ!(C++1yだかその次に入れる気みたいな話をどこかで聞いたけど。)

後はデータ記述言語を何かしら、特にEclipseAndroid開発でXMLGUIを作ったり、.net向けにXAMLを扱った事がある人なら、或いはQt GUIを使った事がある人も.uiはXMLベースだ。何れこのQMLというのがなんとなくGUIの記述言語っぽい事は分かると思う。しかもソースを編集する気力が無くなるXMLと違ってとても本質に忠実でシンプルだ!(勿論、私はこれを見た時に先ず喜んだよ!)

シンプル過ぎるデータ記述言語を目の前にしてもすぐには喜ばないのがC++er達だと思う。「しょぼいんじゃ…拡張性とか…」などとシンプル過ぎる事に懸念を抱くかも。でも大丈夫、この Rectangle { } などのQMLのGUI部品の1つ1つは内部的にはC++のクラスで実装されているし、もちろん構造化され継承によるポリモルフィズムにも基づいているし、それらはユーザーコードでオーバーライドも、或いはほぼ完全に空っぽの要素から派生してオレオレQML要素を作る事だってできるし、widthやheightの様なプロパティを持たせQMLへpublicに公開する事も簡単なんだ。(まあそれはまたおいおい。)

そして更に、QMLの優れている点は(これらは何もQML 2.0からの話じゃなくて1.xからそうみたいだけど)、QMLではECMAScriptが使えるって事。console.logも実装されているからprintfデバッグをQMLで行う事も簡単。

f:id:USAGI-WRP:20130102183601p:plain

絵がどうみてもビデオと違う?いちいち用意するのが面倒だったので最近ちょっと変えた私のアイコンをパーティクルの画像にしてみた。確かにパーティクルシステムのスプライトレンダリングには向いていないけど、本質じゃないから今回は気にしないで。

それと、参考としてVim用のQMLコードシンタックスハイライターは、

にあるよ。コードあるところにVimあり。

…さて、横道に逸れ過ぎないうちに、いい加減にビデオを先に進めてコードを書こう。

Rectangle、ParticleSystem、ImageParticle、Emitter

RectangleはQMLの最も基本的なGUIパーツ。日本語で言うと「四角」ってだけの部品。QMLでは一番外側の要素がウィンドウを構成する際にメニューやらウィンドウの装飾やらを覗いたGUI本体の枠になると思って良い。これは、別に一番外側がRectangleじゃなくたって構わないけれど、シンプルな、いわゆるHello, worldに相当する事をGUIでする時のお約束だね。

QMLの要素は要素ごとに固有または継承したプロパティを持っていて、width,height,colorはREADもWRITEも可能なプロパティ。それらの意味までは説明しなくても想像通り。colorが"black"と文字列を使っている点だけ注意かな。このcolorの部分については、

とか参考にどうぞ。これはQt 4.7の頃の記事みたいだけど、基本的には現役みたい。実際ちょっと桃色にしてみようか。

f:id:USAGI-WRP:20130102191027p:plain

ちなみに色指定は"#ffcccc"みたいに16進数で#RRGGBB形式でも指定できる。但しこの記法で透明度を加えたい場合には#AARRGGBBとなる点に注意が必要。と、言ってもこの最も外側の枠に透明度を指定しただけじゃ、残念な事にウィンドウそのものを透過させる事はできないんだけどね。生憎と私はまだC++コードを使わずにウィンドウを透過させる方法を知らないので、今回そういった事は抜きでQMLだけで続けて遊んでみようかと思う。

さて、ここでプロパティの指定が早速ECMAScriptでQt.rgbaなる関数を呼び出している様子に気づいただろうか?さらにしれっと私はC++形式の行コメント(これはECMAScriptでも同様に使える)も使っている。実はこの部分、次の様にも書ける。

プロパティに代入していたのはECMAScriptの式で、コードブロックで書くこともできるし、console.logも使える。

でもこれはECMAScriptとはちょっと違うと思っただろうか?さて、どこが?

「コードブロックの内部の各ステートメントの末尾にセミコロンが無い事だ!」と思った人が居て、もしもその人が私のJavaScript講座の受講者だったのならノートを見直すべきだよ、それはECMAScriptの言語仕様にも省略可能が明記されているので違う。(すまない、これはちょっとした悪ふざけの引っ掛けだ。不慮の事故を起こさない為にもセミコロンは付けた方が良い事には勿論同意するよ。)

color: { ... }

の部分はもしもECMAScriptでRectangleが連想配列の定義であると枠に当て嵌めて見るならば、

function(){ ... }()

でなければ、全体を純粋なECMAScriptの文法的に見るならば color にセットされる値は { ... } というハッシュオブジェクトになってしまう。しかし { ... } はハッシュオブジェクトのリテラルにそぐわないのでもしこれがECMAScriptならば実行時に文法エラーを吐くだろう。

しかし、実際にはこれはまるでその場でラムダ式が定義され、プロパティは値を評価する度にそのラムダ式が評価される様な挙動を示す。実行してみればこのcolorが評価されたタイミングでconsole.logが評価され標準出力に何やら表示される。この様に、ECMAScriptを基としつつもエレガントにパワフルかつコンパクトでシンプルなのがQMLらしい。(この形容は私が勝手にしただけでQtの中の人がそう言ってるのを聞いた訳ではない。)

さて、 ParticleSystem についてはこれから弄って楽しんで概要を把握する。パーティクルシステムと言うのはビデオでも軽く紹介されて既に動いている様に、「世界には粒がたくさんありました。」と言う事を表現(主に今回の様に可視化、或いは科学的なシミュレーションでもしばしば用いられる)して見せる仕組みの事を言う。世界は画面、粒は…何らかの特徴を持って動く…粒だよ、うん、粒だ。今回は物理ベースでも何でもない単純な幾何学的な動きをする粒をビルボード方式でレンダリングするだけのシステムだけど、実際パーティクルシステムは2次元じゃなくても3次元でも構わないし、粒1つがビー玉でも原子でもボーリング玉でも、はたまた人でも雉でも兎でも、惑星でも、ブラックマター(いやよく知らないけれど)でも何でも良い。勿論、弾幕シューティングゲームだってパーティクルシステムとも言える。

さて、そんな概要としてのパーティクルシステムをQML要素として実現してくれているのがParticleSystem。ParticleSystemは内包するパーティクル(=粒)の特徴に基づいて、その粒をたくさん同時に、その世界の法則に従って運動させる系を実装してくれている。内部に今回のソースではImageParticleとして画像を表示する(いわゆるパーティクルをスプライトレンダリングする)粒子と、その挙動の定義をするEmitterが含まれている。

Emitterを調整して挙動を面白くしてみよう、と言うのが以降のビデオで続く。

…そうそう、忘れていたけど anchors.fill ってプロパティは要素を何かにアンカー(=錨)を打ち込んで、相対的に自身の位置を調整できる仕組みで、CSSで言えばposition:relativeを明示した親要素の中でposition:absoluteの子要素が親要素の右端や下端を"アンカー"にしてright:20とかbottom:20とかって位置決めをできるそういう機能。anchors.fill: parent では自身は親要素にピッタリ広がって張り付くよって事で、親要素と同じ表示粋を子要素に与えたい時に使う便利な指定だよ。

Emitterいじるよ〜

ところで、そもそもEmitterとは?電気デバイスに触れた事がある人ならピンとしたかもしれない、ベース、コレクター、エミッター、なんてね。そのエミッター、つまりパーティクルシステムにおける粒子の放射装置だと思ったら分り易い。ParticleSystemにParticleが出現する為にはParticleを生成して放射してくれる何かが必要、それがEmitter。

さて、現在のEmitterは anchors.fill:parent によって外枠の ParticleSystem に、そのParticleSystemもまた anchors.fill: parent で外枠の Rectangle の表示域に一致している。つまり装飾を覗いたウィンドウ全体。Emitter::sizeは生成するパーティクルの大きさの指示であってEmitterの大きさではない事には注意。また、現在は50となっているけれど、この単位は pixels 。"1cm"とか"12pt"とかは指定できないみたい。

さてさて、ビデオを続けて見よう。Emitterで遊んでいる。19分30秒くらいの状態のコードは、

(※注: 著者のミスで Emitter の width と height に ; がついていますが、これだと各プロパティが ; の後の評価対象を値としてしまう為にエラーではなく undefined が width と height に指定されてしまいます。undefinedをプロパティに指示する事はQMLのstateでも使う手前もあり文法的には合法ですが、このデモ用としては予想した挙動にならない結果となってしまうので ; は付けない状態で試して下さい。)

こんな感じ。Emitter自身の大きさを width:50 height:50 と小さくし、ついでに会場とデモの都合Rectangleの大きさも width:500 height:500 に調整し、Emitterの配置を anchors.bottom: parent.bottom としてRectangleの下端を基準に変更した状態。

f:id:USAGI-WRP:20130102203540p:plain

私のアイコンが size:50 で表示されまくっても大き過ぎてアレなのでこのスクリーンショットでは size:10 にしてある。

ビデオの19分50秒ほどではEmitter::speed(→これはQt5の正式リリース前に仕様が変更となり、現在はEmitter::velocityとなっている)を指定して生成される粒子に速度を与えている。ちなみにこの状態では粒子は等速直線運動しかしない。

f:id:USAGI-WRP:20130102204716p:plain

この辺りからは動画の方が実行の様子が分り易いと思うので動画も用意してみました。


Qt5/QtQuick 2.0 ParticleSystem test 1-1

Emitter::velocity に与えた AngleDirection (こちらはCamelCaseな点にも注意。RectangleやParticleSystemやEmitterと同様にね。)は方位による速度指定を行うオブジェクトで、 angle:300 は弧度法の degrees 単位で速度を与え、magnitude は pixels/sec 。angleについて気を付けなければならないのは、この世界は四角形の左上を原点として、右へX、下へYのスクリーン座標系だって事。だから第一軸のX軸と平行かつ+向きの方位をangle:0として上ではなくて下向きに回るのがangleの+。それで右上はangle:300になる。

AngleDirectionについてはこの後紹介するangle or magnitude への Variation の組み合わせはあるものの、基本的には angle と magnitude しか設定パラメーターはありません。もっと詳しくという方は下記リファレンスをどうぞ。

これに Emitter::lifeSpan:8000 を加えた状態が、

こんな感じ。教材にしているビデオとも同様ですね。Emitter::lifeSpanはEmitterが発生するパーティクルの寿命を ms(ミリ秒) 単位でセットします。これまではユーザーが明示的に指定してなかったのでデフォルト値である1000がセットされた状態となって居て、生成されるパーティクルは1秒の寿命となっていた訳です。この値を8000にしたのでパーティクルの寿命が8秒に伸びて居ます。

ビデオを続けましょう。20分50秒ほどから Emitter::velocity にセットしている AngleDirection に angleVariation:10 を加えたデモを行なっています。

AngleDirection::angleVariation:10 は AngleDirection::angle:300 と指定した速度方位に対して -10以上+10以下の揺らぎを与えます。AngleDirection::angleVariationのデフォルト値は0なので、ユーザーがセットしない限りは揺らぎは生じません。 仕様文書が気になる人は下記からどうぞ。

ビデオを続けます。21分25秒くらいからのデモでは、Emitter::sizeVariation:40 でEmitterが生成する粒子の大きさに -40以上+40以下の揺らぎを与え(正確にはsize/endSizeだったりしますが気になる人は仕様を確認してみてください)、さらにImageParticle::colorVariation: 1.0 によりR,G,B各チャンネルそれぞれに -1.0以上+1.0以下の揺らぎを与えています。

さらにビデオを続けると、ParticleSystemの中に (Gravity)http://qt-project.org/doc/qt-5.0/qtquick/qml-qtquick-particles2-gravity.html(重力効果)を加えています。

注意として、元のビデオの Gravity::acceleration はQt5リリースの前に Gravity::magnutude に名前が変更になっています。 また、元のビデオのままではパラメーター的に美しく挙動を再現出来ないので値は少々変更してあります。Gravityも比較的単純なプロパティしかありません。

angle は重力の向き、 magnitude は重力加速度で pixels / (sec * sec) 単位です。Emitter同様にGravityも自身の領域の大きさと位置も持って居るので、私のサンプルソースでは幅は親要素(ParticleSystem)と同じ、高さは親要素の1/3、設置位置はYを親要素の高さの1/25としました。なんだか立体感を感じる動きになりましたね。

ビデオではここからGPU機能を使うシェーダーエフェクトについての解説となりますが、この記事もだいぶ長ったらしくなってしまい、途中で休憩も挟んだせいか私の口調も不安定で読み難い状態になってしまっていたかと思いますので今回はここまでにしようと思います。内容は薄いけれど、QMLからQt5に興味を持つには十分な掴みとなる内容だったと思います。

references