前回の第28回「物理演算エンジンBox2Dでボールを落とす」は、全体を通してなんとかひとつのオブジェクトを自由落下させた。今回は床を加えて、ボールを弾ませる。ようやく物理演算シミュレーションらしくなってきた。
床をつくる
前回つくったボールは、落ちたままぶつかるものが何もないので、ステージ下端に消えた。そこで、床を加えよう。床をつくる関数(createStaticFloor())は、Box2Dを初期化する関数(initializeBox2D())から呼び出す。引数には、ステージの幅と高さを加えた。また、床の矩形情報は、Rectangleオブジェクトで変数(floor)に納めている。
床の剛体を定義するb2BodyDefオブジェクトも、ボールと同じ関数(defineBody())でつくる。ただし、床は動かないので、引数に与える剛体の種類がb2Body.b2_staticBody定数になる(再掲第28回表1)。人形役の目に見える床は、これも新たに定めた関数(createVisualFloor())で矩形のShapeオブジェクトをつくって返した。中身は、BitmapとShapeでオブジェクトは異なるものの、ボールの関数(createVisualBall())と基本的に同じだ。
第28回表1 剛体の種類を定めるb2Body定数(再掲)
マウスイベント | 剛体の種類 | 値 |
b2_dynamicBody | 動的 | 2 |
b2_kinematicBody | キネマティック | 1 |
b2_staticBody | 静的 | 0 |
第28回コード1「Box2Dでボールを自由落下させる」に上記のスクリプトを書き加えて試すと、床がデフォルトのステージ左上角に取り残されてしまう(図1)。これは、Box2Dの物理空間のシミュレーションに、まだ床が加わっていないためだ。
すべての剛体を物理シミュレーションする
Ticker.tickイベントのリスナー(tick())から呼出した物理シミュレーションを進める関数(update())は、b2World.GetBodyList()メソッドで物理空間に加えられた初めの剛体を取り出している。そして、黒子の物理演算の結果を人形役のボールに当てはめて、ボールは自由落下した。それで終わってしまうと、床についての物理シミュレーションが行われない。だから、床がデフォルトの位置に取り残されたのだ。
物理空間のつぎの剛体を取り出すには、ボールのb2Bodyオブジェクトにb2Body.GetNext()メソッドを呼び出せばよい。これで、つぎの剛体である床のb2Bodyオブジェクトが得られる。なお、初めの剛体を取り出すのはb2Worldクラスのb2World.GetBodyList()、つぎの剛体からはb2Bodyクラスのb2Body.GetNext()メソッドを用いることに注意してほしい。
今のところ、剛体はボールと床のふたつだ。けれど、次回はボール数を増やしたい。そこで、whileループを用い、ありったけのb2Bodyオブジェクトをb2Body.GetNext()メソッドで順に取出して物理シミュレートすることにした。最後の剛体を取り出し終わると、b2Body.GetNext()メソッドはnullを返して、ループから抜ける。
これでどうだとばかり、アニメーションを確かめると、床は確かにステージ下に表れた。ところが、ボールは床をすり抜けるイリュージョンが繰り広げられる(図2)。私たちは人形を見て、ボールとか床とかを捉えている。だが、物理演算する黒子は、剛体の座標しか知らされていない。かたちがわからないのだから、ぶつかり合うシミュレーションのしようがないのだ。
まだ物理シミュレーションとしてでき上がっていないものの、参照の便を考えて、ここまでをコード1としてまとめておこう。つぎは、黒子にふたつの剛体のかたちを教える。
かたちを定める
剛体の大きさやかたちはフィクスチャというオブジェクトで決める。例によって、その定義から始めなければならない。b2FixtureDefオブジェクトをつくり、そのb2FixtureDef.shapeプロパティにBox2Dのシェイプ(b2Shapeおよびそのサブクラスの)オブジェクトで大きさとかたちを与える。正円はb2CircleShape()コンストラクタの引数に半径を渡せばよい。矩形はb2PolygonShapeオブジェクトに、b2PolygonShape.SetAsBox()メソッドで幅と高さを定める。なお、幅と高さは原点(中心)から端までの長さなので、矩形の大きさの半分とする。
フィクスチャを定義する関数(defineFixture())は新たに定めた。ボールと床をつくる関数(createDynamicBall()とcreateStaticFloor())から、それぞれシェイプオブジェクトを引数に渡して呼び出す。そして、剛体のb2Bodyオブジェクトにフィクスチャオブジェクトを与えるのが、b2Body.CreateFixture()メソッドだ。b2FixtureDefオブジェクトを引数とする。そこで、剛体をつくる関数(createBody())の引数にb2FixtureDefオブジェクト(fixtureDef)が加えられ、それをメソッドに渡して呼び出すことにした。
黒子のBox2Dに、ふたつの剛体の大きさとかたちを教えた。これで、互いのぶつかり合いがわかるはずだ。アニメーションを見ると、ボールは床に落ちても弾みもせず、体操の完璧な着地のごとく、床に吸いつくように止まる(図3)。そろそろ物理エンジンの性格がわかってきたろう。ボールが弾む材質だと教えていないからだ。
材質を定める
物理演算エンジンは教えたことしかやらない。しかし、教えさえすれば、仕事は完璧にこなす。あと、もう少しの辛抱だ。材質はフィクスチャの定義に定める。b2FixtureDefオブジェクトには、つぎの表1のように3つのプロパティが備わっている[1]。それらを、以下の新たな関数(setFixture())によりb2FixtureDefオブジェクトに与えた。
これでようやく、動的なボールの剛体を静的な床の剛体の上に自由落下させて、弾ませる物理演算シミュレーションができた。アニメーションを確かめると、ボールが床の上で軽やかにバウンドする(図4)。弾性は大きめにしたので、弾みがいい。摩擦はボールの数をもう少し増やさないと、違いがわかりにくいだろう。
今回はここまでとしよう。JavaScript全体はコード2にまとめた。また、jsdo.itにもサンプルコードを掲げてある。次回は、たくさんのボールを、位置や大きさを変えながらつぎつぎと落とす。もっとも、ここまでできてしまうと、Box2Dについて新たに学ぶべき知識はさほどない。そこで、スクリプトの改善も少し加えるつもりだ。