2009.9.4
Box2D(Box2DFlashAS3)によるActionScript物理シミュレーション

●第4回:動体の生成と表示

◆物理シミュレーションの表示

・物理シミュレーションの表示に必要な処理

物理シミュレーションの結果を表示させるためには、表示オブジェクト関連の処理物理ワールドの更新処理が必要となります。
表示オブジェクト関連の処理とは、物体の表示のためのムービークリップインスタンスやシェイプ、ビットマップデータなどの表示オブジェクトを用意し、物理シミュレーションの計算結果に合わせて表示させることです。
最終的には自分で用意した表示オブジェクトを使うことになりますが、Box2Dにはb2DebugDrawクラスという動作確認用の表示の仕組みが用意されているので当面はこれを使うのが便利です。
物理ワールドの更新処理はb2World.Step()メソッドをenterFrameイベントハンドラなどで継続的に実行することで実現します。
b2World.Step()メソッドについては後述します。
まずはb2DebugDrawクラスについて確認します。

・b2DebugDrawクラスについて

Bb2DebugDrawクラスを使うと、動作確認のための物理シミュレーションの表示を簡単に行えます。
このクラスを使うと、例えば静体は緑の動体は白の図形として表示してくれます。

Bb2DebugDrawクラスによる物体の表示

物体の他、物体同士の様々な関連付けのための機能である「ジョイント」を設定した場合など、ジョイントの状況も表示させることなどもでき、物理シミュレーションの状況の確認に役立ちます。
最終的には物体はムービークリップインスタンスなどに置き換えることになりますが、適切に動作することが確認できるまではb2DebugDrawクラスを使うのがよいでしょう。
b2DebugDrawクラスを使う場合、b2DebugDrawオブジェクトを生成し各種表示関連の設定を行った上で物理ワールド(b2Worldオブジェクト)にセットします。

【コンストラクタ】b2DebugDraw()-b2DebugDrawオブジェクトの生成
【P】b2DebugDraw.m_sprite:Sprite-図形を描画するためのSpriteオブジェクト
【P】b2DebugDraw.m_drawScale:Number-1mを表すピクセル数
【M】b2DebugDraw.SetFlags(flags:uint) : void-表示内容を指定する。

引数-flags:表示内容を表すb2DebugDrawクラスの定数値を指定。
戻り値-なし

【M】b2World.SetDebugDraw(debugDraw:b2DebugDraw) : void-b2DebugDrawオブジェクトをb2Worldオブジェクトに設定する。

引数-debugDraw:表示設定を保持したb2DebugDrawオブジェクト。
戻り値-なし

最低限必要なスクリプトは上記のものになりますが、今回は物体の図形表示を次の図のようにちょっと変えてみようと思います。

今回の表示イメージ

この場合、図形の塗りのアルファ値を制御するb2DebugDraw.m_fillAlphaプロパティを指定します。

【P】b2DebugDraw.m_fillAlpha:Number-塗りのアルファ値を表す数値(0:透明~1:不透明)

b2DebugDrawオブジェクトの設定とb2Worldオブジェクトへの設定は次のようなスクリプトになります。

スクリプト:4-3

・インポートのセクションに追加

import Box2D.Dynamics.b2DebugDraw;

・クラス定義内の先頭に追加

//1mを表すピクセル数
private static const DRAW_SCALE:Number = 100;

・コンストラクタの最後尾に追加

//▼描画設定
var dd:b2DebugDraw = new b2DebugDraw();
dd.m_sprite = this;
dd.m_drawScale = DRAW_SCALE; //1mを100ピクセルにする
dd.m_fillAlpha = 0.3; //塗りのアルファ値
dd.SetFlags(b2DebugDraw.e_shapeBit);//物体を表示する設定に
_world.SetDebugDraw(dd);//物理ワールドにセット

ここでは、1mを表すピクセル数をプライベートな静的定数DRAW_SCALEとして定義してます。このあたりの表示スケールについては第2回で触れていた部分です。
さて、残るは物理シミュレーションの更新処理です。

・物理シミュレーションの更新

物理シミュレーションはb2World.Step()メソッドを実行することで物理シミュレーション内の時間を進めることができます。

【M】b2World.Step(dt:Number, iterations:int) : void-物理シミュレーションを更新する。

引数-dt:更新する時間(秒)を表す数値/iterations:精度を表す整数値。
戻り値-なし

このメソッドを継続的に実行し続けることで物理シミュレーションが適切に処理されます。
ここではenterFrameイベントハンドラで実行しようと思います。
dtパラメータはStep()メソッドによるシミュレーションの経過時間を指定します。今回であればフレームレート分の1秒を設定します。
iterationsパラメータはStep()メソッドが実行される間隔を補完する計算処理の回数です。10程度が適当とされていますので、特に不都合が発生しない限り10にしておこうと思います。
今回はflaファイルのフレームレートが24なのでdtパラメータには1/24秒を指定します。

スクリプト:4-4

・インポートのセクションに追加

import flash.events.Event;

・コンストラクタの最後尾に追加

//▼イベントリスナーの追加
addEventListener(Event.ENTER_FRAME, _enterFrameHandler);

・新しいメソッド(イベントリスナー)を追加

//▼イベントリスナー定義
private function _enterFrameHandler(eventObj:Event):void {
    _world.Step(1 / 24, 10);//物理シミュレーションの更新
}

これで物理シミュレーションの結果が目に見える形で表されました。
ムービープレビューを実行すると、箱が落下して停止します。停止した動体はスリープ状態になり赤で表示されます。

落下して床の上で停止する箱

↑グラフィックイメージをクリックすると、swfファイルが別ウィンドウで開きます。

ここまでのスクリプトの全体像を掲載する前に、箱に反発係数の設定を追加しておこうと思います。
現在の状態では、落下した箱が弾まずにペッタリと床にくっついて停止します。
これは箱も床も反発係数がデフォルトの0になっているからです。
箱に反発係数を設定することで何回か弾んで停止するようになります。
反発係数はb2ShapeDef.restitutionプロパティを設定します。

【P】b2ShapeDef.restitution:Number-反発係数を表す数値。通常は0~1。

1より大きい値を指定すると、弾むたびに元より高い位置まで上昇し収拾がつかなくなる可能性があります。
今回は0.5に設定します。以下今回のスクリプトの全容です。 例によって追加部分は、変更部分はで表示してあります。

スクリプト:4-5

//Box2D_04_02.as
package {
    //▼インポート
    import flash.display.Sprite;
    import Box2D.Collision.b2AABB;
    import Box2D.Common.Math.b2Vec2;
    import Box2D.Dynamics.b2World;
    import Box2D.Dynamics.b2BodyDef;
    import Box2D.Collision.Shapes.b2PolygonDef;
    import Box2D.Dynamics.b2Body;
    import Box2D.Dynamics.b2DebugDraw;
    import flash.events.Event;


    public class Box2D_04_02 extends Sprite {
        //▼定数定義
        //1mを表すピクセル数

        private static const DRAW_SCALE:Number = 100;
        //▼プロパティ定義
        private var _world:b2World;
        private var _bBox:b2Body;
        //▼コンストラクタ定義
        public function Box2D_04_02() {
            //▼物理エンジンのセットアップ
            //物理エンジン適用範囲の設定(ステージの100ピクセル外側)

            var worldRegion:b2AABB = new b2AABB();//矩形作成
            worldRegion.lowerBound.Set(-1, -1);
            worldRegion.upperBound.Set(6.5, 5);
            //重力の設定
            var gravity:b2Vec2 = new b2Vec2(0, 9.8);
            //範囲・重力・スリープの有無を指定して物理ワールドを作成
            _world = new b2World(worldRegion, gravity, true);

            //▼床(静体)の生成
            //BodyDefの設定

            var bdFloor:b2BodyDef = new b2BodyDef();
            bdFloor.position.Set(5.5 / 2, 3);
            //ShapeDefの設定
            var pdFloor:b2PolygonDef = new b2PolygonDef();
            pdFloor.SetAsBox(2, 0.15);
            //ボディの生成
            var bFloor:b2Body = _world.CreateBody(bdFloor);
            bFloor.CreateShape(pdFloor);

            //▼箱(動体)の生成
            var bdBox:b2BodyDef = new b2BodyDef();
            bdBox.position.Set(5.5 / 2, 1);
            //ShapeDefの設定
            var pdBox:b2PolygonDef = new b2PolygonDef();
            pdBox.SetAsBox(0.3, 0.2);
            pdBox.density = 1;//密度(1平方メートルあたりの重量(kg))
            //ボディの生成
            _bBox = _world.CreateBody(bdBox);
            _bBox.CreateShape(pdBox);
            //質量の設定
            _bBox.SetMassFromShapes();

            //▼描画設定
            var dd:b2DebugDraw = new b2DebugDraw();
            dd.m_sprite = this;
            dd.m_drawScale = DRAW_SCALE;
//1mを100ピクセルにする
            dd.m_fillAlpha = 0.3; //塗りのアルファ値
            dd.SetFlags(b2DebugDraw.e_shapeBit);//物体を表示する設定に
            _world.SetDebugDraw(dd);//物理ワールドにセット

            //▼イベントリスナーの追加
            addEventListener(Event.ENTER_FRAME, _enterFrameHandler);
        }

        //▼イベントリスナー定義
        private function _enterFrameHandler(eventObj:Event):void {
            _world.Step(1 / 24, 10);
//物理シミュレーションの更新
        }

    }
}

これで箱は床で何度か弾んでからとまるようになりました(swfを別ウィンドウで開く)。
今回はここまでです。次回から何回かは、物体についてより理解を深めていこうと思います。
とりあえず次回は長方形以外の物体を作成してみます。
→今回のファイル【Box2D_04_02.zip

このサイトへのご意見・ご感想は[takuya@haphands.com]までお願いします!