enchant.js + box2d で お手軽に物理演算を試してみる。
■はじめに

enchant.jsはゲームやアプリを作成できるHTML5 + JavaScriptのフレームワークです。
http://enchantjs.com/ja/


Box2dは2次元の物理エンジンで摩擦とか加速度とかの物理演算を行なうライブラリです。
ただし、サポートしているのは力を加えても変形しない剛体のオブジェクトのみです。
ゆっくりのようなやわらかい饅頭などを表現するのは厳しいです。

もともとのBox2dはC++で実装されているライブラリで、Java,C#、JavaScriptなどの多くの言語に移植されています。
JavaScript版のBox2dはbox2dWebという名前で下記のページからダウンロードできます。
http://code.google.com/p/box2dweb/



enchant.jsからbox2dwebを使用するにはPhySpriteというプラグインを使用します。
PhySpriteを用いることで容易にbox2dwebを扱うことができます。

しかしながら、enchant.jsに入っているPhySpriteは単純なオブジェクトの操作しか行なえません。
最新のPhySpriteを下記のページからダウンロードすることで、ジョイントや多角形のオブジェクトも利用することができるようになります。
http://kassymemo.blogspot.jp/2011/12/enchantjsphysprite.html





■enchant.js v0.7.0 と PhySprite1.52を使用する場合の注意点。
以下の条件を全て満たす場合に、正常に表示されない不具合がでる。
・enchant.js v0.7.0(最新)を使用する
・PhySprite1.52(最新)を使用する
・enterframeなどの定期処理でPhySpriteを継承するオブジェクトのangleを更新しつづける。
・sceneのx座標、y座標を修正する。

不具合の例:
http://needtec.sakura.ne.jp/PhySpriteNgSample/ng.html
この例では、ボールが分身しているようにみえます。

正しい例:
http://needtec.sakura.ne.jp/PhySpriteNgSample/ok.html
ボールが分身したりなどしないですね。

これはPhySpriteのinitializeメソッドを修正することで正しい動作をします。

b0232065_632134.png


従来のコードではENTER_FRAMEで移動と回転が一緒にできないとあり、回転は2回に1回しか行なわれていません。。
正しい例で使用しているコードでは、これをコメントアウトして、回転と移動を一緒のタイミングで行なっています。

もともと、移動と回転が一緒にできないというはenchant.jsのバグです。
https://github.com/wise9/enchant.js/issues/80

この不具合はenchant.js v6.0では回収されているので、最新のenchant.jsを使用する場合は、回転と移動は同時に行なわなければならない。

■enchat.jsのステージからマウスのポインタが外れたことを検知するにはどうするか?
下記のようにenchant-stageをIDとするブロック要素にonMouseOutのイベントを記述してやればよい。

<div id="enchant-stage" style="width:640px;height:480px;" onMouseOut = "onMouseOut(this)">


■オブジェクト同士の衝突判定
PhySpriteプラグインにcontactメソッドが存在するが、それよりも、Box2dのBeginContact、EndContact、PostSolveを直接捕まえたほうが使いやすいです。

BeginContactはオブジェクトが接触したとき1回だけ実行されます。
EndContactはオブジェクトが離れたとき一回だけ実行されます。
PostSolveは衝撃度が引数で与えら取得できます。この値をみてどのくらいの威力でぶつかったか判断できます。
また、PostSolveはオブジェクトが接触している間、複数回実行される可能性があります。
この場合は引数の衝撃度をみて、あまりに小さい値の場合は無視するといいでしょう。

以下のようにBox2Dの関数を直接しようしてください。
また、Box2dのオブジェクトからPhySpriteのオブジェクトを取得するにはGetUserData()を使用します。

// オブジェクト同士の衝突用イベントリスナー
var b2Listener = Box2D.Dynamics.b2ContactListener;

//Add listeners for contact
var listener = new b2Listener;

/**
* オブジェクト同士が接触した場合、一度だけ行なわれる処理
* @memberOf listener.BeginContact
* @param {Object} contact 接触情報
*/
listener.BeginContact = function(contact) {
var a = contact.GetFixtureA().GetBody().GetUserData();
var b = contact.GetFixtureB().GetBody().GetUserData();
console.log(a.objectname + "と" + b.objectname + "が接触した" );
};

/**
* オブジェクトの接触が終わった場合、一度のみ発生するイベント
* @memberOf listener.EndContact
* @param {Object} contact 接触情報
*/
listener.EndContact = function(contact) {
var a = contact.GetFixtureA().GetBody().GetUserData();
var b = contact.GetFixtureB().GetBody().GetUserData();
console.log(a.objectname + "と" + b.objectname + "が離れた" );
};
/**
* オブジェクト同士の接触中に発生するイベント
* このイベント内で接触時の衝撃が取得できる。
* @memberOf listener.PostSolve
* @param {Object} contact 接触情報
* @param {Number} impulse 衝撃
*/
listener.PostSolve = function(contact, impulse) {
var impulse = impulse.normalImpulses[0];
// 一度の接触で複数回実行される可能性がある。
// 特定の大きさ未満はむしするようにしたほうがよい
//if (impulse > 20) return;
var a = contact.GetFixtureA().GetBody().GetUserData();
var b = contact.GetFixtureB().GetBody().GetUserData();
console.log(a.objectname + "と" + b.objectname + "は衝撃" + impulse );

};

world.SetContactListener(listener);

実際の例は次のとおりです。
http://needtec.sakura.ne.jp/simplewater/contact.html

■実際に衝突はしないが衝突の検知はしたい場合
実際に衝突はしないが、オブジェクトが接触したことは検知したい場合はSetSensorにTrueを指定します。

var ball = new PhyCircleSprite(16, DYNAMIC_SPRITE,0.5, 0.5, 1.8, true);
ball.image = new Surface(32, 32);
ball.image = game.assets['./image/maricha.png'];
ball.frame=0;
ball.x = 100;
ball.y = 250;
ball.objectname = "まりちゃ";
ball.getBody().SetSensor(true);
game.rootScene.addChild(ball);

これにより、BeginContact,EndContactは発生するが、PostSolveが発生せず重なって表示することが可能になります。

実際の例は次のとおりです。
http://needtec.sakura.ne.jp/simplewater/contact2.html


■特定のオブジェクトだけに衝突しない場合
この場合はフィルターを使用します。
Box2dにおけるフィルターの概念は以下を参考にしてください。
http://ja.softuses.com/122188

ball(まりちゃ) と ball2,ball3(れいみゅ)は衝突するが、れいみゅどうしは衝突しない場合は以下のような実装になります。
filterのcategoryとmaskBitsを旨く組み合わせてグループ化してください。
Sensorと違い、れいみゅ同士がぶつかった場合にBeginContactすら発生しません。

var filter;
var ball = new PhyCircleSprite(16, DYNAMIC_SPRITE,0.5, 0.5, 1.8, true);
ball.image = new Surface(32, 32);
ball.image = game.assets['./image/maricha.png'];
ball.frame=0;
ball.x = 100;
ball.y = 250;
ball.objectname = "まりちゃ";
game.rootScene.addChild(ball);

var filter;
var ball2 = new PhyCircleSprite(16, DYNAMIC_SPRITE,0.5, 0.5, 1.8, true);
ball2.image = new Surface(32, 32);
ball2.image = game.assets['./image/reimyu.png'];
ball2.frame=0;
ball2.x = 220;
ball2.y = 250;
ball2.objectname = "れいみゅ1";
filter = ball2.getBody().GetFilterData();
filter.categoryBits = 0x0002;
filter.maskBits = 0xFFFD;
ball2.getBody().SetFilterData(filter);
game.rootScene.addChild(ball2);

var ball3 = new PhyCircleSprite(16, DYNAMIC_SPRITE,0.5, 0.5, 1.8, true);
ball3.image = new Surface(32, 32);
ball3.image = game.assets['./image/reimyu.png'];
ball3.frame=12;
ball3.x = 120;
ball3.y = 250;
ball3.objectname = "れいみゅ2";
filter = ball3.getBody().GetFilterData();
filter.categoryBits = 0x0002;
filter.maskBits = 0xFFFD;
ball3.getBody().SetFilterData(filter);
game.rootScene.addChild(ball3);


実際の例は次のとおりです。
http://needtec.sakura.ne.jp/simplewater/contact3.html

■まとめ
enchant.js+box2dWebを使用することにより、比較的容易にピタゴラススイッチ的なアプリケーションを作成することが可能になります。

また、box2dwebそのもののサンプルはなくても、別言語でのBox2dのサンプルが多いのも魅力ですね。
[PR]
by mima_ita | 2013-11-06 06:14 | box2d
<< enchant.js + bo... PHPで固定少数点の計算をする >>



実験ですお
検索
カテゴリ
最新の記事
.NET4.5におけるasy..
at 2014-07-02 00:46
.NETでTwitterを検..
at 2014-06-29 00:49
Redmineのプラグインで..
at 2014-06-28 03:29
IO.popenのwrite..
at 2014-06-28 03:25
RedmineのWikiでU..
at 2014-06-28 03:16
以前の記事
最新のトラックバック
その他のジャンル
ブログパーツ