Box2DFlashAS3 の単純なサンプルと使い方 (2.0.2版)

Box2dFlashAS3 はバージョンによって API が激しく変わっていてなかなか困りものだ。2.0.0 なら gihyo.jp の 特集:Box2DでActionScript物理プログラミング が分かりやすくてよいんだけど、2.0.1 で重要な API が改名されていて、そのことがパッケージには書いていない。

「Box2dFlashAS3 どうなってるんだ」と思ったら、どうやら Box2D 本家の API 変更に素直に追従しているだけのようだ。本家側ではそこそこドキュメントはそろっているようなので、ドキュメントが欲しい人は Box2D 付属のクラスライブラリや Box2D User Manual を見たほうがよいかもしれない。

今回は Box2dFlashAS3 ver 2.0.2 の単純なサンプルを作った。クリックすると始まるよ。

これのソースコードを紹介しながら Box2dFlashAS3 を使う手順を解説していく。

目次:

  1. 世界の作成
  2. 床の作成
  3. 落下する物体の作成
  4. シミュレーションの開始
  5. 描画

1. 世界の作成

シミュレーションを行うための世界を作成する。

// シミュレーションする座標の範囲を指定する
var worldAABB:b2AABB = new b2AABB();
worldAABB.lowerBound.Set(-100.0, -100.0);
worldAABB.upperBound.Set(100.0, 100.0);

// 重力を定義する
var gravity:b2Vec2 = new b2Vec2(0.0, 10.0);

// 世界のインスタンスを作成する
world = new b2World(worldAABB, gravity, true);

まずは、worldAABB を作ってシミュレーション計算する範囲を指定する。*1

次に重力を設定する。y 軸正方向に 10.0。物理をかじったことある人にはおなじみの値(正確には 9.80665… [m/s2])。

これで準備完了。世界を生成する。

m_world = new b2World(worldAABB, gravity, true);

第3パラメータは doSleep。ここを true にしておくと、物体が動いていないときはその分のシミュレーション処理を削減できる。

2. 床の作成

さて、世界ができたら床を作っていこう。

ここから世界に物体を配置していくんだけど、Box2D では、円や箱を作る手順がちょっと複雑なので毎回忘れてしまう。始めに手順を整理しておく。

物体を作る方法

  1. 物体の定義を作る
    • new b2BodyDef()
    • 場所や角度を決める
  2. 物体を作る
    • world.CreateBody(bodyDef):b2Body
    • 世界上に物体を作成する
  3. 形の定義を作る
    • b2PolygonDef, b2CircleDef
    • 形の大きさなどを決める
    • 反発係数、摩擦係数などを決める
  4. 形を物体に追加する
    • body.CreateShape(shapeDef)
    • 物体上に形を追加する
    • いくつもの形を追加してもよい
  5. (動く物体の場合のみ) 重さを計算する
    • body.SetMassFromShapes()
    • これを忘れると落ちない

XXXDef というのは XXX を作成するときのパラメータだと考えるとよいだろう。事前に XXXDef に値を設定しておいて、CreateXXXX(XXXDef); メソッドで実体を作る、という流れになる。

それでは、手順に沿って作成していこう。

2-1. 物体の定義を作る。

// 物体の定義を作る
var wallBdDef:b2BodyDef = new b2BodyDef();
wallBdDef.position.Set(400 / SCALE / 2, 300 / SCALE);
wallBdDef.angle = Math.PI / 24;

400, 300 の位置に物体を作る。角度はラジアンで指定する(π(=180°) / 24 なので 7.5°傾ける)。

2-2. 物体を作る。

世界上に物体を作成する。

// 物体を作る
var wallBd:b2Body = world.CreateBody(wallBdDef);

2-3. 形の定義を作成する

// 形の定義を作る
var wallShapeDef:b2PolygonDef = new b2PolygonDef();
wallShapeDef.SetAsBox(180 / SCALE, 10 / SCALE);

b2PolygonDef を使って 180×10 の四角形の定義を作る。*2

2-4. 形を物体に追加する

定義を作っただけではだめで物体に追加しなきゃいけない。

// 形を物体に追加する
wallBd.CreateShape(wallShapeDef);

b2Body オブジェクトの CreateShape を呼ぶ。*3

2-5. (動く物体の場合のみ) 重さを計算する

今回は動かない物体なので必要ない。

3. 落下する物体の作成

同じようにして積み木を作成していく。

// 物体の定義を作る (x 座標と角度はランダム)
var objBdDef:b2BodyDef = new b2BodyDef();
objBdDef.position.Set((300 * Math.random()) / SCALE, 0);
objBdDef.angle = Math.PI / 2 * Math.random();

// 物体を作る
var objBd:b2Body = world.CreateBody(objBdDef);

// 形の定義を作る
var shapeDef:b2PolygonDef = new b2PolygonDef();
shapeDef.SetAsBox(30 / SCALE, 30 / SCALE);
shapeDef.density = 1;
shapeDef.restitution = 0.4;
shapeDef.friction = 0.1;

// 形を物体に追加する
objBd.CreateShape(shapeDef);

// 定義を変更してもう1個の形を追加する
shapeDef.SetAsBox(40 / SCALE, 5 / SCALE);
objBd.CreateShape(shapeDef);

// 重さ・重心を計算する
objBd.SetMassFromShapes();

先ほどの手順と同じだが、動く物体なので最後に SetMassFromShapes() を利用して重さや重心を計算している。

ShepeDef のプロパティは次の通り。

density
0 以外に設定すると、固定されない(自由落下する)ようになる。
restitution
はね返り具合。
friction
摩擦ぐあいを表すパラメータ。0 だとよく滑る。

物体にはいくつも形を追加できる。ここでは、2個の四角を追加している。

4. シミュレーションの開始

enterFrame の中で次の関数を実行する。

world.Step(1 / 9, 10);
  • 第一引数(timeStep)は1回のステップで進める秒数。
  • 第二引数(iterations)はシミュレーションの精度。小さいとパフォーマンスがよくなり、大きいと正確になる(めり込んだりしない)

5. 描画

ver 2 から DebugView が導入された。開発の初期の段階では、物体を正しく定義できているか調べるのに便利だ。今回も DebugView を使って描画している。

一回、次のように定義するだけで、自動的に描画してくれるようになる。ありがたい。

//----------------------------------
// DebugDraw を有効にする
//----------------------------------
var debugDraw:b2DebugDraw = new b2DebugDraw();
debugDraw.m_sprite = this;
debugDraw.m_drawScale = SCALE;
debugDraw.m_fillAlpha = .8;
debugDraw.m_lineThickness = 1;
debugDraw.m_drawFlags = b2DebugDraw.e_shapeBit;
world.SetDebugDraw(debugDraw);

ソース

コメントを細かく書いているので128行あります。

package {
import Box2D.Dynamics.*;
import Box2D.Collision.*;
import Box2D.Collision.Shapes.*;
import Box2D.Common.Math.*;
import flash.events.Event
import flash.display.Sprite;
import flash.text.TextField;

[SWF(backgroundColor="#666666", width="350", height="200")]
public class Box2dSimpleSample202 extends Sprite {
    private const SCALE:Number = 10;
    private var world:b2World;

    // コンストラクタ
    public function Box2dSimpleSample202() {
        stage.scaleMode = "noScale";
        stage.align = "TL";

        // メッセージとクリック時の処理
        var tf:TextField = new TextField();
        tf.textColor = 0xffffff;
        tf.text = "Click to start";
        addChild(tf);
        var animation:Boolean = false;
        stage.addEventListener("click", function(event:Event):void{
            count = 0;
            animation = !animation;
            tf.text = "Click to " + (animation ? "stop" : "start");
        });

        // 初期化
        init();
        createObject();

        // 毎フレームの処理
        var count:int = 0;
        addEventListener("enterFrame", function(event:Event):void {
            world.Step(1 / 9, 10);
            if (count == 0 && animation){
                createObject();
            }
            count = (count + 1) % 30;

            // 下に行ったオブジェクトを削除する
            for (var b:b2Body = world.GetBodyList(); b; b = b.GetNext()) {
                if (b.GetWorldCenter().y * SCALE > 600){
                    world.DestroyBody(b);
                }
            }
        });
    }

    // 初期化
    private function init():void {
        //----------------------------------
        // 世界を作成する
        //----------------------------------
        // シミュレーションする座標の範囲を指定する
        var worldAABB:b2AABB = new b2AABB();
        worldAABB.lowerBound.Set(-100.0, -100.0);
        worldAABB.upperBound.Set(100.0, 100.0);

        // 重力を定義する
        var gravity:b2Vec2 = new b2Vec2(0.0, 10.0);

        // 世界のインスタンスを作成する
        world = new b2World(worldAABB, gravity, true);

        //----------------------------------
        // 床を作る
        //----------------------------------
        // 物体の定義を作る
        var wallBdDef:b2BodyDef = new b2BodyDef();
        wallBdDef.position.Set(400 / SCALE / 2, 300 / SCALE);
        wallBdDef.angle = Math.PI / 24;

        // 物体を作る
        var wallBd:b2Body = world.CreateBody(wallBdDef);

        // 形の定義を作る
        var wallShapeDef:b2PolygonDef = new b2PolygonDef();
        wallShapeDef.SetAsBox(180 / SCALE, 10 / SCALE);

        // 形を物体に追加する
        wallBd.CreateShape(wallShapeDef);

        //----------------------------------
        // DebugDraw を有効にする
        //----------------------------------
        var debugDraw:b2DebugDraw = new b2DebugDraw();
        debugDraw.m_sprite = this;
        debugDraw.m_drawScale = SCALE;
        debugDraw.m_fillAlpha = .8;
        debugDraw.m_lineThickness = 1;
        debugDraw.m_drawFlags = b2DebugDraw.e_shapeBit;
        world.SetDebugDraw(debugDraw);
    }

    // 物体を1個作る
    private function createObject():void{
        // 物体の定義を作る (x 座標と角度はランダム)
        var objBdDef:b2BodyDef = new b2BodyDef();
        objBdDef.position.Set((300 * Math.random()) / SCALE, 0);
        objBdDef.angle = Math.PI / 2 * Math.random();

        // 物体を作る
        var objBd:b2Body = world.CreateBody(objBdDef);

        // 形の定義を作る
        var shapeDef:b2PolygonDef = new b2PolygonDef();
        shapeDef.SetAsBox(30 / SCALE, 30 / SCALE);
        shapeDef.density = 1;
        shapeDef.restitution = 0.4;
        shapeDef.friction = 0.1;

        // 形を物体に追加する
        objBd.CreateShape(shapeDef);

        // 定義を変更してもう1個の形を追加する
        shapeDef.SetAsBox(40 / SCALE, 5 / SCALE);
        objBd.CreateShape(shapeDef);

        // 重さ・重心を計算する
        objBd.SetMassFromShapes();
    }
}
}

*1: ver 1 では b2AABB の範囲を指定するプロパティは minVertex, maxVertex だったのが、ver 2 からはそれぞれ lowerBound, upperBound に変わっいてる

*2: ver1 には b2BoxDef があったが、ver2 からは箱は b2PolygonDef.SetAsBox() で作成するようになった

*3: ver 2.0.0 では CreateStaticShape()CreateDynamicShape() に分かれていたが、ver 2.0.1 からは CreateShape() に統一された