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

第4回いろいろな形の物体を作る

Box2Dで作れる物体の形は、長方形だけではありません。他にも、円や多角形を作ることができます。また、それらを組み合わせた複雑な形も作ることができます。今回は、それらの形の作り方について説明します。

円を作るには

まずは円の作り方です。前回と似たような形で、マウスボタンを押した場所から離した場所までを半径とする円を作れるようなFlashを作ります。詳しくはサンプルをご覧ください。

これをいつも通り以下のコマンドでコンパイルします。

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

生成されるFlashは以下のようなものになります。

全体の流れについて

基本的な流れは長方形を作るときと変わりません。マウスのボタンが押されてmouseDownHandlerが呼ばれたら、その場所をプロパティcircleCenterに記憶します。これは円の中心となります。

マウスのボタンが離されてmouseUpHandlerが呼ばれたら、まず円の中心からマウスボタンが離された場所までの距離、つまり円の半径を計算します。円の半径がある程度以上なら、その場所に円を作ります。作られた円は、長方形のときと同様に重力に従って落下します。

円を見ると、直感的にゴムボールなどをイメージする場合が多いと思います。そういった物体を落としたときにバウンドしないと少し違和感があるので、今回は反発係数を0.5に設定しました。

プログラムは、前回のTsumiki.asをベースに書き換えています。変更があるのはほとんどmouseUpHandlerの部分なので、そこに的を絞って説明します。

円の中心と半径を計算する

マウスのボタンが押されたら、その場所を円の中心とします。長方形のときと同様に、マウスボタンが押された座標の値をDRAW_SCALEで割って計算します。

var x:Number = circleCenter.x / DRAW_SCALE;
var y:Number = circleCenter.y / DRAW_SCALE;

円の半径については、図を参照しながら説明します。

円の半径について
円の半径について

円の中心(circleCenter.x、circleCenter.y)とマウスボタンが離された場所(event.stageX、event.stageY)の差をdx、dyとします。この値は、引き算をすることで簡単に求まります。引き算の結果はDRAW_SCALEで割り、物理エンジン内の単位に直しておきます。なおdxやdyがプラスかマイナスかはここでは問題になりません。

var dx:Number = (event.stageX - circleCenter.x) / DRAW_SCALE;
var dy:Number = (event.stageY - circleCenter.y) / DRAW_SCALE;

そして、これらの値から円の半径radiusを計算します。dxの二乗とdyの二乗を足し合わせ、Math.sqrtを使って平方根を計算しています。

var radius:Number = Math.sqrt(dx * dx + dy * dy);

b2CircleDefで円を作る

円の中心と半径が求まったので、物理エンジン内に円形の物体を作ります。今まではb2BodyDefとb2PolygonDefを使って物体を定義してきましたが、円の場合は後者のb2PolygonDefがb2CircleDefになります。

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

var shapeDef:b2CircleDef = new b2CircleDef();
shapeDef.radius = radius;
shapeDef.density = 1;     // 密度 [kg/m^2]
shapeDef.restitution = 0.5;  // 反発係数、通常は0~1

b2CircleDefにはradiusというプロパティがあります。これは名前のとおり円の半径を表すプロパティです。その他のdensityやrestitutionはb2PolygonDefのときと同じです。

ちなみに、b2PolygonDefとb2CircleDefはb2ShapeDefクラスの派生クラスになっています。densityやrestitutionなどの基本的なパラメータは、b2ShapeDefクラスのプロパティになっています。

ちょっと早足でしたが、以上で円の作り方の説明を終わります。ポイントは、b2CircleDefクラスを使うということぐらいです。円は中心と半径を設定すればいいので、長方形のときよりも計算が簡単かと思います。

三角形を作るには

円の次は三角形に挑戦します。ドラッグアンドドロップで三角形を作れるようにするのは難しいので、クリックした場所に点が打たれ、3つ目の点を打ち終わった時点で三角形が作られるようにします。

このソースをコンパイルすると、以下のようなFlashが生成されます。

点を3つ打つと三角形ができますね。ちなみに点を打つときは時計回りになるようにしてください。反時計回りに点を打つと不自然な動作をします。これはBox2Dの仕様です。ユーザが点を打つなどして三角形を作るようなFlashを作る場合は、何らかの方法で時計回りに限定する必要があります。このサンプルでは三角形を作ることに集中するため、想定外の入力を無視するような処理は省いています。

全体の流れについて

今まではマウスのボタンが押されたときと離されたときのイベントを拾って処理をしていましたが、今回はクリックされたときのイベント(MouseEvent.CLICK)をclickHandlerメソッドで拾って処理します。

stage.addEventListener(MouseEvent.CLICK, clickHandler);

メインとなる処理は、このclickHandlerの中に書きます。1度目のクリックと2度目のクリックでは、クリックされた場所を配列に記憶しておきます。3度目になると、b2PolygonDefを使って三角形の設定を行い、物理エンジン内に配置します。

サンプルを動かしてみると分かりますが、クリックした点には小さい円が描かれます。長方形と円のときはこういった補助的な描画は行いませんでしたが、今回は少し分かりにくいかと思ったのでこの処理を追加しました。円を描く処理はenterFrameHandlerに書いてあります。

クリックされた点を記憶する

三角形を作るためには、点が3つ必要です。この点を記憶しておくために、pointsという配列をDrawingPolygonクラスのプロパティとして用意します。また、次に打とうとしている点が何番目の点なのかを表すpointIndexも用意します。

private var points:Array = [new Point(), new Point(), new Point()];
private var pointIndex:int = 0;

マウスがクリックされてclickHandlerが呼ばれたら、pointsにクリックされた座標を記録していきます。

points[pointIndex].x = event.stageX / DRAW_SCALE;
points[pointIndex].y = event.stageY / DRAW_SCALE;

pointIndex++;
if (pointIndex < points.length) {
	return;
}
pointIndex = 0;

最初にクリックされたはpointIndexが0なので、配列の0番目にクリックされた座標が入ります。その後、pointIndexに1を加えます。pointIndexが必要な点の数より少なければ、return文で処理を終了します。2回目のクリックでも同様に配列の1番目に座標が記録されます。

3回目のクリックになると、if文の条件が3 < 3となり、成り立たなくなります。するとreturn文を通過しないので、if文の先に処理が進みます。そこではまず、pointIndexを0にリセットします。

中心座標を設定する

ここまでpointsに記憶してきた3つの点を使って三角形を作ります。三角形は、長方形のときと同じくb2BodyDefとb2PolygonDefを使って定義します。b2BodyDefには中心座標を設定しなければならないので、それを計算するところからはじめます。

var center:Point = new Point();
var i:int;
for (i = 0; i < points.length; ++i) {
	center.x += points[i].x;
	center.y += points[i].y;
}
center.x /= points.length;
center.y /= points.length;

まず、計算結果を入れるためにcenterという変数を作ります。これに対してクリックした点のX、Y座標を全て足していきます。合計した値を点の数で割って平均を取ると、三角形の中心座標になります。

中心座標が求まったので、b2BodyDefクラスの変数bodyDefを作り、座標を設定します。

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

頂点などを設定する

次はb2PolygonDefの設定です。長方形のときにも使ったクラスですが、使い方が違います。

var shapeDef:b2PolygonDef = new b2PolygonDef();
shapeDef.vertexCount = points.length;
for (i = 0; i < points.length; ++i) {
	shapeDef.vertices[i].Set(points[i].x - center.x, points[i].y - center.y);
}

違いが分かったでしょうか。長方形のときはSetAsBoxやSetAsOrientedBoxを使って形を設定していましたが、三角形の場合はvertexCountとverticesを使います。

vertexCountには、頂点の数を設定します。三角形なので3を設定すればいいのですが、後々のことを考えてpointsの要素数としておきます。、それぞれの座標についてはverticesという配列に設定しています。verticesはb2Vec2クラスの配列なので、Setメソッドを使って座標を設定します。

verticesに設定する座標は、bodyDef.positionに設定した中心座標からみた相対的な値でなければなりません。そのため、クリックされた座標から中心座標を引いた値を使います。

その他のパラメータを設定する

仕上げとして密度、反発係数、摩擦係数を設定します。

shapeDef.density = 1;     // 密度 [kg/m^2]
shapeDef.restitution = 0; // 反発係数、通常は0~1
shapeDef.friction = 0.5;  // 摩擦係数、通常は0~1

摩擦係数frictionは新しく出てきたパラメータです。今までの長方形や円ではあまり意味がなさそうなので設定しませんでしたが、今回は三角形という形の特性上、摩擦を設定したほうが面白そうなので使うことにしました。0.5というのがどれぐらいの大きさなのかかを知るには、実際に値を変えて試してみるのがいいと思います。

クリックした点を描画する

最後に、物理エンジンとは直接関係ありませんが、クリックした場所に円を描いている処理について説明します。この処理は、enterFrameHandlerで、物理エンジンを1ステップ進めた後のところに書かれています。

DebugDrawを使っている場合、b2WorldクラスのStepメソッド内で描画処理が行われます。この中では、画面の内容を全て消去して、物理エンジンに登録されている物体の形を描くという処理がされています。そのため、追加の描画処理を書く場合はStepメソッドの直後に書くのが無難だと言えます。

クリックした点を描く処理は以下のとおりです。

var i:int;
for (i = 0; i < pointIndex; ++i) {
	graphics.drawCircle(
		points[i].x * DRAW_SCALE,
		points[i].y * DRAW_SCALE,
		5);
}

今までクリックされた回数だけfor文を回し、drawCircleメソッドを使ってクリックされた点に小さな円を描きます。

多角形を作るには

三角形のプログラムを書き換える

多角形を作るのは簡単です。というのも、三角形の時点でほぼプログラムが完成しているからです。例えば五角形を作りたい場合は、pointsの定義を以下のように書き換えてください。

private var points:Array = [new Point(), new Point(),
	new Point(), new Point(), new Point()];

これだけで、先ほどの三角形を作るFlashが五角形を作るFlashになります。プログラムの中で「三角形」という表現を使わずに「ポリゴン」と言っていたのはこのためです。

おかしな動きをするケース

三角形の時には時計回りという制約がついていましたが、五角形になるとさらに制約が厳しくなります。野球のホームベースのような形の物体を作ると特に問題なく五角形が作られます。

しかし適当に点を打っていると、変な形の物体ができてしまうことがあります。アルファベットのMの字を描くような感じで、真ん中の点を低めに打ってみてください。変な形になってしまいますね。これひとつでも十分変な動きをするのですが、たくさん作ると画面外に飛んでいったりしてしまうことがあります。

また、正常に見えてもおかしい場合があります。同じMの字でも、真ん中が少し高めになるように点を打ってみてください。2つの山がくっついたような五角形が作られたように見えます。ではその上にもう1つ同じ形の物体を作って落としてみてください。どうなりましたか?やはりおかしな動作をしますね。

おかしな動きをする五角形
おかしな動きをする五角形

こういったことが起こるのは、物理エンジンが凸包しか扱えないことが原因です。これについては初回の記事で少し触れました。凸包ではない物体を作りたい場合は、凸包を組み合わせて作るしかありません。

まとめ

円や多角形を作る方法について説明してきました。b2BodyDefを使って物体の中心座標を決めるところは全て共通ですが、形を決める部分はバラバラです。実際には多角形が必要となるシーンはあまりないと思うので、長方形と円を押さえておけばいいと思います。

物体を作る方法については一通り説明し終わったので、次回は物と物をつなぐジョイントについて説明したいと思います。

おすすめ記事

記事・ニュース一覧