#はじめに
さて、Advent Calendarも無事に8日目を迎えました。昨日はasobotさんによるQt CreatorのRC版の情報でした。新しいリリースバージョンが出てくるのが楽しみですね。
さて、今回のカレンダーはここまでQMLを扱ったエントリーなしという驚愕の事実に気がつきまして、本日はQML関係としまして、qml-box2dをかる〜くご紹介しておきます。hermit4は、普段QML自体もほとんど使う事がない初心者ですので、色々と間違いもあるかと思います。何か気がつかれた方はコメント下さい。
基礎知識
##QMLとは
QMLはQtを使って実装された宣言型UI記述言語です。
import QtQuick 2.4
Rectangle {
property alias mouseArea: mouseArea
width: 360
height: 360
MouseArea {
id: mouseArea
anchors.fill: parent
}
Text {
anchors.centerIn: parent
text: "Hello World"
}
}
import QtQuick 2.4
import QtQuick.Window 2.2
Window {
visible: true
MainForm {
anchors.fill: parent
mouseArea.onClicked: {
Qt.quit();
}
}
}
このコードは、QMLアプリケーションのプロジェクトを新規作成すると生成されるコードです。MainFormは、Rectangle(長方形)の中にMouseAreaというマウスのイベントを取得可能な見えない領域と、Hello Worldの文字を生成しています。main.qmlでは、Windowの中にMainFormを一つ用意しており、マウスのクリック時に動作を終了するよう定義しています。
上記のような宣言構文で画面上の配置を行い、動作をJavaScriptで定義できるほか、C++で記述した拡張ライブラリを登録して利用する事もできます。
Qtプロジェクトでは、標準ライブラリのQt QuickモジュールとQt Widgetのような標準的なGUIアプリのコントロールを提供するQt Quick Controlsモジュールの2つのライブラリが用意されています。上記の例では、QtQuickモジュールを利用しています。
日本語では、22日目に記事を書いて下さる予定のIoriAyane(折戸さん)による「Qt QuickではじめるクロスプラットフォームUIプログラミング」という書籍が販売されています。QML/Qt Quick初期の頃のものなので、そのままでは色々素直に動かなかったりはしますが、Qtに付属のチュートリアルやサンプルと合わせて、なぜ動かないかも含めて調べていくと、きっといつの間にかQMLが使いこなせるようになるかと思います。
##Box2Dとは
昨年のQt Advent Calendarの13日目にyasukikudanさんが、「QGraphcsViewでBox2Dを表示する」というエントリーを登録して下さっています。まぁ、細かい説明なしのシンプルなものなので、なんぞこれって思った方もいるかもしれません。
Box2Dは、2次元上での物理演算エンジンで、ある重力下での質量、速度、摩擦といった力学計算を行ってくれるゲーム用のエンジンです。スマートフォンでゲームをたしなむ方ですと、Angry Birdsの動作演算に利用されているものだといえば、なんとなく想像がつくかもしれません。
元々はC++で作られたものですが、C#, JavaScript, Python, ActionScript等に移植されている人気のエンジンです。
実際、どういう事が出来るのかは、このあとqml-box2dの使い方で見ていきたいと思います。
#qml-box2d
QML Box2Dについて
QMLはC++で拡張することができると述べましたが、qml-box2dもその一つで、C++で実装されたBox2DをQML上で動作できるように改良したQMLモジュールライブラリです。現在、GitHubにて公開されています。
Install方法
本日は時間もない1ので、Advent Calendarも乱立するご時世に、わざわざQtを見に来られているので、既にQtの環境はお持ちかと思います。ついでに、Git環境も既に整っているという前提で話を進めてしまいます。わからなければ、コメント残して下さい。あとで手直しします。
$ git clone https://github.com/qml-box2d/qml-box2d.git
$ cd qml-box2d
$ qmake
$ make
$ make install
Windows環境の場合は、Qtメニューからたどれるコマンドプロンプトを開き、環境に応じてvcbarsall.batを通した後、makeの代わりに\Tools\QtCreator\bin\jom.exe を使うと素直にビルドできました。なお、Qtのinstall環境によっては、make installは管理者権限が必要な場合もあります。
make installを実施すると、Qtに、qml-box2dのライブラリがinstallされます。
例えば、Mac OS X の場合は、イカの通り
./Qt5.5.1/5.5/clang_64/qml/Box2D.2.0/libBox2D.dylib
./Qt5.5.1/5.5/clang_64/qml/Box2D.2.0/qmldir
動作検証
qml-box2dもexampleが色々用意されていますので、まずはそちらを動かしてみましょう。
$ qmlscene <path to qml-box2d>/examples/boxes/boxes.qml
上から3つの箱が落ちてくるだけのシンプルなアプリが実行されるかと思います。
概要説明
最初にサンプルの内容を説明しよう・・・と思ったのですが、これがまた意外と量が多いので余計にわかりにくくなりそうです。というわけで、まずは、qml-box2dのソースコードを見てみましょう。
void Box2DPlugin::registerTypes(const char *uri)
{
#if !defined(STATIC_PLUGIN_BOX2D)
Q_ASSERT(QLatin1String(uri) == QLatin1String("Box2D"));
#endif
qmlRegisterType<Box2DWorld>(uri, versionMajor, versionMinor, "World");
qmlRegisterUncreatableType<Box2DProfile>(uri, versionMajor, versionMinor, "Profile",
QStringLiteral("Property group of World"));
qmlRegisterType<Box2DBody>(uri, versionMajor, versionMinor, "Body");
qmlRegisterUncreatableType<Box2DFixture>(uri, versionMajor, versionMinor, "Fixture",
QStringLiteral("Base type for Box, Circle etc."));
qmlRegisterType<Box2DBox>(uri, versionMajor, versionMinor, "Box");
qmlRegisterType<Box2DCircle>(uri, versionMajor, versionMinor, "Circle");
qmlRegisterType<Box2DPolygon>(uri, versionMajor, versionMinor, "Polygon");
qmlRegisterType<Box2DChain>(uri, versionMajor, versionMinor, "Chain");
qmlRegisterType<Box2DEdge>(uri, versionMajor, versionMinor, "Edge");
qmlRegisterType<Box2DDebugDraw>(uri, versionMajor, versionMinor, "DebugDraw");
qmlRegisterUncreatableType<Box2DJoint>(uri, versionMajor, versionMinor, "Joint",
QStringLiteral("Base type for DistanceJoint, RevoluteJoint etc."));
qmlRegisterType<Box2DDistanceJoint>(uri, versionMajor, versionMinor, "DistanceJoint");
qmlRegisterType<Box2DPrismaticJoint>(uri, versionMajor, versionMinor, "PrismaticJoint");
qmlRegisterType<Box2DRevoluteJoint>(uri, versionMajor, versionMinor, "RevoluteJoint");
qmlRegisterType<Box2DMotorJoint>(uri, versionMajor, versionMinor, "MotorJoint");
qmlRegisterType<Box2DWeldJoint>(uri, versionMajor, versionMinor, "WeldJoint");
qmlRegisterType<Box2DPulleyJoint>(uri, versionMajor, versionMinor, "PulleyJoint");
qmlRegisterType<Box2DFrictionJoint>(uri, versionMajor, versionMinor, "FrictionJoint");
qmlRegisterType<Box2DWheelJoint>(uri, versionMajor, versionMinor, "WheelJoint");
qmlRegisterType<Box2DMouseJoint>(uri, versionMajor, versionMinor, "MouseJoint");
qmlRegisterType<Box2DGearJoint>(uri, versionMajor, versionMinor, "GearJoint");
qmlRegisterType<Box2DRopeJoint>(uri, versionMajor, versionMinor, "RopeJoint");
qmlRegisterType<Box2DRayCast>(uri, versionMajor, versionMinor, "RayCast");
qmlRegisterUncreatableType<Box2DContact>(uri, versionMajor, versionMinor, "Contact", QStringLiteral("Contact class"));
}
QMLで利用できる型として定義をしているのが上記のメソッドです。
- World
- Body
- [UncreatableType] Fixture
- Box
- Circle
- Polygon
- Chain
- Edge
- DebugDraw
- [UncreatableType] Joint
- DistanceJoint
- PrismaticJoint
- RevoluteJoint
- MotorJoint
- WeldJoint
- PullyJoint
- FrictionJoint
- WheelJoint
- MouseJoint
- GearJoint
- RopeJoint
- RayCast
- [UncreatableType] Contact
World
シミュレーション上の1つの世界を表します。
- 重力の設定
- 物理シミュレーションの調整
- 指定領域のFixtureの検出
- 衝突検出と交錯したFixtureの検出
といった機能を持ちます。プロパティグループとしてProfileを保持しています。
Body
物理演算上の基本オブジェクト。ただしこれ自体が衝突したり、跳ねたりする”物”を表しているのではなく、演算上のプロパティに過ぎない。だったらBodyという命名をするなって突っ込みを入れたくなりますよね。
- 質量
- 速度
- 回転慣性
- 角速度
- 場所
- 角度
といった特性を保持します。
Fixture
物理演算上で、物質の形状、大きさ、材質特性を保持します。Bodyに対して割り当てられるもので、FixtureをもったBodyが衝突や、バウンドする演算対象の物質となります。
- 形状(円や多角形)
- 衝突特性(どう弾むのか)
- 摩擦
- 密度(重さ)
といった特性を保持します。QMLでは、Fixtureは、以下のような種類が用意されています。
- Box
- Circle
- Polygon
- Chain
- Edge
DebugDraw
box2dは物理演算エンジンで、描画エンジンではないため、演算結果の数値が求められるだけで、通常は描画しません。DebugDrawは、物理演算のデバッグ用に演算結果を画面上に描画する機能を提供します。
Joint
Body間の制約条件を示すもので、物質同士がどのような関係で存在しているのかという状態をしめします。たとえば固く溶接されているのか、ヒンジのようなもので接続されているのか、そういった関連性をJointで定義します。種類としては、以下のものが用意されています。
- DistanceJoint
- PrismaticJoint
- RevoluteJoint
- MotorJoint
- WeldJoint
- PullyJoint
- FrictionJoint
- WheelJoint
- MouseJoint
- GearJoint
- RopeJoint
RayCast
レイキャストは、衝突判定のため、光線のようにまっすぐ伸びる仮想的な線を利用して衝突を判定するための機能を提供しています。
RayCast {
maxFraction:
}
Contact
box2dはゲームエンジンですので、衝突の結果として、物体の消滅、破損、サウンド効果等を与える必要があります。Contactは物体の衝突について、どう扱うかの定義を行うための機能を提供しています。
シンプルな例
exampleの中に色々入ってはいますが、最初の一歩としては大きすぎるので、簡単なものを作ってみましょう。
テキストエディタか何かで、以下のコードを書いてみて下さい。
import QtQuick 2.4
import QtQuick.Window 2.2
import Box2D 2.0
Window {
visible: true
width: 640
height: 480
Rectangle {
id: screen
anchors.fill: parent
World {
id: physicsWorld
}
DebugDraw {
visible: true
}
Body {
world: physicsWorld
bodyType: Body.Dynamic
Box {
x: screen.width/2
y: 10
width: 10
height: 10
}
}
}
}
編集が終わったら、qmlsceneで実行してみましょう。
$ qmlscene simple.qml
四角い形状の何かが、画面の上から下へ落ちて行ったかと思います。
DebugDrawを使って、演算結果を画面表示しているのは、Body+Box(Fixture)は、演算の為の形状であって描画対象ではないため、DebugDrawを通さないと画面に何も表示されないからです。
それでは、DebugDrawを外して画面上に表示させるのにはどうしたらよいかというと、表示対象の物を用意してあげる必要があります。
import QtQuick 2.4
import QtQuick.Window 2.2
import Box2D 2.0
Window {
visible: true
width: 640
height: 480
Rectangle {
anchors.fill: parent
World {
id: physicsWorld
}
Rectangle {
id: rectangle
x: parent.width/2
y: 10
width: 10
height: 10
color: "red"
Body {
id: boxBody
target: rectangle
world: physicsWorld
bodyType: Body.Dynamic
Box {
id: box
x: rectangle.x
y: rectangle.y
width: rectangle.width
height: rectangle.height
}
}
}
}
}
描画対象となるRectangle(id=rectangle)を用意し、Bodyはtargetをrectangleとして、その移動を演算して反映させるようにします。これで、rectangleは、box2dの演算結果に基づいて、Body+Boxと同様に画面上を落下していくことになるのです。
実際、何をどう使うと、どんなことができるのかという事は、exampleを一つずつ見ていくのが良いかと思います。
まとめ
本日は、QMLに2D空間での物理演算エンジンであるbox2dを組み合わせたqml-box2dライブラリについてご紹介致しました。ゲームを作ろうとすると、物理演算が必要になってくるわけですが、コレを使うとずいぶんと楽になりそうですね。
なにぶん、十分な時間もなく書き付けている(本日終了30分前にまだ書いてる)ので、ごちゃごちゃしていてすいません。明日は、informationseaさんによる、Windows環境でのStatic版Qtのビルド方法だそうです。お楽しみに。
Appendix.A qml-box2d Examples
ui.qmlをqmlsceneで見れば、選択メニューで一覧できるようですが、一通りのせておきます。
共有アイテム
実装を簡略にするためによく使う機能を名前を付けて使い回せるようにしている
- shared/BoxBody.qml
Body + Box Fixtureの組み合わせ - shared/ChainBody.qml
Body + Chain Fixtureの組み合わせ - shared/CircleBody.qml
Body + Circle Fixtureの組み合わせ - shared/EdgeBody.qml
Body + Edgeの組み合わせ - shared/ImageBoxBody.qml
BoxBodyにImageという画像描画を組み合わせたもの - shared/PhysicsItem.qml
Body + Itemの組み合わせ。 - shared/PolygonBody.qml
Body + Polygon Fixtureの組み合わせ - shared/RectangleBoxBody.qml
BoxBodyにRectangleという描画領域を組み合わせたもの - shared/ScreenBoundaries.qml
演算対象物が領域外に抜けて行かないようにするためのボーダーを用意するためのもの。
実装例
- accelerometer/accelerometer.qml
BoxesにQMLのAccelerometerを組み合わせたもの。
- boxes/boxes.qml
動作検証時に使ったサンプル。
- cannon/main.qml
大砲をうつというゲームっぽい作りのもの。
なお、画像ではわかりませんが、Shoot時に発射音も出しています。
- contacts/main.qml
ベルトコンベアと吸い込み・吐き出しといった動作にくわえ、吐き出された時に色が変わるといったプロパティ変化もついています。
サイズ圧縮の関係でGif画像はせわしないですが、もっとゆっくり動作してます。
- demolition/demolition.qml
マウスのクリックで弾け飛ぶboxが並んでいます。ぴょんぴょんしているのは、hermit4が適当にクリックしているからです。
- distance/distance.qml
Joint関連のサンプルですかね。マウスをクリックすると位置によりカラフルな円が回転しながら動作し、それに引っ張られて後ろの長方形も移動します。画像だとちょっとわかりにくいので、実際のサンプルを動かしてみて下さい。
- filtering/filterling.qml
- fixtures/main.qml
- friction/main.qml
ぶつかった時の動作に関するサンプル
- gear/main.qml
回転体や、重さで開くゲート、動作するゲートなど動くものとの接触などのサンプルでしょうか。
画像を小さくする上でだいぶ早くなっているので、何がなんだかわかりにくいですが・・・。
- impulse
- monera/monera.qml
- motorjoint/boart.qml
MortorJointの例。波間をゆらゆらとボートが揺れるような動きが実装できる。
- mouse/main.qml
Mouseで操作可能なボックスの例でしょうか。マウスでドラッグすると物体を放り投げられます。
- movingBox/movingBox.qml
- polygons/polygons.qml
- prismatic/prismatic.qml
- pulley/main.qml
- raycast/main.qml
- revolute/revolute.qml
RevoluteJointのサンプル。マウスのクリックで停止、カーソルキーの左右で動く方向や勢いが変えられます。
- rope/main.qml
RopeJointのサンプル。これはみたままでわかりやすいですね。
- weld/main.qml
- wheel
Appendix.B qml-box2d プロパティ
それでは、各タイプについてプロパティを抜き出していってみます。ただの羅列でごめんなさい。そのうちキチンと調べてまとめたいと思います。
World
World {
running:
timeStep:
velocityIterations:
positionIterations:
gravity:
autoClearForces:
pixelsPerMeter:
profile:
enableContactEvents:
}
Profile {
step:
collide:
solve:
solveInit:
solveVelocity:
solvePosition:
broadphase:
solveTOI:
synchronize:
emitSignals:
}
Body
Body {
world:
target:
linearDamping:
angularDamping:
bodyType:
bullet:
sleepingAllowed:
fixedRotation:
active:
awake:
linearVelocity:
angularVelocity:
fixtures:
gravityScale:
}
Fixture
Box {
// Fixture共通
density
friction
restitution
sensor
categories
collidesWith
groupIndex
// Box用
x
y
width
height
rotation
}
Circle {
density
friction
restitution
sensor
categories
collidesWith
groupIndex
x
y
radius
}
Polygon {
density
friction
restitution
sensor
categories
collidesWith
groupIndex
vertices
}
Chain {
density
friction
restitution
sensor
categories
collidesWith
groupIndex
vertices
loop
prevVertex
nextVertex
}
Edge {
density
friction
restitution
sensor
categories
collidesWith
groupIndex
vertices
}
Joint
DistanceJoint {
JointType:
collideConnected:
bodyA:
bodyB:
localAnchorA:
localAnchorB:
length:
frequencyHz:
dampingRatio:
}
PrismaticJoint {
JointType:
collideConnected:
bodyA:
bodyB:
localAnchorA:
localAnchorB:
localAxisA:
referenceAngle:
enableLimit:
lowerTranslation:
upperTranslation:
enableMotor:
maxMotorForce:
motorSpeed:
}
RevoluteJoint {
JointType:
collideConnected:
bodyA:
bodyB:
localAnchorA:
localAnchorB:
referenceAngle:
enableLimit:
lowerAngle:
upperAngle:
enableMotor:
motorSpeed:
maxMotorTorque:
}
MotorJoint {
JointType:
collideConnected:
bodyA:
bodyB:
linearOffset:
angularOffset:
maxForce:
maxTorque:
correctionFactor:
}
WeldJoint {
JointType:
collideConnected:
bodyA:
bodyB:
referenceAngle:
frequencyHz:
dampingRatio:
localAnchorA:
localAnchorB:
}
PullyJoint {
JointType:
collideConnected:
bodyA:
bodyB:
groundAnchorA:
groundAnchorB:
localAnchorA:
localAnchorB:
lengthA:
lengthB:
ratio:
}
FrictionJoint {
JointType:
collideConnected:
bodyA:
bodyB:
localAnchorA:
localAnchorB:
maxForce:
maxTorque:
}
WheelJoint {
JointType:
collideConnected:
bodyA:
bodyB:
localAnchorA:
localAnchorB:
localAxisA:
dampingRatio:
frequencyHz:
maxMotorTorque:
motorSpeed:
enableMotor:
}
MouseJoint {
JointType:
collideConnected:
bodyA:
bodyB:
target:
maxForce:
frequencyHz:
dampingRatio:
}
GearJoint {
JointType:
collideConnected:
bodyA:
bodyB:
joint1:
joint2:
ration:
}
RopeJoint {
JointType:
collideConnected:
bodyA:
bodyB:
localAnchorA:
localAnchorB:
maxLength:
}
Contact
Contact {
enabled:
fixtureA:
fixtureB:
childIndexA:
childIndexB:
friction:
restitution:
tangentSpeed:
}
注釈
-
Macがハングアップしてまともに動作しなくなり格闘してました。ふと気が付いてカスペルスキーさんを止めたら動いた・・・ ↩