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

●第6回:物体の制御1(直接的な制御)

◆基準点と重心

物体には「基準点」と「重心」という2つの基準があります。
Box2Dによる物理シミュレーションの世界では、重心の方が重要性が高いケースが多くなります。
物体(主に動体)の制御は大まかに移動と回転がありますが、物理シミュレーションの恩恵を享受できるのは「物体に力を加えた結果として」移動・回転する場合です。
物体に力を加えて制御する際の基準となるのが重心なので、より重要性が高いケースが多くなるということです。
前回の最後に使ったb2Body.SetXForm()メソッドでは、直接的に位置を変更しました。このメソッドでは力を加えた結果として移動されたわけではなく、制御の基準も基準点になっています。
少し整理すると、物体の制御は移動・回転について、それぞれ「直接的な制御」と「力を加えた結果としての制御」の2種類を行える、ということです。
今回はまず「直接的な制御」を中心に扱っていこうと思います。
そうそう、スクリプトコードがそれなりに長くなってきたので、今回から掲載は最低限にしてサンプルファイルをこまめにアップする形にさせていただこうと思います。

ところで、物体を制御するようになると座標位置を確認したいことが頻繁に出てきます。
Box2Dでは物体の座標をb2Vec2オブジェクトで表しますが、b2Vec2オブジェクトそのものをtrace()しても「[object b2Vec2]」と表示され、座標位置が分かりません。
この辺を便利にしておこうと思います。
Box2D.Common.Mathパッケージ内のb2Vec2.asを開き、次のパブリックメソッドを追加しておきます。

public function toString():String {
    return "(x=" + x + ", y=" + y + ")";
}

これでb2Vec2オブジェクトそのものをtrace()したときに「(x=X座標, y=Y座標)」のような形式で表示されます。
さて、「直接的な制御」の前に前回の続きを少し。
前回のサンプルでは、左右対称に作ったやじり型の動体が弾んだ後に左にずれました。これを修正しようと思います。

・重心の設定

左にずれるのは重心がずれているのが原因です。b2Body.GetLocalCenter()メソッドを使って重心のローカル座標を調べることができます。X座標が0になっていなければ左右均等ではないということになります。
重心を設定するにはb2Body.SetMass()メソッドを使います。このメソッドは本来b2Body.SetMassFromShapes()メソッドの代わりに明示的に質量を設定する際に使いますが、重心を設定するのにも使えます。以下、今回使用するスクリプトを掲載。

【M】b2Body.GetLocalCenter() : b2Vec2-b2Bodyオブジェクトの重心のローカル座標を取得する。

戻り値-重心のローカル座標を表すb2Vec2オブジェクト。

【M】b2Body.GetMass() : Number-質量を取得する。

戻り値-質量を表す数値。

【M】b2Body.GetInertia() : Number-回転の慣性を取得する。

戻り値-回転の慣性を表す数値。

【M】b2Body.SetMass(massData:b2MassData) : void-物体に明示的に質量を指定する。

引数-massData : b2MassData:質量や重心、回転の慣性に関する情報を保持したb2MassDataオブジェクト。
戻り値-なし。

【コンストラクタ】b2MassData()-b2MassDataオブジェクトの生成。
【P】b2MassData.mass : Number-質量を表す数値。
【P】b2MassData.center : b2Vec2-重心のローカル座標を表すb2Vec2オブジェクト。
【P】b2MassData.I : Number-回転の慣性を表す数値。

今回質量は今まで通りb2Body.SetMassFromShapes()メソッドで算出します。
ただしb2Body.SetMass()メソッドで重心を設定する際に、引数となるb2MassDataオブジェクトにはb2MassData.massプロパティに質量を指定する必要があります。b2MassData.massプロパティのデフォルト値は0なので、値を指定しないと質量が0になり結果として静体になってしまいます。
b2Body.SetMassFromShapes()メソッドで算出された質量はb2Body.GetMass()メソッドで取得し、b2Body.SetMass()メソッドで重心と共に改めて設定します。
b2Body.SetMass()メソッドでは引数となるb2MassDataオブジェクトのプロパティとしてもうひとつ回転の慣性を指定できます。これは後回しにして後で触れてみます。

前回の【スクリプト:5-4】(Box2D_05_03.as)のクラス名を「Box2D_06_01.as」に変更して、以下のスクリプトを追加します。

スクリプト:6-1

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

import Box2D.Collision.Shapes.b2MassData;

・コンストラクタ内、質量の設定(b2Body.SetMassFromShapes()メソッド実行)の次に追加

//▼重心の変更
trace(_bArrow.GetLocalCenter());//変更前の重心座標
var massData:b2MassData = new b2MassData();
massData.mass = _bArrow.GetMass();//自動設定された質量を再設定
massData.center = new b2Vec2(0, 0);//明示的にx = 0, y = 0を指定
_bArrow.SetMass(massData);
trace(_bArrow.GetLocalCenter());//変更後の重心座標

ムービープレビューを実行するとやじり型の動体がその場で弾み、重心が左右均等になったことが分かります。

落下するやじり型

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

重心変更前の座標をtrace()した結果が(x=-0.08333333333333333, y=0)であり、数値としても左右にブレがあったことが確認できます。変更後は(x=0, y=0)となっています。

→サンプルファイル【Box2D_06_01.zip】

◆直接的な回転

物体を回転させるには、物体を生成する際に行う方法と生成後に任意のタイミングで行う方法があります。

・生成時の回転

生成時に回転させるには、b2BodyDef.angleプロパティに回転角度(ラジアン)を表す数値を指定します。
矩形の場合はもうひとつ方法があります。それは矩形を作る際のb2PolygonDef.SetAsBox()メソッドの代わりにb2PolygonDef.SetAsOrientedBox()メソッドを使う方法です。

【P】b2BodyDef.angle : Number-生成する物体の回転角度(ラジアン)を表す数値。
【M】b2PolygonDef.SetAsOrientedBox(hx:Number, hy:Number, center:b2Vec2=null, angle:Number=0.0) : void-物体としての傾いた長方形を定義する。

引数-hx:横サイズの半分(メートル)/hy:縦サイズの半分(メートル)/center:長方形の座標位置を表すb2Vec2オブジェクト/angle:回転角度(ラジアン)を表す数値
戻り値-なし

以下、コード掲載なしでサンプルファイルのみ。
傾いた長方形の動体を作成するのに、b2BodyDef.angleプロパティを使ったもの【Box2D_06_02.as】とb2PolygonDef.SetAsOrientedBox()メソッドを使ったもの【Box2D_06_02.as】です。

→サンプルファイル【Box2D_06_02.zip】/【Box2D_06_03.zip

動作的にはどちらも違いはありません↓。

落下するやじり型

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

・物体生成後の回転

生成後の回転は前回使ったb2Body.SetXForm()メソッドを使います。
上記【スクリプト:6-1】(Box2D_06_01.as)のスクリプトで、やじり型の動体の初期位置を設定するために使っているb2Body.SetXForm()メソッドの第2引数に「Math.PI / 180 * 10」を指定し、少し傾けてみます。

スクリプト:6-2

・コンストラクタ内、位置の設定(b2Body.SetXForm()メソッド実行)を変更

//位置の設定
_bArrow.SetXForm(new b2Vec2(5.5 / 2, 1), Math.PI / 180 * 10);

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

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

この場合、着地したときにやじりの右足でバランスを取ってバウンドしています。これは明らかに違和感があります。
これは、重心の変更の際にb2Body.SetMass()メソッドの引数となるb2MassDataオブジェクトの指定でb2MassData.Iプロパティを設定していないことが原因です。
b2MassData.Iプロパティは「回転の慣性」を制御するプロパティです。先ほどのサンプルでは、やじり型の動体の右足が着地したら、そこを軸に回転の慣性が働いて左足も着地してしかるべきです。
現状はb2MassData.Iプロパティの設定を省略したことでデフォルト値の0に設定され、回転の慣性が働かない状態になっています。
このプロパティの値を明示的に決めるのは難しいのですが、b2Body.SetMassFromShapes()メソッドを実行すると質量と共に適切な値が自動的に設定されます。b2Body.GetInertia()で設定されたb2MassData.Iプロパティの値を取得できるので、これで再設定すればよいでしょう。

スクリプト:6-3

・コンストラクタ内、重心の変更セクション内b2MassData.centerプロパティの設定後に追加

massData.I = _bArrow.GetInertia();

反対側の足で跳ね返ったやじり型

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

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

円形や長方形など、作成用に専用のAPIが用意されている形状はあまり考えなくても自然な動作をするものができますが、独自の形状を作る場合などはこのように少々注意が必要になるケースがでてきそうです。
…というか、そもそも独自の形状の作り方にもっといい方法があるんだうろなーとも思います。

・その他(注意など)

直接的な移動については、既に行っているので割愛させていただきます。
動体に関しては、生成後の直接的な移動・回転は共にb2Body.SetXForm()メソッドを使うわけですが、動体がスリープ状態で実行すると不都合がでる場合があります。
次のサンプルでは、ボタンクリックで回転([Rotate]ボタン)、移動([Move]ボタン)ができるようになっています。

回転と移動をボタンで行う

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

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

やじり型の動体が落下し、スリープ状態になってから[Rotate]ボタンをクリックすると床の静体にめり込むのが分かります。

スリープ時の回転操作で床にめり込むやじり型

動体に直接的な操作を行う場合このような齟齬が発生する可能性が出てきますが、それを抑えるためには明示的に動体を「起こす」(=スリープ状態を解除する)とよいようです。動体を起こすにはb2Body.WakeUp()メソッドを使いますが、それはまた改めて。

次回は動体の制御の要といっていい、「力を加える」ことによる制御を行おうと思います。

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