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

●第9回:物体を表示オブジェクトで表示する

◆Box2Dでの表示について

今までは第4回で扱ったb2DebugDrawクラスを使って物体を表示していました。
Box2Dはそもそも物理シミュレーションの演算を行うためのもので、b2DebugDrawクラスの役割はあくまで動作確認の補助です。
主機能の演算と表示は切り離されているので、b2DebugDrawクラスを使わずにムービークリップやビットマップ画像などの表示オブジェクトを使って表示する場合、私たちがその仕組みを用意することになります。
考え方としては、物体の位置と回転角度を対応する表示オブジェクトに反映させる、ということになります(大きさは予め物体の表示サイズと合わせたものを用意しておくのがよいでしょう)。
表示の更新処理は物理演算が更新されるのに合わせて行うので、enterFrameイベントリスナー内で実行します。
方法論としては、技術評論社さんの記事「Box2DでActionScript物理プログラミング」の第6回「画像を使って表現力アップ」をほぼそのまま使わせていただこうと思います。
今回は次のようなサンプルを例に表示オブジェクトによる表示を行います。

b2DebugDrawクラスのみの表示

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

→サンプルファイル【Box2D_09_01.zip

このサンプルでは、外周を囲む壁(静体)、中央付近で回転する追加の壁(静体)、ボール(動体)、箱(動体)の4種類の物体の表示を行います。

◆各物体に対応する表示オブジェクトの内訳

今回は、色々なパターンを扱うために静体をスクリプトで回転させたり、表示オブジェクトをスクリプトで生成したり、オーサリング時に配置したりしています。
以下、各物体にどんな表示オブジェクトを対応させているかをざっと紹介しておきます。

・外周を囲む壁(静体)

外周を囲む壁はコンテンツ内で移動や回転をさせていません。配置した場所にずっとあり続けるだけです。
なので、オーサリング時に描画したシェイプそのままです。スクリプトは使っていません。

残りの物体にはSprite(およびそのサブクラスの)オブジェクトを関連付けています。
Spriteオブジェクトをいくつかのパターンで扱っているので説明を入れておきますが、読み飛ばしていただいても結構です。

・中央付近で回転する追加の壁(静体)

静体は力を加えることによる制御は行えませんが、直接的な制御は行うことができます。
追加の壁にはオーサリング時に配置したムービークリップインスタンスexWall_mcを関連付けています。

・ボール(動体)

ボールにはFLAファイル内に読み込んだビットマップ画像(埋め込みアセットクラスBmpCircle)をスクリプトで関連付けています。ビットマップ画像はBitmapDataクラスのサブクラスとして扱われるので、単に表示するだけならBmpCircleオブジェクトをBitmapオブジェクトに関連付けてaddChild()すればOKです。
物体と表示オブジェクトの基準位置がずれているですが、その場合基準点が画像の左上になってしまいボールの基準点(中心部)とずれてしまいます。

画像の基準点も中心にするためにSpriteオブジェクトを生成し、BitmapオブジェクトをaddChild()してSprite内で画像を基準点中央に配置します。
ぶっちゃけビットマップ画像を中央に配置したムービークリップインスタンスをオーサリング時に配置しておくのが簡単です。

・箱(動体)

シェイプを含む基準点を中央にしたムービークリップシンボルを埋め込みアセットクラスMcRectとして定義し、スクリプトでインスタンスを生成し関連付けています。
こちらもオーサリング時にステージ上に配置しておくのが簡単ですね。

◆物体と表示オブジェクトの関連付けと表示の更新

・関連付け

物体と表示オブジェクトの関連付けは極論を言えば好きなようにやればよいのですが、ここでは冒頭に書いたように技術評論社さんの記事の手法を使わせていただきます。
物体(b2Bodyオブジェクト)にはm_userDataというユーザーに自由に使ってよいプロパティが用意されています。
このプロパティに関連付けたい表示オブジェクトを設定しておきます。
m_userDataプロパティはパブリックプロパティで直接アクセスできてしまうのですが、アクセスするためのメソッドも用意されているのでそちらを使うことにします。

【M】b2Body.SetUserData(data:*) : void-物体に任意のデータを設定する。

引数-data:物体に設定する任意のデータ。
戻り値-なし。

【M】b2Body.GetUserData() : *-b2Body.SetUserData()メソッドで物体に設定したデータを取得する。

戻り値-b2Body.SetUserData()メソッドで物体に設定したデータ。

例えば追加の壁(変数_exWall)にムービークリップインスタンスexWall_mcを関連付ける場合、次のようなスクリプトになります。

_exWall.SetUserData(exWall_mc);

・表示オブジェクトと物体の基準位置を合わせる

表示の更新は冒頭に書いた通りenterFrameイベントリスナーで物体の位置を表示オブジェクトのx、yプロパティに反映し、回転角度をrotationプロパティに設定することで実現します。
位置を取得する場合、b2Body.GetPosition()メソッドかb2Body.GetWorldCenter()メソッドを使います。
両者の違いはb2Body.GetPosition()メソッドが物体の基準位置のワールド座標を取得するのに対し、b2Body.GetWorldCenter()メソッドが物体の重心のグローバル座標を取得する点です。
どちらを使っても構いませんが、重要なのは表示オブジェクトの基準点と物体の基準点または重心の位置が一致していることです。その上で一致している方の位置を取得して表示オブジェクトの位置に反映します。
表示オブジェクトの基準点と物体の基準点及び重心の3つが一致しているのが理想的な状況です。
その場合にはb2Body.GetPosition()メソッドでもb2Body.GetWorldCenter()メソッドでも問題ありません。
円や長方形などBox2Dで用意されている形状は基本的に物体の基準点と重心は中心で一致しますが、複雑な形状を作成する場合にはそうならない場合があるので場合によっては注意が必要になります。

・表示の更新処理

表示の更新は、m_userDataプロパティにSpriteオブジェクトが関連付けられている全ての物体(b2Bodyオブジェクト)に関して行います。
b2World.GetBodyList()メソッドで最後に作成されたb2Bodyオブジェクトを取得することができ、b2Body.GetNext()メソッドでそのb2Bodyオブジェクトの前に作成されたb2Bodyオブジェクトを取得できます。
これらを使うことで全ての物体をチェックできます。
なお、最初に作成されたb2BodyオブジェクトにGetNext()メソッドを実行するとnullが返されます。

【M】2World.GetBodyList() : b2Body-物理ワールド内に最後に作られたb2Bodyオブジェクトを返す。

戻り値-物理ワールド内に最後に作られたb2Bodyオブジェクト。

【M】b2Body.GetNext() : b2Body-対象のb2Bodyオブジェクトの前に作られたb2Bodyオブジェクトを返す。

戻り値-対象のb2Bodyオブジェクトの前に作られたb2Bodyオブジェクト。対象のb2Bodyオブジェクトが最初に作成されたものの場合null。

結論としてenterFrameイベントリスナー内で次のようにforループステートメントを実行して全ての物体の位置と回転角度を反映させることができます。

for (var b:b2Body = _world.GetBodyList(); b; b = b.GetNext()) {
    if (b.GetUserData() is Sprite) {
        //物理エンジン内での位置と回転角度を反映させる
        b.GetUserData().x = b.GetPosition().x * DRAW_SCALE;
        b.GetUserData().y = b.GetPosition().y * DRAW_SCALE;
        b.GetUserData().rotation = b.GetAngle() * 180 / Math.PI;
    }
}

初期化式で最後に作成された物体を取得し、増減式で1つ前の物体を取得します。継続条件は、変数bがb2Bodyオブジェクトを参照している場合にはtrueと評価され、nullになるとfalseと評価されます。

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

→サンプルファイル【Box2D_09_02.zip

・処理の順序と表示のズレ

今回のサンプルではenterFrameイベントリスナー内で、次のような処理を行っています。
 ・追加壁の回転処理
 ・b2World.Step()メソッドによる物理シミュレーションの更新処理
 ・表示オブジェクトの表示更新処理
これらの処理は、上記の順に実行するとb2DebugDrawクラスの表示と表示オブジェクトが一致しますが、実行する順番によってはズレが生じます。
ちなみに先のサンプルではb2DebugDrawクラスの表示を消さずに表示オブジェクトを追加していますが、両者にズレは見られません。
次の例では、1)表示オブジェクトの表示更新処理、2)追加壁の回転処理、3)b2World.Step()メソッドによる物理シミュレーションの更新処理の順に実行しています。

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

→サンプルファイル【Box2D_09_03.zip

◆最後に

最後に、b2DebugDrawクラスの表示と表示オブジェクトの表示が一致しない主な要因を挙げておきます。
 ・表示オブジェクトの基準点と物体の基準点又は重心の位置が一致していない
 ・enterFrameイベントリスナー内での処理の順序が適切でない
ついでですが…。
実は今回は、表示オブジェクトによる表示のための別の仕組みをいくつか考えてみました。しかしいずれもムダに冗長になってしまい、そちらを敢えて選ぶだけの必然性に欠けたものにしかなっていないように思われたので公開を見合わせました。

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