Box2DでActionScript物理プログラミング

第2回物理エンジンをセットアップし、箱を落とすFlashを作る

Box2Dを使った簡単なFlashを作ってみましょう。まずは、床の上に箱を落とすだけのFlashです。ソースコードの説明をする前に、動作を見てみましょう。

サンプルについて

コンパイルと実行

まず、以下のActionScriptファイルをダウンロードしてください。このファイルはどこに置いても構いません。

次に、mxmlcを使ってActionScriptをコンパイルします。このとき、-source-pathオプションを指定して、Box2Dがある場所を指定する必要があります。

mxmlc -source-path=C:\lib\Box2DFlashAS3_2.0.0 DropBox.as

少し長いですね。通常のコンソール画面であれば、上キーを押して以前のコマンド履歴を参照出来るので、二度目以降は入力する必要はありません。何度もコンパイルするときに時間がかかって仕方が無いという方にはfcshがオススメです。興味のある方は調べてみてください。

さて、出来たDropBox.swfは以下のようなものになります。

最初は何も表示されていませんが、画面内をクリックしてみてください。少し傾いた箱が落ちてきて、バウンドした後に静止します。もう一度クリックすると、最初から実行されます。

物理エンジン内のレイアウト

このサンプルが、実際にどのようなスケールの中で展開されているのか説明します。幅4m、厚さ20cmの床の上に、幅60cm、高さ40cmの箱を落とします。箱は、床から2mのところから落とします。画面全体のサイズは幅5m、高さ3.75mです。図で説明すると以下のとおりです。

物体の配置図
物体の配置図

こうしたスケールにしている理由の1つは、Box2Dで扱いやすいスケールが0.1m(10cm)から10mであるということです。マニュアルの中では、スープの缶からバスぐらいまでと書かれています。Box2Dはこの範囲内で動くようにチューニングされており、範囲を超えてしまうと、シミュレーションの精度が落ちてしまうようです。詳しくはマニュアル中の3.3. Unitsを参照してください。

もう1つの理由は、イメージのしやすさです。実はBox2Dの公式マニュアルに今回の説明とほぼ同じ説明があるのですが、その中に出てくる箱のサイズは1m四方と少し大きめです。そこで、それよりも少し小さくして、幅60cm、高さ40cmとしました。

イベントハンドラの登録

それではプログラムの説明に移ります。まずはコンストラクタの説明です。ここでは2つのイベントハンドラを登録しています。

stage.addEventListener(MouseEvent.CLICK, clickHandler);
addEventListener(Event.ENTER_FRAME, enterFrameHandler);

1つは、クリックによって物理エンジンのセットアップを行うためのイベントハンドラclickHandlerです。このイベントハンドラが今回の内容の大半を占めます。

もう1つは、画面が更新されるときに物理エンジン内の時間を進めるためのイベントハンドラenterFrameHandlerです。

物理エンジンのセットアップ

clickHandlerでは、物理エンジンのセットアップ、床と箱の設置、描画設定と、一通りのことを行います。まずは物理エンジンのセットアップから見ていきましょう。

外枠を定義する

最初にすることは、物理エンジンが影響する範囲を決めることです。

var worldAABB:b2AABB = new b2AABB();
worldAABB.lowerBound.Set(-100, -100);
worldAABB.upperBound.Set(100, 100);

範囲はb2AABBクラスで定義されます。このAABB(Axis-aligned bounding box)とは物理エンジンでよく出てくるのですが、斜めになっていない、軸に沿った長方形だと考えてください。長方形の左上がlowerBound、右下がupperBoundとなります。

これを図で表すと以下のようになります。

物理エンジンの影響する範囲
物理エンジンの影響する範囲

ここでひとつ大事なポイント。指定している数値は決してピクセル単位ではありません。マニュアルには、この数値はメートルとして考えると書かれています。これに従うと、AABBは縦横200mとなります。これだけのサイズがあれば、物理エンジンの影響範囲は十分にカバーできます。

その他の単位については、重さはキログラム、時間は秒とするといいと書かれているので、これらも指示通りにします。

重力を定義する

重さと時間の話が出てきたところで、重力を定義します。重力は一般的に9.81メートル毎秒毎秒ですが、ここでは10としておきます。

var gravity:b2Vec2 = new b2Vec2(0, 10);

重力はベクトルを表すb2Vecクラスで定義します。重力は下を向いているので、ベクトルのX成分は0、Y成分は10となります。

物理エンジン全体のセットアップ

最後に、ここまで定義してきたworldAABBとgravityを使って物理エンジン全体をセットアップします。

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

Box2Dでは、物理エンジン全体をワールドと言い、b2Worldクラスで表します。コンストラクタにワールドの範囲と重力を指定すれば、セットアップ完了です。

床の設置

物理エンジンをセットアップしただけの状態は、いわば世界に何も無い状態です。今からこの世界に物を設置していかなければなりません。冒頭でお見せしたとおり、これからこの世界に床と箱を設置するのですが、まずは床から設置していきましょう。

床の場所とサイズ
床の場所とサイズ

床の場所を定義する

床の場所は、b2BodyDefクラスを使って定義します。

var floorBodyDef:b2BodyDef = new b2BodyDef();
floorBodyDef.position.Set(2.5, 3);

floorBodyDefというインスタンスを作り、positionに床の場所(左から2.5m、上から3m)を設定するだけです。なお、このpositionに指定するのは床の中心座標です。床の左上の座標ではないので注意してください。

床の形を定義する

床の形は、b2PolygonDefクラスを使って定義します。

var floorShapeDef:b2PolygonDef = new b2PolygonDef();
floorShapeDef.SetAsBox(2, 0.1);

b2PolygonDefは、その名のとおり色々な多角形を定義するのに使えます。SetAsBoxを使えば、まっすぐな長方形を定義できます。引数には長方形の大きさの半分の値を指定します。

ここで作りたい床のサイズは幅4m、高さ(厚さ)20cmなので、その半分の2と0.1を指定します。少しややこしく感じられるかもしれませんが、最初に指定した床の中心位置からの幅と高さだと思えば分かりやすいと思います。

床を設置する

ここまで定義してきた位置と形の情報を使って、床をワールド内に設置します。

var floor:b2Body = world.CreateStaticBody(floorBodyDef);
floor.CreateShape(floorShapeDef);

worldのCreateStaticBodyメソッドを呼ぶと、b2Bodyクラスのインスタンスが返されます。これがワールド内に設置された床です。ただし、このままでは床が形を持っていない状況なので、CreateShapeで形を指定します。

CreateStaticBodyというメソッドの名前が示すとおり、ここで作られる床は一切動きません。100kgのボールなどが床にぶつかっても平気です。もちろん、プログラムで明示的に動かすことはできます。

箱の設置

床の次は箱です。物体をワールド内に設置する流れは同じですが、内容が少し違います。

箱の場所とサイズ
箱の場所とサイズ

箱の場所を定義する

床のときと同じように、b2BodyDefを使って箱の場所を定義します。

var bodyDef:b2BodyDef = new b2BodyDef();
bodyDef.position.Set(2.5, 1);

箱は、左から2.5m、上から1mの位置から落とします。この書き方は床のときとまったく同じです。ここでの「上から」というのは画面の上端から1mという意味です。床は上から3mの高さにあるので、箱は床の上から2mの位置にあることになります。

箱の形を定義する

箱の形は、床のときとは違って少し傾いています。

var shapeDef:b2PolygonDef= new b2PolygonDef();
shapeDef.SetAsOrientedBox(0.3, 0.2, new b2Vec2(0, 0), 0.8);

使っているメソッドがSetAsBoxからSetAsOrientedBoxに変わっています。SetAsBoxを使うと「まっすぐな長方形」が作れますが、SetAsOrientedBoxを使うと「傾いた長方形」が作れます。

SetAsOrientedBoxの引数ですが、最初の2つはSetAsBoxと同じく、長方形の幅と高さの半分の値です。3つ目の引数には、b2Vec2を使って回転させるときの中心座標を指定します。長方形の真ん中を中心に回転させるときは、(0, 0)を指定してください。4つ目の引数には、長方形を傾ける角度をラジアン単位で指定します。時計回りの方向がプラスです。0.8とすれば、大体45度回ります。

箱の重さを設定する

床のときは形まで設定してしまえばワールドへの設置が出来ましたが、箱の場合はまだ続きます。まずは重さ(密度)を設定します。

shapeDef.density = 1;

densityは密度のことです。これを1にすると、大きさが1平方メートルの物体の重さが1キログラムとなります。今回作った箱の大きさは0.24平方メートルなので、重さは0.24キログラムです。かなり軽い箱だといえます。

この値を変えることで箱の重さを定義できるのですが、今回のサンプルでは意味がありません。ガリレオ・ガリレイがピサの斜塔で行った実験から分かるとおり、箱が重くても軽くても、落ちるスピードは一定だからです。重さが意味を持つようになるのは、動く物体同士がぶつかったときですが、それは別の機会に説明します。

箱の反発係数を設定する

最後に、反発係数restitutionを設定します。反発係数とは、物体が何かにぶつかったときにどれだけ跳ね返るかを表す数値です。

shapeDef.restitution = 0.2;

反発係数を0.2にすると、床で少しバウンドして落ち着くような動作になります。反発係数は、値が大きいほどその効果を発揮します。0にするとまったくバウンドしません。逆に1にするといつまでもバウンドし続けます。このような物体は普通は存在しないので、大抵は反発係数を0に近いあたりで設定します。

ちなみにこれを1以上にすると、まるでトランポリンのように、落とし始めた高さ以上にバウンドします。こうすると大抵は物体の速度がどんどん上がっていき、収集がつかなくなってしまいます。

箱を設置する

パラメータの設定が終わったので、箱をワールド内に設置します。

var body:b2Body = world.CreateDynamicBody(bodyDef);
body.CreateShape(shapeDef);
body.SetMassFromShapes();

箱はワールド内で動き回るので、CreateDynamicBodyを使います。床でCreateStaticBodyを使ったのと対照的です。その次のCreateShapeで形を設定した後、SetMassFromShapesで重さを設定します。指定した密度はここで反映されます。

DebugDrawの設定

ようやく床と箱を設置できたところで、次はそれらを描画する設定をします。前回述べたとおり、Box2Dには物理エンジンによるシミュレーション結果を画面に表示するための機能が備わっています。今からその設定を行います。

var debugDraw:b2DebugDraw = new b2DebugDraw();
debugDraw.m_sprite = this;
debugDraw.m_drawScale = 100; // 1mを100ピクセルにする
debugDraw.m_fillAlpha = 0.3; // 不透明度
debugDraw.m_lineThickness = 1; // 線の太さ
debugDraw.m_drawFlags = b2DebugDraw.e_shapeBit;
world.SetDebugDraw(debugDraw);

b2DebugDrawがBox2Dが持つ描画システムです。不透明度や線の太さなど、色々な値を設定していますが、最も重要なのはm_drawScaleです。m_drawScaleは、物理エンジン内の1mを画面上の何ピクセルとして表すかを示す値です。

最初のほうで、画面全体のサイズを幅5m、高さ3.75mとすると書きましたが、そうなる理由がここにあります。mxmlcで作られるFlashのサイズは、何も指定しなければ幅が500ピクセル、高さが375ピクセルとなります。1mが100ピクセルということは、幅が5m、高さが3.75mになります。

物理エンジン内の時間を進める

ようやく全ての初期化が終わりました。後は物理エンジン内の時間を進める処理を書くだけです。Stepメソッドを呼び出すことで、物理エンジン内の時間を進めることが出来ます。

world.Step(1 / 24, 10);

1つ目の引数には、進める時間を秒単位で指定します。mxmlcで作られるFlashはデフォルトで秒間24フレームなので、1フレームで1/24秒だけ時間を進めます。

2つ目の引数には、物理エンジンのシミュレーション精度を指定します。値が大きいほど精度が上がりますが、その分時間もかかります。Box2Dのマニュアルには、10を指定するのがいいだろうと書かれているので、それに従います。

まとめ

これで今回のサンプルプログラムの説明が終わりました。プログラミングは実際に手を動かして学習したほうが理解が深まるので、是非サンプル中の数値を適当に書き換えて実行してみてください。そうすることで、それぞれの数値がサンプルFlash内のどこに影響しているか、実際に目で見て確認できます。

次回は、マウスのクリックに反応するような、もっと遊べるFlashに挑戦します。

おすすめ記事

記事・ニュース一覧