box2dというのは、いわゆる二次元の物理演算ライブラリです。サイコロを転がすようなのは3次元の物理演算。二次元というのは、まさに新幹線ゲームの10円玉のような、奥行きがない世界のコト。
もともとActionScriptで使い慣れていたbox2dなので、まあ多少の変更ならなんとかなると思っていたら、なかなか世の中甘くはないですな。
とりあえず、以下のbox2dwebのページにアクセスしてみてください。デモが見られます。
http://code.google.com/p/box2dweb/
簡単に説明しますと、外枠に緑色の部分がありますね。これが動かない壁。そこに円とか長方形とかが落下して跳ねる、それだけのデモです。
この画像のような表示になりましたか?それは結構!しかし…
同じページをAndroidタブレットのDolphin HDブラウザーで表示させたのがこちら。
うまく行くときもありますが、何度かリロードするとこんな具合。壁が斜めになってますね。欠陥住宅だ!アネハが作ったのか?いや、あの人が作ったマンションは大地震でも倒れなかったぞ!
えーっと、何の…。あーそうそう、動かないハズの壁がなぜかDolphin HDブラウザは動いちゃうんですよ。壁だけでなく、ちょっとした固定物も何かの拍子に回転したり動いたり。おかげで新幹線ゲームは成立しませんでしたよ、ええ。
どうも処理中に0で割ってるとか、なにか計算精度の丸め方がおかしいとか、JavaScript側の問題のような気がします。ちょっと今は調べる気力がない。困ったもんだ。
こういうのは些細な問題で、HTML5が標準化されれば解決する的な話をしてるのは、だいたい上の方にいる人なんですよね。IE6が何をもたらしたか思いだせっちゅうねん。苦労するのは下の方の人間なんだぞー!そもそも、ブラウザーごとに動作が異なりリファレンスマシンも存在しないような環境に誰が投資すんねん!まあ、まとまらないことを見越した誰かさんが、自分の製品の圧倒的なシェアを利用して、かつてのマイクロソフトみたいなことをやろうとしてるんでしょうな。
さて次がiPhone用のChrome。10円玉がアウトホールに落ちても消えない。
box2dにはsensorというオプションがあって、これとヒットしたオブジェクトを教えてくれるというもの。10円玉がアウトホールに落ちるところで利用しているんですが、なぜかiPhoneのChromeでは機能しない。これもJavaScriptの実装の問題のような気がするんですが、よくわからない。仕方がないので、ヒットの検出部分は自前で実装しました。そしたら動くんでやんの。そりゃそうだ。
もっと不可解なのがパソコン用のInternet Explorer 9で、こちらも10円玉がアウトホールに落ちない。ところが、デバッグしようとF12キーを押して開発者ツールを表示すると直ってしまう。まあこのゲームは事実上、iPhoneやiPad専用なのでいいけれど、なんだかよくわからないですね。ゲイツ先輩の考えることは私には難しいです。
と、問題点はそんなところなので、昔のv2.0.1からv2.1.a.3の違いなどをコードも含めてbox2dの使い方を解説をしておきましょう。
ネームスペース
まず最初はネームスペース問題。JavaScriptにはネームスペースなんてハイカラな仕様がないので、関数名の衝突を避けるためにライブラリ側でてんでバラバラの実装を行っています。ブラボー!box2dwebの場合は各クラス名に名前空間的なものを追加してやらないと使えない。たとえばvar g = new b2Vec2(0, 30.0);
ではだめで
var g = new Box2D.Common.Math.b2Vec2(0, 30.0);
とライブラリのフルパス的なものを書き加えてやらないといけない。でもそういうのは面倒なので、
var b2Vec2 = Box2D.Common.Math.b2Vec2;
みたいなのを最初に定義して、あとはこのb2Vec2を使います。仕方がない(ループの中とかタイトなところではやらないけど)。で、以下のコードはこういう定義をどこかでやってる前提なんでよろしくね。
世界を作る
最初にやるべきことは光を作ること、は飛ばして世界を作ることです。こんな感じ。var world = new b2World(new b2Vec2(0, 9.8), true);
下方向に9.8とかいう重力の働く世界を作って、worldに代入せよと。最後のtrueは、この世界で動かなくなった怠け者はそのまま凍り付けというオプション。処理が軽くなります。殴ると起きます。
物体を作る
次に、この世界に登場させる物体を作るわけですが、こいつがややこしい。まずフィクスチャーを作るための定義を準備します。フィクスチャーというのは、この2D世界に生成される物体です。その定義とは、物体の密度とか摩擦係数とか反発係数とか、そういう物理的な特性をまとめたヒナ形です。雛形あきこ好きだったなあ。
var boxFixDef = new b2FixtureDef();//フィクスチャーの定義を生成
boxFixDef.density = 1.0;
boxFixDef.friction = 0.5;
boxFixDef.restitution = 0.5;
これだけだと手触りしか決まってないので、形状を生成してフィクスチャー定義のshapeプロパティにセットします。ここでは一辺が10メートルの正方形を設定。あ、この世界では1が1メートルなんでよろしくね。
var boxShape = new b2PolygonShape();
boxShape.SetAsBox(10, 10);
boxFixDef.shape = boxShape;
これで正方形フィクスチャーを作るための定義が完成しました。型抜きの型を作ってるわけですよ。
ボディを作る
さて、このフィクスチャー定義を使ってworldに形状をドカンドカン追加できるかというとさにあらず。フィクスチャーはb2Bodyにしか追加できません。b2Bodyとはなんぞや。そうだなあ。PhotoShopでいうところのレイヤーみたいな。アニメにおける透明なセル的な。色んなフィクスチャーをまとめるベースのようなもの。こいつを動かすとすべてのフィクスチャーが動き、回せば回る。
そこでこのb2Bodyなるもの定義を作ります。また定義かよ。
必要なのは座標とか傾きとか動く物体か静止物体か。他にもちょこちょこあるけどまあ割愛。
var boxBodyDef = new b2BodyDef();
boxBodyDef.position.Set(0 , 0);
boxBodyDef.type = b2Body.b2_dynamicBody;
typeのb2_dynamicBodyってのは動く物体だよって意味で、固定物はb2_staticBodyを指定します。
やれやれ長かった。これでやっと物体を生成できる。
var boxBody = world.CreateBody( boxBodyDef );//ボディをworldに生成し…
boxBody.CreateFixture( boxFixDef );//フィクスチャーを追加する
フィクスチャーはボディにいくつでも追加できます。たとえば新幹線ゲームでは、10円玉が動くステージは1つのボディに沢山の通路フィクスチャーが貼り付けてあります。
おおっと、でもこれじゃあ世界は止まったままですね。動かしましょう。
world.Step( 1 / 30);
これで1/30秒ごとに世界は計算され、結果がボディに反映されます。画面で確認できましたか?できませんよね。だってCanvasに書いてないもん!
box2dはあくまで計算でして、画面の描画はやってくれません。デバッグドローという機能を使えば確認はできますが、まあその辺は自力でなんとかしてちょ。
とりあえずボディの座標を拾うなら、
boxBody.GetPosition();
を。角度が知りたければ、
boxBody.GetAngle();
を。このふたつが分かれば、あとは画像の座標をセットすれば何か表示されるでしょう。ただし、スケールを考慮すること。計算はメートルで行われているので、それを画面のピクセルに対応させる必要がある。そこも自分で考えよう!
というわけで次はEaselJS。