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

●第5回:様々な形状の物体を作る

◆円の作成

前回まではb2PolygonDefクラスでSetAsBox()メソッドを使って長方形を作ってきました。
今回は長方形以外の形を作ってみようと思います。最初は円を作ります。
第3回で述べたように、形状はb2ShapeDefクラスのサブクラスであるb2PolygonDefクラスかb2CircleDefクラスで指定します。
円はb2CircleDefオブジェクトを生成し、b2CircleDef.radiusプロパティで半径を指定します。

【コンストラクタ】b2CircleDef()-b2CircleDefオブジェクトの生成
【P】b2CircleDef.radius:Number-半径(m)を表す数値

前回の【スクリプト:4-5】(Box2D_04_02.as)とほぼ同じ手順です。
変数名が変わったりもしていますが、「▼箱(動体)の生成」のセクションが「▼ボール(動体)の生成」セクションになっているのが大きな変更点です。これにより動体の形状を長方形から円にします。今回は重要部分を青の太字で表示します。

スクリプト:5-1

//Box2D_05_01.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;
    import Box2D.Collision.Shapes.b2CircleDef;
    public class Box2D_05_01 extends Sprite {
        //▼定数定義
        //1mを表すピクセル数

        private static const DRAW_SCALE:Number = 100;
        //▼プロパティ定義
        private var _world:b2World;
        private var _bBall:b2Body;
        //▼コンストラクタ定義
        public function Box2D_05_01() {
            //▼物理エンジンのセットアップ
            //物理エンジン適用範囲の設定(ステージの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 bdBall:b2BodyDef = new b2BodyDef();
            bdBall.position.Set(5.5 / 2, 1);
            //ShapeDefの設定
            var pdBall:b2CircleDef = new b2CircleDef();
            pdBall.radius = 0.25;
            pdBall.density = 1;//密度(1平方メートルあたりの重量(kg))
            pdBall.restitution = 0.5;//反発係数(通常0~1)
            //ボディの生成
            _bBall = _world.CreateBody(bdBall);
            _bBall.CreateShape(pdBall);
            //質量の設定
            _bBall.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ファイルが別ウィンドウで開きます。

b2DebugDrawによる円は、中心から半径分の直線が1本追加されたものとして描画されます。
これにより、円が回転した場合にその状況が確認しやすくなります(今回は回転しませんが)。
→サンプルファイル【Box2D_05_01.zip

◆多角形の作成

多角形は長方形と同様にb2PolygonDefクラスを使って定義します。が、定義方法は全く異なります。
多角形はb2PolygonDef.vertexCountプロパティによる頂点の数の指定と、b2PolygonDef.verticesプロパティによる頂点座標の指定が必要になります。b2PolygonDef.verticesプロパティはb2Vec2オブジェクトを要素とするArrayオブジェクトで、デフォルトで(x = 0, y = 0)のb2Vec2オブジェクトが8つ設定されています。

【P】b2PolygonDef.vertexCount:int-多角形の頂点数を表す整数値
【P】b2PolygonDef.vertices:Array-頂点の座標を表すb2Vec2オブジェクトを要素とするArrayオブジェクト

頂点座標を指定する場合、次の2つの注意点があります。

  • 画面上時計回りに指定する
  • へこんだ形状にしない

1つめの、時計回りに指定するというのはBox2Dのマニュアルで指定されています。
ちなみにマニュアルでは「右手座標系に基づいて反時計回りに指定」と記載されています。右手座標系とは、X軸を右向き、Y軸を上向き、Z軸を手前向きに考えた座標系(のよう)です。これをFlashの座標系に置き換えて、反時計回りに指定すると、結果的にFlashの座標系では時計回りの指定になります。
2つめのへこんだ形状というのは、やじりのように3角形の底辺がへこんだ形状や星型などが代表的なものでしょう。

へこんだ図形:やじり型、蝶ネクタイ型

へこんだ図形の例

このような形状を持った物体を作成する場合には、複数の適正なb2ShapeDefオブジェクトを子として持ったb2Bodyオブジェクトを作成します。このような物体の作成も後でやってみようと思います。
まずは、5角形を作成します。
大まかな流れは今までと変わりません。やはり重要部分を青の太字で表示しておきます。

スクリプト:5-2

//Box2D_05_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_05_02 extends Sprite {
        //▼定数定義
        //1mを表すピクセル数

        private static const DRAW_SCALE:Number = 100;
        //▼プロパティ定義
        private var _world:b2World;
        private var _bPentagon:b2Body;
        //▼コンストラクタ定義
        public function Box2D_05_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);

            //▼5角形(動体)の生成
            var bdPentagon:b2BodyDef = new b2BodyDef();
            bdPentagon.position.Set(5.5 / 2, 1);
            //ShapeDefの設定
            var pdPentagon:b2PolygonDef = new b2PolygonDef();
            pdPentagon.vertexCount = 5;//頂点の数
            //以下5行頂点の座標を設定
            pdPentagon.vertices[0].Set(0, -0.3);
            pdPentagon.vertices[1].Set(0.24, -0.1);
            pdPentagon.vertices[2].Set(0.16, 0.2);
            pdPentagon.vertices[3].Set(-0.16, 0.2);
            pdPentagon.vertices[4].Set(-0.24, -0.1);
            pdPentagon.density = 1;//密度(1平方メートルあたりの重量(kg))
            pdPentagon.restitution = 0.5;//反発係数(通常0~1)
            //ボディの生成
            _bPentagon = _world.CreateBody(bdPentagon);
            _bPentagon.CreateShape(pdPentagon);
            //質量の設定
            _bPentagon.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);//物理シミュレーションの更新
        }
    }
}

落下する5角形

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

5角形なのでb2PolygonDef.vertexCountプロパティに5を設定し、b2PolygonDef.verticesプロパティのインデックス0から4にb2Vec2.Set()メソッドで頂点座標を指定しています。
この方式による形状の指定で特徴的なのは、頂点座標とb2BodyDef.positionプロパティによる物体の位置の関係です。このサンプルでは、5角形の頂点の座標位置は分かりやすさを優先して(x = 0, y = 0)を中心に指定していますが、表示位置はb2BodyDef.positionプロパティで指定した位置になっています。
つまり、頂点の座標位置の指定は絶対的な座標位置が重要なのではなく、形状を表す相対的な位置関係のみが重要と考えてよさそうです。
→サンプルファイル【Box2D_05_02.zip

・8角形以上の多角形の作成

【スクリプト:5-2】では頂点の座標をb2PolygonDef.verticesプロパティに指定する際に、b2Vec2.Set()メソッドを使いました。これは先に述べた通り、b2PolygonDef.verticesプロパティがデフォルトでb2Vec2オブジェクトを8つ持っているために、8角形までの座標の指定はb2Vec2.Set()メソッドを使うことができるからです。
9個以上の以上の頂点を持った図形を作る場合は自分でb2PolygonDef.verticesプロパティにb2Vec2オブジェクトを追加すればよさそうです。
以下、10角形の動体を作成してみます。頂点座標はforループで取得します。
スクリプトは【スクリプト:5-2】からの変更点のみ挙げておきます。

スクリプト:5-3

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

import flash.geom.Point;

・コンストラクタ内、5角形(動体)の生成セクションの頂点の数と頂点座標の指定部分を書き換え

pdPentagon.vertexCount = 10;//頂点の数
//forループで頂点の座標を設定
for (var i:uint = 0; i < pdPentagon.vertexCount; i++){
    var pnt:Point = Point.polar(0.25, i * Math.PI / pdPentagon.vertexCount * 2);
    pdPentagon.vertices[i] = new b2Vec2(pnt.x, pnt.y);
}

5角形ではないのに変数名に「Pentagon」のフレーズが入っているのがアレですが、気にしないでおきましょう。

落下する10角形

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

b2PolygonDef.verticesプロパティの値を変更すれば、多角形の形を変更できます。
→サンプルファイル【Box2D_05_02a.zip

Box2DはそもそもFlash用に開発されたものではないので、基本的に一般的な右手座標系で考えるようになっているようです。
例えば第2回で物理ワールドの適用範囲を指定するために、b2AABB.lowerBoundプロパティとb2AABB.upperBoundプロパティを使いました。これらもFlashの座標系ではlowerBoundプロパティが左上の座標位置を表し、upperBoundプロパティが右下の座標位置を表していました。
この一見不自然なプロパティ名も本来の右手座標系に基づいているのでしょう。

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