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

第3回マウスのドラッグ&ドロップで、好きなサイズの箱を作る

今回はマウスを使って箱を作れるFlashを作ります。前回はクリックすると決まった場所から1つだけ箱が落ちてくるものでしたが、それを少し進化させ、マウスのドラッグ&ドロップで好きなサイズの箱を何個でも落とすことができるようにします。

クリックした場所から箱を落とすには

いきなり好きなサイズの箱を作るFlashに取り掛かると難しいので、まずは前回の記事で解説したDropBoxをほんの少し書き換え、クリックした場所から箱が落ちてくるFlashを作ります。動作としては以下のようになります。

マウス操作に応じた処理をしているので、少しは自分で操作している感覚が得られると思います。これを実現するためにどのぐらいプログラムを書き換えているかというと、実はたったの1行です。

前回からの変更点

前回のプログラムの中で箱の場所を決めていたのは、61行目から始まる以下の部分でした。

// 箱の位置を左から2.5m、上から1mとする
var boxBodyDef:b2BodyDef = new b2BodyDef();
boxBodyDef.position.Set(2.5, 1);

この中の最後の行を以下のように書き換えると、クリックした場所から箱を落とせるようになります。

boxBodyDef.position.Set(event.stageX / 100, event.stageY / 100);

eventはMouseEvent型の変数で、プロパティstageXとstageYは、マウスがクリックされた場所です。この値はもちろんピクセル単位なので、200や300などといった値になります。しかし、物理エンジン内の単位はメートルなので、このままでは大きすぎます。そのため、マウスの座標を100で割っています。

100で割る理由については、前回説明したDebugDrawが関係してきます。DebugDrawについては軽く説明しましたが、今回は特にここが大事なのでもう一度詳しく説明します。

マウスの座標を物理エンジン内の座標へ

「画面上での座標」「物理エンジン内の座標」を対応付けているのは、b2DebugDrawクラスのプロパティであるm_drawScaleです。この値は、物理エンジン内での1mを何ピクセルにするかを表しています。m_drawScaleの値は100にしているので、1mが100ピクセルということになります。

例えば、画面上の(200, 100)という座標でマウスがクリックされたとします。これを物理エンジン内の単位に置き換えると、左から2m、上から1mの位置になります。

カーソル座標との対応
カーソル座標との対応

この関係を式にすると、以下のようになります。

物理エンジン内の位置 = マウスの座標 / m_drawScale
      (2, 1)      =  (200, 100)  /  100

つまり、マウスカーソルの座標をm_drawScaleで割ることによって、物理エンジン内の位置に変換することができます。この座標に箱を作れば、マウスでクリックした位置と箱の作られる位置がぴったりと一致します。

stageXとstageYについて詳しく

話が変わりますが、この記事に貼り付けられているFlashのサイズは400x300です。しかし、DropBoxの物理エンジンは、横5m、縦3.75mとして設定してあります。ではm_drawScaleが中途半端な値になっているかというと、そうではありません。ここまで説明してきているのと同じように、100を使っています。それでもクリックした位置から箱が降ってくるのはなぜでしょうか。

実はFlashそのものはmxmlcデフォルトの500x375として作ってあるのですが、HTMLに貼り付けるときに400x300というサイズを指定しています。こうすると、マウスをクリックしたときにstageXとstageYに設定される値は、自動的に500x375の範囲内になります。このようにマウスカーソルの座標は勝手に調整されるので、プログラムを作っているときは、表示されるときのサイズを考えなくても大丈夫です。

マウスをドラッグして箱を作る

いよいよ本題に移ります。今回もActionScriptファイル1つだけのシンプルなプログラムです。クラス名は、箱を積み重ねていくということでTsumikiという名前にしています。

初期化や床の設置など、大部分はDropBoxのプログラムを流用しています。箱を作る部分も、基本的な流れは同じです。

コンパイルするときのコマンドは、ActionScriptファイルがTsumiki.asになっている部分を除いて、前回と同様です。

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

無事コンパイルできると、以下のようなFlashが作られます。

サンプルでできること

初期状態では前回使った床が表示されていますが、箱がどこにもありません。画面内でマウスのボタンを押し、そのままドラッグしてどこかで離すと、その大きさの箱が画面に現れます。前回とは違い、何個も箱を落とすことができます。

画面下にある床は固定したものとして作られているので、何があっても動きません。床をまたぐような箱を作ろうとすると、上か下に強制的に押し出されます。また、たくさん箱が詰まったところの中に箱を作ろうとすると、他の箱が外に押し出されます。

マウスをドラッグしている間に、作られる箱の枠を表示したほうが見やすくなるかとも考えたのですが、プログラムが少し複雑になってしまうので表示していません。

マウスを使った箱の作成

マウスをドラッグして箱を作るプログラムは、改良版DropBoxのときに説明したことの応用でできます。

ドラッグ開始の処理

マウスのボタンが押されたら、その場所をboxBeginに記憶しておきます。

private function mouseDownHandler(event:MouseEvent):void {
	// マウスが押された場所を記憶しておく
	boxBegin.x = event.stageX;
	boxBegin.y = event.stageY;
}

boxBeginはPoint型の変数です。この時点ではこの場所が箱の四隅のどこになるかは分かりませんが、ひとまず記憶しておきます。

ドラッグ終了の処理

マウスのボタンが離されたら、箱の場所と大きさを計算して設置します。大まかな流れを最初に示します。

private function mouseUpHandler(event:MouseEvent):void {
	// 箱の場所と大きさを計算する
	
	// 小さすぎる箱ができるのを防ぐ
	
	// 箱の場所を設定する
	
	// 箱の大きさなどを設定する
	
	// 箱を動く物体として作る
}

箱の場所と大きさを計算する

まずは箱の場所と大きさの計算です。分かりやすいように図解します。

箱の場所と大きさ
箱の場所と大きさ

xとyが箱の中心座標、halfWidthとhalfHeightが箱の幅と高さの半分の値です。なぜわざわざ高さと幅を半分にしているかというと、最終的にBox2Dの機能を呼び出すときには半分の値が必要になるからです。これらの値を計算するプログラムは以下のようになります。

var x:Number = (event.stageX + boxBegin.x) / 2 / DRAW_SCALE;
var y:Number = (event.stageY + boxBegin.y) / 2 / DRAW_SCALE;
var halfWidth:Number = Math.abs(event.stageX - boxBegin.x) / 2 / DRAW_SCALE;
var halfHeight:Number = Math.abs(event.stageY - boxBegin.y) / 2 / DRAW_SCALE;

stageX、stageY、boxBeginに入っている値はカーソル座標のままで、範囲が500x375の中となっています。これをDRAW_SCALEで割ることで、物理エンジン内の座標に変換することができます。DRAW_SCALEはb2DebugDrawにも使っている値で、以下のように定義されています。今回は何ヶ所かでこの数値を使うので、このように定数にしています。

private static const DRAW_SCALE:Number = 100;

小さすぎる箱ができるのを防ぐ

箱の大きさを計算し、幅か高さが10cm以内になってしまった場合は、箱を作らずに処理を終了します。なぜこのような処理を行うかというと、あまりにも箱が小さいとシミュレーションが狂ってしまうからです。この処理を行っているのが以下のコードです。

if (halfWidth < 0.05 || halfHeight < 0.05) {
	return;
}

このif文を外して小さな箱を作れるようにすると、どのようにシミュレーションが狂ってしまうのかを試すことができます。上のほうから小さな箱を落とすと、下にある箱を貫通してしまうことがあります。このような不具合を避けるために、あらかじめ条件式を設けることで、小さい箱が作れないようにしています。

箱の場所や大きさなどを設定する

次に箱の場所や大きさなどを設定します。ここでも前回のおさらいですが、b2BodyDefで場所を、b2PolygonDefで大きさや密度などを設定します。

箱の場所は、先ほど計算したxとyをそのまま使います。前回から変わった点といえば、Setの引数が固定された値から変数になった所だけです。

var bodyDef:b2BodyDef = new b2BodyDef();
bodyDef.position.Set(x, y);

箱の大きさにはhalfWidthとhalfHeightを使います。今回は箱を傾けてしまうとかえって分かりにくくなると思ったので、SetAsOrientedBoxではなくSetAsBoxを使っています。

var shapeDef:b2PolygonDef= new b2PolygonDef();
shapeDef.SetAsBox(halfWidth, halfHeight);
shapeDef.density = 1;     // 密度 [kg/m^2]
shapeDef.restitution = 0;  // 反発係数、通常は0~1

密度は前回から変えていませんが、反発係数は0にしています。今回のように箱などの物体がたくさん置かれるようなケースでは、反発係数の値を大きくしてしまうと変な動きをしてしまう場合があります。0にするのはそういう事態を防ぐためです。

箱を作る

最後に、これらの情報を基に箱を作ります。ここは前回とまったく同じです。

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

物理エンジンの初期化は、Tsumikiクラスのコンストラクタ内で行っています。また、床の設置やDebugDrawの設定についても同じくコンストラクタで行っています。内容は前回と同じで、clickHandlerからコンストラクタに移動しただけなので、説明は割愛します。

まとめ

マウスのドラッグ&ドロップによって箱を作るFlashを作りながら、マウスカーソルの座標と物理エンジン内の座標の関係について説明してきました。この関係が理解できれば、Box2Dを使ってできることがかなり広がると思います。

次回はこれを応用し、長方形だけではなく、円や三角形なども作れるようにしてみたいと思います。

おすすめ記事

記事・ニュース一覧