昨日ちょっとした時間が出来たので、物理演算エンジンであるBox2Dを触ってみました。
Box2Dは、もともとはC++で書かれた物理演算エンジンです。オープンソースであることから色々な言語に移植されており、中でもFlashのActionScriptのライブラリが非常に有名です。もちろん今回利用するのはJavaScript版、Box2D JSです。
http://box2d-js.sourceforge.net/
私は、C++版はもちろんのこと、ActionScript版すら触ったことがないので、下記に間違い等がたくさん含まれていると思います。もし間違いなどがあれば、ぜひコメントなりツイッター(@tkihira)なりでご連絡ください!
完成図はこちらです。クリックすると三角形が飛び跳ねます。真ん中の四角形にぶつかると画面が赤くなります。クリック連打すると左右の壁を飛び越してしまうので、そうなったら更新しない限り復活しません。
今回は簡単な使い方を知るためなので、デモのソースをそのまま利用する形で開発をします。ソースコードはこちらです。
<!DOCTYPE html>
<html><head><title>Box2DJS test</title>
<script src="prototype-1.6.0.2.js"></script>
<script src="box2d.js"></script>
<script src='draw_world.js'></script>
<script src='demo_base.js'></script>
<script>
(function() {
window.onload = function() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var world = createWorld();
var box = createBox(world, 250, 200, 10, 10);
var triangle = createMyShape(world, 250, 100);
var color = "#000";
(function tick() {
world.Step(1 / 60, 1);
var c = box.GetContactList();
if(c) {
color = "#f00";
setTimeout(function() { color = "#000"; }, 50);
}
ctx.fillStyle = color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawWorld(world, ctx);
setTimeout(tick, 1 / 60);
})();
document.body.onclick = function(e) {
triangle.WakeUp();
triangle.SetLinearVelocity(new b2Vec2(-600*Math.random()+300,-300));
};
};
var createMyShape = function(world, x, y) {
var ballSd = new b2PolyDef();
ballSd.density = 1.0;
ballSd.restitution = 0.8;
var v = 3;
var radius = 5;
ballSd.vertexCount = v;
for(var i = 0; i < v; i++) {
ballSd.vertices[i].Set(radius * Math.cos(Math.PI * 2 / v * i), radius * Math.sin(Math.PI * 2 / v * i));
}
var ballBd = new b2BodyDef();
ballBd.AddShape(ballSd);
ballBd.position.Set(x,y);
return world.CreateBody(ballBd);
};
})();
</script></head>
<body style="margin:0"><canvas id="canvas" width="500" height="300"></canvas></body></html>
最悪なことに、このライブラリはprototype.jsに依存しているようなので、3行目でprototype.jsを読み込んでいます。自分の書いた部分のコードにはprototype.jsの機能を使っていませんのでご安心ください。4行目はbox2d.min.jsみたいなもので、box2dはあまりに大量のソースコードがあるので、1つにまとめたものです。
5行目、6行目は標準バンドルでついてくるデモ用のコードを利用しています。5行目は描画系のツール関数があるのでそれを利用するために読み込んでいます(実質drawShapeのみしか利用していません)。6行目は便利な関数をいくつか利用するために読み込んでいます。
さて、ではonload関数を上から説明しましょう。まず13行目で、Box2Dの座標空間を作成します。createWorld関数はデモ用のJavaScript(demo_base.js)に含まれており、500x300の座標空間を作り、重力を下向きに設定し、地面と左右の壁を作成してくれます。次の14行目で四角形を定義しています。これもdemo_base.jsで用意されている関数で、座標空間と重心座標のxとy、それと高さと幅を指定します。固定しない場合は、この後の引数にfalseを入れると自由に動きます。
15行目で三角形を作成していますが、これは自分で定義した関数で、37行目から始まります。今回は多角形を作成するので、b2PolyDefを利用してまずは形を定義します。densityは密度(重さに関係する)、restitutionは反発係数(跳ね返りやすさ)、ここでは指定していませんが他にfriction(摩擦、デフォルト0.2)が指定出来ます。
43行目から47行目で頂点を設定します。頂点の設定には制限があり、
- 反時計回りで定義すること
- 頂点の数は8個まで(設定ファイルを書き換えれば増やせるが、重さに直結する)
- 凹型の図形は定義できない(地面と接触する際は、必ず隣り合う2頂点が接触する必要がある)
といった条件を守る必要があります。今回は正三角形を反時計回りで作成しているので、問題ありません。
定義した形を、47~49行目においてb2BodyDefに適用します。基本的にBox2Dではまず形を定義し、それらを組み合わせたBodyと呼ばれるオブジェクトを定義する形になります。今回は上で定義した正三角形を代入し、それをworldのCreateBodyに引き渡すことで、Box2Dの座標系に新しいオブジェクトを用意することが出来ます。
ではonloadに戻り、18行目からの毎秒呼ばれる関数を見てみましょう。world.Stepは物理演算を行い、内部の状態を1/60秒だけ進めます。この引数の意味は私はいまいち理解しておりません。もし詳細をしっている方がいればぜひ教えてください。
次に、20行目から24行目で、衝突判定を行っています。もしboxに衝突するオブジェクトがあった場合は背景を赤くします。ContactListを調べることで、実際にどのオブジェクトにぶつかったのかを確認することが出来ますが、今回は明らかに三角形しかないので、内容までは無視しています。
27行目で、現在の世界を描画します。drawWorldはdraw_world.jsに入っており、Box2Dの座標系とcanvasの座標系が1対1で対応している前提で描画されます。今回は最初の配置をcanvasのサイズに合わせて書いているので、問題なく描画されます。
31行目から34行目で、クリックされた際の処理を書いています。何も動きがない際など、あるオブジェクトに対して演算する必要がなくなるとエンジンが判断したタイミングで、エンジンはそのオブジェクトをスリープモードに移行します。スリープモードを解除してやらないとそのオブジェクトは計算されないので、まずは三角形のスリープモードを解除します。それからX軸を適当なランダム方向に、Y軸を上-300の速さで固定して、三角形に新しい速度を与えています。これでクリックされると上方向の左右どちらかに三角形が跳ね上がるシステムが出来るわけです。
world.Stepという一番肝心なところを調べていないのですが、今のところ使い方としてはこのような形で良いのではないかなと思っています。もし私が何か勘違いや、奇妙な方法を取っていると思われた場合は、ぜひ教えてください(@tkihira)。よろしくお願いします!