ここまでの連載で、長方形や円などの形をした物体を作ってきました。これだけでも色々と遊べるのですが、それぞれの物体がバラバラに動作するので、人の体や車といった複雑な動きを実現することができません。
そういった動きを実現するためには、ジョイントという要素が不可欠です。今回は、このジョイントについて説明します。
ジョイントの役割と種類
ジョイントは、その名のとおり物体と物体をつなぐ役割を果たします。単につなぐといっても、そのつなぎ方には色々なものがあります。Box2Dには、6種類のジョイントが用意されています。
なお、それぞれのジョイントの名前について著者が勝手に訳したもので、今のところ特に決まった呼び方はありません。
ディスタンスジョイント
ディスタンスジョイントは、物体と物体の距離を一定に保つだけのシンプルなジョイントです。b2DistanceJointDefクラスで定義されます。古い時計についている振り子のようなイメージです。
回転ジョイント
回転ジョイントは、物体と物体をピンのようなものでつなぎ、回転させるジョイントです。b2RevoluteJointDefクラスで定義されます。人間で例えると、腕や足などの関節です。関節を中心に物を回すこともできます。
直動ジョイント
直動ジョイントは、ピストンのような動きをするジョイントです。b2PrismaticJointDefクラスで定義されます。直線的な動きをするので、ピンボールのボールを発射するところなどで使うといいでしょう。
プーリージョイント
プーリージョイントは、名前のとおりプーリーの役割を果たすジョイントです。b2PulleyJointDefクラスで定義されます。ディスタンスジョイントを使っていて途中で方向を曲げたくなったらプーリージョイントを使います。
ギアジョイント
ギアジョイントは、ギアによってつながれている関係を表すジョイントです。b2GearJointDefクラスで定義されます。几帳面に多角形を組み合わせてギアを作ることもできますが、このギアジョイントを使えばギアの仕組みが簡単に作れます。
マウスジョイント
マウスジョイントは、マウスで物を動かすためのジョイントです。b2MouseJointDefクラスで定義されます。マニュアルにはテスト用と書かれていますが、使い道によっては十分活用できるジョイントです。
今回取り上げるジョイント
6種類もあるジョイントを全て説明すると紙面が足りなくなってしまうので、今回は回転ジョイントとマウスジョイントについて説明します。
まずは長方形と円を回転ジョイントで組み合わせて車を作ります。そして、マウスジョイントを使って、マウスを使って車を持ち運べるようにします。
車のパーツを作る
車は、車体を表す長方形1つと、車輪を表す円2つで作ります。まずはその段階までのソースとFlashを見てみましょう。
ActionScriptのほうは起動直後にパーツが現れるようになっていますが、Flashのほうは説明の都合上クリックすると開始するようになっています。
まずは車体から
物理エンジンや床の設置についてはいつも通りなので、車体(body)を作るところから説明します。車体は長方形なので、b2PolygonDefクラスのSetAsBoxメソッドを使って形を定義します。場所の定義にはb2BodyDefクラスを使います。
車体を作る場所は、床の中心から少し上とします。サイズは、画面にコンパクトに収まるように、幅を80cm、高さを20cmとしています。場所の定義bodyDefと、形の定義bodyShapeを使って、b2Bodyクラスの変数bodyを作れば、車体の完成です。
前輪を作る
次は前輪(frontWheel)です。車体の中心から右側に30cmずらしたところを前輪の中心位置とします。場所を定義するときに、新しくb2BodyDefクラスの変数を作る必要はありません。車体の定義で使ったbodyDef変数を使いまわすことができます。Setメソッドで場所だけ書き換えるだけで構いません。
形については、b2CircleDefクラスの変数wheelShapeを作り、円形の物体を定義します。半径は15cmとします。車体から少しはみ出るような大きさです。
後輪を作る
最後は後輪(rearWheel)です。前輪とは逆に、車体の中心から左側に30cmずらしたところを中心とします。後輪を作るときには、bodyDefに加えてwheelShapeも再利用できるので、コメント抜きでたったの4行で後輪を作れます。
車体と車輪をジョイントでつなぐ
ここまででCarParts.asの大まかな説明をしましたが、このソースコードから作られるFlashは、先ほどお見せしたとおり、車には程遠いものです。車体が床に落ちてしまい、車輪はゴロゴロとどこかに転がっていってしまいます。これらをつなぐのがジョイントの役目です。/p>
なお、ここから説明するソースコードは、後輪を作るところの直後から書き足していくものと考えてください。
b2RevolutionJointDefでつなぐ場所を決める
ジョイントを作るには、物体を作るときと同じように
- XxxJoiontDefクラスを使ってジョイントを定義する
- 定義を使ってXxxJointクラスの変数を作る
という順番で作業を進めます。車体と車輪をつなぐには、回転ジョイント(b2RevolutionJointDef)を使います。
// ジョイントを定義するjointDef変数
var jointDef:b2RevoluteJointDef = new b2RevoluteJointDef();
// つながれる2つの物体と、つなぐ位置(前輪の中央)を使って、定義を初期化する
jointDef.Initialize(body, frontWheel, frontWheel.GetWorldCenter());
jointDef変数を作ったら、まずInitializeメソッドで初期化します。1番目と2番目の引数は、つなぎ合わせる物体です。まずは車体と前輪をつなぐので、bodyとfrontWheelを指定します。
3番目の引数は、回転の軸となる座標です。この座標に指定しているfrontWheel.GetWorldCenter()は、前輪の中心座標、つまり画面の左から2.8m、上から2mの場所を表します。数字で直接指定することもできますが、このようにGetWorldCenterメソッドを使ったほうが分かりやすいかと思います。
ジョイントの細かい設定
次に、ジョイントに関する細かい設定をします。
// 回転速度の設定(6秒で1回転ぐらい)
jointDef.motorSpeed = 1;
// トルクの設定(大きいほど坂道に強くなる)
jointDef.maxMotorTorque = 1;
// 車輪を回すようにする
jointDef.enableMotor = true;
motorSpeedは、回転の速さを表す値です。単位はラジアン毎秒なので、1にすると約6秒で車輪が1回転することになります。
maxMotorTorqueは、トルク(回転の強さ)を表す値です。速さとはまた別の値で、坂道を走らせるのであればある程度意味を持ってくる値です。単位はニュートンメートルですが、今回のサンプルでは少し説明しづらいので詳細は省きます。
ここまで設定してきた値は、enableMotorをtrueにすることで効果を発揮します。これは文字通り、回転ジョイントをモーターとさせるかどうかのフラグです。連載第1回のサンプルで紹介したラグドールのようなものを作るときは、これがfalseになります。
定義からジョイントを作る
ジョイントの定義が終わったので、実際にジョイントを作ります。
// 回転ジョイントを作る
var frontJoint:b2RevoluteJoint = b2RevoluteJoint(world.CreateJoint(jointDef));
ここの書き方はCreateDynamicBodyなどを使って物体を作るときとほぼ同じですが、b2RevoluteJointにキャストしている部分だけが違います。CreateJointメソッドが返すオブジェクトの型がb2Jointという汎用的な型なので、それをfrontJointに代入するためにキャストしています。
後輪のジョイントを作る
後輪のジョイントを作るときには、前輪のジョイントの定義をほぼそのまま使えるので、たった2行で書くことができます。
// つながれる2つの物体と、つなぐ位置(後輪の中央)を使って、定義を初期化する
jointDef.Initialize(body, rearWheel, rearWheel.GetWorldCenter());
// 回転ジョイントを作る
var rearJoint:b2RevoluteJoint = b2RevoluteJoint(world.CreateJoint(jointDef));
後輪の中心を軸に車体と後輪をつなぐように設定し、ジョイントを作るだけです。他のパラメータは変える必要がありません。
ここまで書いてきたプログラムに必要なimport文を書き加えてコンパイルすると、以下のような結果になります(これもクリックで動作開始するように書き換えてます)。
ちゃんと車ができましたね。四角や円だけでもそこそこ使えるBox2Dですが、ジョイントを使うとできることが格段に増えます。
マウスジョイントを使って車を持ち運ぶ
ただ車が走っているだけではつまらないので、マウスで持ち運べるようにしましょう。マウスで操作をするときには、マウスジョイント(b2MouseJointDef)を使います。
クラスにプロパティを追加する
まず準備段階として、クラスに以下の3つのプロパティを追加します。
// 物理エンジンの管理クラス
private var world:b2World; // ← この行までは最初からある
// 車体
private var body:b2Body;
// マウスジョイントの定義
private var mouseJointDef:b2MouseJointDef;
// マウスジョイント
private var mouseJoint:b2MouseJoint;
このうち、bodyは先ほど車体として使っていたローカル変数を移動させたものです。そのため、bodyを作っている行を以下のように書き換えます。
var body:b2BodyDef = world.CreateDynamicBody(bodyDef);
↓ 書き換える
body = world.CreateDynamicBody(bodyDef);
mouseJointDefとmouseJointは、今回新しく追加されたもので、マウスジョイントの定義と、ジョイントそのものです。
マウスジョイントを定義する
車を作り終わった直後のところに、マウスジョイントの定義を設定するプログラムを書きます。
// マウスジョイントを定義する
mouseJointDef = new b2MouseJointDef();
// 片方のbodyにはワールド全体を設定する
mouseJointDef.body1 = world.GetGroundBody();
// もう片方には移動させたい物体(=車体)を設定する
mouseJointDef.body2 = body;
// 車体の中心座標を設定する
mouseJointDef.target = body.GetWorldCenter();
// マウスで引っ張られるときの力
mouseJointDef.maxForce = 5;
// シミュレーションの間隔
mouseJointDef.timeStep = 1 / 24;
回転ジョイントのときと同じように、つなぎたい物体をbody1とbody2に設定します。ここではb2WorldクラスのGetGroundBodyメソッドを使って、片方をワールド全体に設定します。もう片方にはマウスで動かしたい物体を設定します。
targetには、引っ張られる対象の座標を設定します。今回は車体を引っ張ることにするので、body.GetWorldCenterを使って車体の中心座標を設定します。
maxForceとtimeStepは、それぞれ説明にあるとおり、マウスで引っ張る力とシミュレーションの間隔です。maxForceが小さすぎると、重力に打ち勝つことができずに車が落ちていってしまいます。このあたりは実際に値を書き換えてみて試してみてください。シミュレーション間隔は、Flashの画面更新間隔と同じ1/24秒にします。
マウスイベントのハンドラを追加する
ここまで書いて気づいたかもしれませんが、マウスジョイントの定義は作りましたが、マウスジョイントそのものは作っていません。なぜかというと、マウスジョイントを作ってしまうと、車体が引っ張られ続けてしまうからです。マウスボタンが押されている間だけジョイントでつなぐという処理が必要になります。
マウス操作が絡んでくるので、イベントハンドラを登録しなければなりません。コンストラクタの最後に、以下の3行を追加してください。
stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
マウスジョイントを作る
マウスボタンが押されたときに、マウスジョイントを作ります。作り方は回転ジョイントと同じくCreateJointメソッドを使います。
private function mouseDownHandler(event:MouseEvent):void {
// マウスボタンが押されたら、マウスジョイントを作る
mouseJoint = b2MouseJoint(world.CreateJoint(mouseJointDef));
}
マウスジョイントを切り離す
重要なのはマウスボタンが離されたときで、ここでジョイントを切らないと車が宙に浮いたままになってしまいます。ジョイントを切るには、以下のようにDestroyJointメソッドを使います。ジョイントを切ったら、mouseJointをnullにしておきます。
private function mouseUpHandler(event:MouseEvent):void {
// マウスボタンが離されたら、マウスボタンを切り離す
world.DestroyJoint(mouseJoint);
mouseJoint = null;
}
マウスジョイントの位置を更新する
最後に、マウスカーソルが動かされたときの処理です。mouseJointがnullでないとき、つまりジョイントが存在しているときだけ処理を実行します。実行する処理とは、現在のマウスカーソル位置をマウスジョイントに設定することです。これには、SetTargetメソッドを使います。
private function mouseMoveHandler(event:MouseEvent):void {
// mouseJointがnullでないときだけ処理を実行する
if (mouseJoint) {
// マウスが押された場所を物理エンジン内の座標系に変換する
var x:Number = event.stageX / DRAW_SCALE;
var y:Number = event.stageY / DRAW_SCALE;
// マウスジョイントのカーソル位置を更新
mouseJoint.SetTarget(new b2Vec2(x, y));
}
}
これで完成です。コンパイルすると以下のようなFlashができると思います。画面上部でマウスを適当にドラッグすると、車を持ち運ぶことができます。
ここまで編集してきたソースを、クラス名を変えて置いておきますので、参考にしてください。
まとめ
6種類あるジョイントの中から回転ジョイントとマウスジョイントを使い、マウスで持ち運ぶことのできる車を作りました。ジョイントを使うことで、より動きがあり、Box2DらしいFlashが作れると思います。今回説明し切れなかったジョイントについては、Box2Dのサンプルなどを参考にしてぜひ試してみてください。
次回は脱DebugDrawをテーマに、自前の描画を行う方法について説明したいと思います。