【enchant.js】Hello Physical World vol.1【box2d】

最近、このブログに「box2d.enchant.js」で検索かけて辿り着く方が多いようです。*1
こんな記事も書いているわけですからさもありなん。しかし改めて読み返すと……結構つっけんどんですよね。もっと初歩からキチンと書いとけや、と。

というわけで、box2d.enchant.jsについて改めて記事を起こしてみることにしました。題して「Hello Physical World」! はじまり、はじまり~!

そも、Box2Dとは何なのでしょうか。
Wikipediaには「質量・速度・摩擦といった、古典力学的な法則をシミュレーションするゲーム用の2D物理演算エンジン」とあります。ゲーム用の2D物理演算エンジン、いいですね! 実際に使ってもらうと分かるのですが、プログラマーが特に処理を書かなくても、モノは落ちてくれるは床で跳ねるわジャンプの軌跡もなめらかと、一見いいこと尽くめですw もちろん、それ故の制約ってのもあるんですが、それはオイオイ……。

で、このBox2Dをenchant.jsでも手軽に使えるようにしたのがbox2d.enchant.jsなのです。
box2d.enchant.jsはbox2dwebをラップしたpluginで、@さんが開発されたPhySprite.enchant.jsがベースとなっています。*2

enchant.js+Box2Dというと、日経ソフトウエアの記事を思い出される方もいらっしゃるかと思います。後日ITproにも転載され、どうやらムックにも再掲されているようです。こちらで使われているのはBox2DJSというライブラリです。

さて。
物理演算、物理エンジンと聞くと何だかものすごそうで、尻込みしてしまうかと思います。でも気後れする必要はありません。確かにライブラリを作るとなると大変ですが、そのライブラリを利用する限りにおいてはそんなことは全くないのですから。
それではさっそく、ひとつコードを書いてみましょう。

enchant.jsでBox2Dを使うには、まずindex.htmlでBox2dWeb-2.1.a.3.jsとbox2d.enchant.jsを読み込むよう指示する必要があります。

<!doctype html>
<html>
<head>
    <script src='enchant.js'></script>
    <script src='plugins/libs/Box2dWeb-2.1.a.3.js'></script>
    <script src='plugins/box2d.enchant.js'></script>
    <script src='main.js'></script>
    <title>Box2D Sample</title>
</head>
<body>
</body>
</html>

次はコーディングですが、その前にまずmain.jsのテンプレートを用意します。

enchant();

window.onload = function() {
    var core = enchant.Core(320, 320);
    core.onload = function() {
        //物理世界の構築
        var world = new PhysicsWorld(0.0, 9.8);
        
        core.rootScene.onenterframe = function(e) {
            //物理世界の時間を進める
            world.step(core.fps);
        };
    };
    core.start();
};

構築した物理世界の時間を1ステップごと進め、その結果を画面上のオブジェクトに反映させるのが、Box2Dでのゲーム開発の基本です。「結果を画面上のオブジェクトに反映させる」などと書くと身構えられちゃいそうですが、box2d.enchant.jsではそのへんもキチンと考慮されており、通常のSprite配置と同じ感覚で扱えます。

それではこのテンプレートの解説に参りましょう。ポイントは2つです。
まずは7行目のPhysicsWorldです。このクラスをインスタンス化することで物理世界を構築します。引数は第一項目がx軸の重力、第二項目がy軸の重力です。x軸の重力は通常ありませんので0.0です。y軸は1G=9.8m/s^2ですので9.8を入れます。
もうひとつがonenterframe内で呼び出されているPhysicsWorld.stepメソッドです。引数は物理世界のフレームレートです。ゲームのフレームレートを入れておけばいいでしょう。

準備はここまでです。
次は実際にリンゴを床に落としてみましょう。

enchant.jsではゲーム画面にリンゴを登場させる際、Spriteでオブジェクトを生成し、そこにリンゴの画像を貼り付けSceneに加えます。

var apple = new Sprite(16, 16);
apple.image = core.assets['icon0.png'];
apple.frame = 15;
core.rootScene.addChild(apple);

box2d.enchant.jsでも同じです。リンゴを登場させるには以下のように記述します。

var apple = new PhyCircleSprite(8, enchant.box2d.DYNAMIC_SPRITE, 1.0, 0.5, 1.0, true);
apple.image = core.assets['icon0.png'];
apple.frame = 15;
core.rootScene.addChild(apple);

異なるのはリンゴを生成するクラスがSpriteではなくPhyCircleSpriteなところですね。
まずは通しでコードを書いて、実際にリンゴが自由落下するところを見てみましょう。

たったこれだけのコードでリンゴが落ちていってくれました。
次はこのコードに床を足して、リンゴを跳ねさせてみましょう。

お気付きでしょうか。
リンゴと床を生成するクラスが異なっています。名前から分かる通り、PhyCircleSpriteは円を生成するクラス、PhyBoxSpriteは矩形を生成するクラスです。ともにPhySpriteをベースとしています。PhySpriteとはSpriteの拡張クラスで、物理世界の動きをSpriteに伝える役目を担います。PhySpriteを直接扱うことはありませんが、メンバは当たり前のように利用しますので注意して下さい。

では、PhyCircleSpriteについて解説してゆきましょう。
第一項目は円の半径です。実際に作成されるSpriteの縦横は、指定した値の倍の長さになります。
第二項目は生成したオブジェクトの、物理世界でのタイプを指定します。タイプには以下のふたつがあります。*3

  • enchant.box2d.DYNAMIC_SPRITE
    • 動的なオブジェクトです。一言で言うと動きます、動かせます。
  • enchant.box2d.STATIC_SPRITE
    • 静的なオブジェクトです。動きません。

第三項目は密度です。重さですね。同じ容積のオブジェクトでも密度が違えば動きが変わります。
第四項目は摩擦係数で、値は0.0~1.0の範囲を取ります。
第五項目は反発係数で、一般には0.0~1.0の範囲で指定します。1.0以上も指定可能です。
第六項目は物理演算を最初から適用するかどうかを指定します。trueであればゲームが始まった途端オブジェクトが重力に従って落ちてきますが、falseの場合は何らかの契機があるまで静止状態になります。
次はPhyBoxSpriteですが、第一項目と第二項目はそれぞれSpriteの幅と高さを指定するもので、続く項目はPhyCircleSpriteの第二項目以降と同じです。

何だか初回から盛りだくさんになっていますが、もうすこしお付き合い下さい。

imageやframeといったプロパティはいいですよね。これはSpriteのプロパティと同じものです。
ですがpositionというプロパティはSpriteにもEntityにもNodeにもありません。このプロパティはPhySpriteのもので、中心座標ベクトルを指定します。いいですか、中心座標です。Spriteの左上座標ではありません。

さて。
今回のソースはこれでひと通りの説明が終わりました。
でも、まだまだ謎なことばかりですよね。反発係数1.0と0.5の物体がぶつかったらどうなるのか、摩擦係数が違うもの同士はどのように影響を与えるのか……。
ゆっくりとですが、いろいろと実験し公開してゆきたいと思います。

*1:ちょっと前までは「くうドール」だったのにねw

*2:PhySprite.enchant.jsの方が開発が進んでおり多角形とジョイントを利用することができますが、まだenchant.js v0.6には未対応のようです。

*3:Box2D自身には、b2_dynamicBody(DYNAMIC_SPRITE)とb2_staticBody(STATIC_SPRITE)の他にb2_kinematicBodyというタイプが存在します。これはDYNAMICとSTATICの間の子のようなタイプみたいなのですが、詳細が分かっておりません。また、box2d.enchant.jsでは除かれています。