物理演算-Box2d その1

Box2dWebの世界へようこそ!

今回から数回に渡って、私の備忘録も兼ねてenchant.jsでBox2dWebを利用する手順を書いてゆこうと思います。
先日も書いた通り、enchant.jsには@kassy708さんが作成されたbox2d.enchant.jsがpluginとして提供されています。しかしこのプラグインがサポートしているのは、静的・動的オブジェクト同士の衝突のみであり、もうひとつのキモであるジョイントについてはサポートされていません。
ここでは、直接Box2dWebを利用し、ジョイントの動作を楽しむにはどうしたらいいかをまとめてゆきたいと思います。

物理エンジンのプログラミングは以下のような流れになります。
1.物理演算を行う空間を用意する
2.オブジェクトを空間内に配置する
3.演算を行う
4.画面に結果を反映させる
 3〜4の繰り返し

まずは下準備。
index.htmlで読み込むプラグインはBox2dWeb-2.1.a.3.jsとbox2d.enchant.jsです。
「何でbox2d.enchant.jsを?」と思われると思いますが、その理由はおいおい説明します。

次は物理演算を行う空間を用意します。

enchant();
var WORLD_SIZE = 320;

window.onload = function() {
    var main = new Game(WORLD_SIZE, WORLD_SIZE);
    main.onload = function() {
    };
    main.start();
};

お馴染みのテンプレですね。main.onloadの中に以下の2文を挿入します。

       var gravity = new b2Vec2(0, 9.8);
       var world = new b2World(gravity, true);

まずひとつ目の変数gravityですが、これ自身はx軸及びy軸にかかる重力を定義しています。
ついでworld、こちらが空間そのもの設定した変数です。第一引数は空間内にかかる重力、そして第二引数は即座に物理演算を実行するかどうかを設定するためのフラグです。
第一引数にgravityが入っていることから想像できると思いますが、worldの世界はy軸に常に9.8の引力がかかっている――つまり1Gの世界だということです。gravityの引数を(0, 0)にすれば無重力状態となります。
第二引数は常にtrueでいいでしょう。何かスイッチを入れたら物理演算が始まる、というような場合はfalseでもいいでしょうが、あとで変更する手段を私が知らないのでここは常にtrueで。(同様にあとで重力を変更する方法も知らないです、面目ない)

次はオブジェクトを配置しましょう。
ですがその前にWORLD_SIZEの下に以下の2文を足して下さい。

var WORLD_HARF = Math.floor(WORLD_SIZE / 2);
var WORLD_SCALE = 32;

WORLD_HARFは別になくてもかまいません。あるとあとあと便利かな、という程度のものです。
WORLD_SCALEについてはあとで説明します。

それではオブジェクトを配置しましょう。
今回はボールを画面上に配置し、それが上から落ちてゆく様を眺めることにしましょう。

	// ボールの生成
	var circleDef = new b2BodyDef();
	circleDef.type = b2Body.b2_dynamicBody;
	circleDef.position.Set(WORLD_HARF / WORLD_SCALE, 16 / WORLD_SCALE);
	var circleBdy = world.CreateBody(circleDef);
	
	var circlePly = new b2CircleShape(16 / WORLD_SCALE);
	
	var circleFix = new b2FixtureDef();
	circleFix.shape = circlePly;
	circleFix.density = 1;
	circleFix.friction = 0.3;
	circleFix.restitution = 5;
	circleBdy.CreateFixture(circleFix);	

さて。小難しくなってきましたね。
オブジェクトは4つの要素から成り立っています。
b2BodyDefで生成される基本情報、CreateBodyで生成される実体、b2CircleShapeで生成される形態、b2FixtureDefで生成される......これは何と表現すべきだろう。
まぁ取り敢えず上から見てゆきましょうか。
b2BodyDefで生成されたcircleDefでは、オブジェクトのタイプ(静的/動的)と中心位置を空間のどこにするかを設定します。
オブジェクトのタイプはtypeプロパティで設定します。設定値は静的(b2_staticBody)、動的(b2_dynamicBody)のいずれかで、静的がデフォルトとなっているようです。
次はpositionのSet関数です。注意してもらいたいのは、この関数はオブジェクトの中心位置を空間のどこに置くかを定める関数だということです。
enchant.jsのSpriteを画面中央に設置する場合、

sprite.x = WORLD_HARF - sprite.width / 2;
sprite.y = WORLD_HARF - sprite.height / 2;

と書きますが、Box2Dのオブジェクトの場合は

object.position.set(WORLD_HARF, WORLD_HARF);

と書きます。

......ごめんなさい、ウソ書きました。
正しくはこうです。

object.position.set(WORLD_HARF / WORLD_SCALE, WORLD_HARF / WORLD_SCALE);

はい。何故WORLD_SCALEで割る必要があるのでしょうか。
物理演算を行う空間の中の単位は、距離はメートル、重さはキロ、時間は秒で表します。
重さ、時間はいいとして、1ピクセル1メートルなどとしたらとんでもなく馬鹿でかいオブジェクトが出来上がります。当然、演算には時間がかかります。
ですのでピクセル単位を空間内の現実的な値に落とし込むことが必要になります。それがWORLD_SCALEなのです。ちなみに値は任意です。
では改めて以下の部分を見てみましょう。

	// ボールの生成
	var circleDef = new b2BodyDef();
	circleDef.type = b2Body.b2_dynamicBody;
	circleDef.position.Set(WORLD_HARF / WORLD_SCALE, 16 / WORLD_SCALE);

生成したのは動的オブジェクト。そして設置位置の中心点はx軸が画面中央、y軸が上から16ピクセルですね。
それでは次の1文。

	var circleBdy = world.CreateBody(circleDef);

基本情報を元に、空間上にオブジェクトを配置しました。
ただ、現時点では大きさや重さ、摩擦係数や反発係数が未定義です。
「空間のここに何かあるよ!」という程度ですね。
はい次。

	var circlePly = new b2CircleShape(16 / WORLD_SCALE);

ここでは円形オブジェクトの定義をしています。引数は半径です。WORLD_SCALEで割るのを忘れないで下さい。
いよいよ最後です。

	var circleFix = new b2FixtureDef();
	circleFix.shape = circlePly;
	circleFix.density = 1;
	circleFix.friction = 0.3;
	circleFix.restitution = 5;
	circleBdy.CreateFixture(circleFix);	

fixtureを定義します。
shapeにはb2CircleShapeで生成したcirclePlyをぶちこんでいます。
densityでは重さ、frictionでは摩擦係数を、restitutionでは反発係数を定義してます。
そして最後にcircleBdyに統合します。
はい、これでボールが出来上がりました。

次は物理空間の時間を進めましょう。

	// メインループ
	main.rootScene.addEventListener(Event.ENTER_FRAME, function() {
	    var timeStep = 1 / main.fps;
	    var velocityIterations = 1;
	    var positionIterations = 1;
	    // 物理空間の更新
	    world.Step(timeStep,velocityIterations,positionIterations);
	});

空間を更新する関数そのものはStepです。その引数ですが……
変数timeStepは何fps動かすかを代入しています。あとのふたつの変数は……ごめんなさい、よくわからない。

さて。これであとは実行すれば物理空間でボールが落ちてゆくさまを見ることができます。
はい、実行!
――ほら! 落ちてった、落ちてったよ、ボール!

え? 何も表示されないって?
はい、ごめんなさい。
最後に大切な画面への描画を忘れていました。
ボールを作成した後に以下のように追加して下さい。

	// デバッグ用スプライト
	var debugSprite = new Sprite(WORLD_SIZE, WORLD_SIZE);
	debugSprite.image = new Surface(WORLD_SIZE, WORLD_SIZE);
	main.rootScene.addChild(debugSprite);
	var debugDraw = new b2DebugDraw();
	debugDraw.SetSprite(debugSprite.image.context);
	debugDraw.SetDrawScale(WORLD_SCALE);
	debugDraw.SetLineThickness(1.0);
	debugDraw.SetAlpha(1);
	debugDraw.SetFillAlpha(0.4);
	debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
	world.SetDebugDraw(debugDraw);

そしてメインループ、物理空間の更新の後に以下のように追加。

	    // debug画面の更新
	    world.ClearForces();
	    world.DrawDebugData();

……まぁこれらの関数についてはおなじないということで。

さて。これで実際にボールが落ちてゆくさまを見ていただくことが出来たかと思います。
通常、enchant.jsで物が落ちてゆく様子を描画するには、該当スプライトのyプロパティに値を追加してゆく必要があります。しかし今回yプロパティは操作していません。それどころか、スプライトはデバッグ用のものしか用意していないのです。重力に引かれ落ちてゆく、そのことがわかるかと思います。
jsdo.itにもコードをアップしました。そちらもご覧下さい。
物理演算 Box2d その1