Box2DFlashAS3
摩擦や重力などを加味してアニメーションを作れば、よりリアルな映像を作ることができます。
よくあるサンプルでは壁に跳ね返ったり、重力がかかりボールが段々はねなくなっていくサンプルがあると思います。
1 2 3 4 5
| ball.vx *= -0.7;
ball.vy * = gravity;
ball.x += ball.vx;
ball.x += ball.vy; |
今回はそういった質量、重力の動きを簡単に再現できる「Box2DFlashAS3」という物理ライブラリを勉強します。
てっく煮ブログに初心者向けの記事が載っていましたのでこちらと「プロとして恥ずかしくないActionScript」の本を使っていきます。
主な流れは以下の通りです。
1. 世界の作成
2. 床の作成
3. 物体の作成
4. シミュレーションの開始
5. 描画
1 世界の作成
まずは物理的空間を作るための「世界」の作成を行います。
具体的には、b2AABB を使い演算する範囲を指定して、b2Vec2 で重力を設定します。
1 2 3 4 5 6 7
| var worldAABB:b2AABB = new b2AABB();
var wordlAABB.lowerBound.Set(-100.0 , 100.0);
var worldAABB.upperBound.Set(-100.0,100.0);
var gravity:b2Vec2 = new b2Vec2(0.0, 10.0);
var doSleep:Boolean = true;
m_world:b2World = new b2World(worldAABB,gravity,doSleep); |
doSleepは物体は止まったときに、演算を続けるかスリープ状態にするかを選択しています。
trueの場合はスリープ状態にします。
2 床の作成
次に落ちてくる物体を受け止める地面を設定しています。
ここでは以下のクラスが使われています。
- b2Body 先ほど設定した「世界」に追加する際の大枠となるクラス
- b2BodyDef サイズや位置を設定
- b2PolygonDef 四角形のオブジェクトを生成する
- b2CircleDef
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| var body:b2Body;
var bodyDef:b2BodyDef;
var boxDef:b2PolygonDef;
var circleDef:b2CircleDef;
// var m_scale :Number = 30; と既に指定されている。
var insWidth:Number = 0;
var insHeight:Number = stage.stageHeight / m_scale;
var floorWidth:Number = stage.stageWidth / m_scale;
var floorHeight:Number = 3;
// サイズや位置を設定している。
bodyDef = new b2BodyDef();
bodyDef.position.Set(insWidth, insHeight + floorHeight);
boxDef = new b2PolygonDef();
boxDef.SetAsBox(floorWidth, floorHeight);
boxDef.friction = 0.3; // friction は摩擦
boxDef.density = 0; // density は密度
//bodyに追加している。
body = m_world.CreateBody(bodyDef);
body.CreateShape(boxDef); //落下箱物体に形状(boxDef)を与えます
body.SetMassFromShapes(); // (動く物体の場合のみ) 重さを計算する |
3. 物体の作成
次に物体を作成します。
今回は上からオブジェクトが落ちてくるというものです。
ここが非常に理解するのが難しいですが、てっく煮ブログに理解しやすい手順が載っていましたのでこれを参照しました。
- ① b2BodyDefで物体の定義を作る
- ② 先ほどのb2BodyDefを world.CreateBody(bodyDef);を使って物体を作る
- ③ b2PolygonDefやb2CircleDefを使って 形の定義を作る
- ④ 先ほどの形を body.CreateShape(sHapeDef)を使いb2Bodyに追加する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| private function onTimer(event:Event):void {
// ① 物体の定義を設定している。
var bodyDef:b2BodyDef = new b2BodyDef();
//初期の位置(x,y)を指定。
bodyDef.position.Set(Math.random() * stage.stageWidth / m_scale, -1); //position.Set(x,y);
//物体の大きさを決めるrXとrYを設定。丸だとradius( 半径)に設定されている、四角だと4角の位置
var rX:Number = Math.random() + 0.5;
var rY:Number = Math.random() + 0.5;
//「物理空間」追加するための大枠(b2Body)を設定
var body:b2Body;
//物体のgraphics 設定している。ifの後ろに続きあり。
var userData:Sprite = new Sprite();
var graphics:Graphics = userData.graphics;
graphics.beginFill(0x000000, 1);
if (Math.random() < 0.33){
// ③ 形の定義を作る
var boxDef:b2PolygonDef = new b2PolygonDef();
boxDef.SetAsBox(rX, rY); //上記で指定したrX,rYを利用
boxDef.density = 1.0;
boxDef.friction = 0.5;
boxDef.restitution = 0.2; //跳ね返りぐあい。
//物体定義 graphics設定の続き
graphics.drawRect( -rX / 2, -rY / 2, rX, rY);
graphics.endFill();
bodyDef.userData = userData;
bodyDef.userData.width = rX * 2 * 30;
bodyDef.userData.height = rY * 2 * 30;
// ② ①の定義を使って物理空間(b2world) に物体を作る。
body = m_world.CreateBody(bodyDef);
// ④ 形を ②で作った物体に追加している。
body.CreateShape(boxDef); // ③形(boxDef) を物体に追加している。
} else if(Math.random() < 0.66) {
var circleDef:b2CircleDef = new b2CircleDef();
circleDef.radius = rX;
circleDef.density = 1.0;
circleDef.friction = 0.5;
circleDef.restitution = 0.2;
graphics.drawCircle( 0, 0, rX);
graphics.endFill();
bodyDef.userData = userData;
bodyDef.userData.width = rX * 2 * 30;
bodyDef.userData.height = rX * 2 * 30;
body = m_world.CreateBody(bodyDef);
body.CreateShape(circleDef);
} else {
var radius:Number = rX;
var triangle_h:Number = Math.tan(60 * Math.PI / 180) * radius;
var tryangleDef:b2PolygonDef = new b2PolygonDef();
tryangleDef.vertexCount = 3;
tryangleDef.vertices[0].Set( -1 * radius, -1 * triangle_h / 2);
tryangleDef.vertices[1].Set( radius, -1 * triangle_h / 2);
tryangleDef.vertices[2].Set( 0, triangle_h / 2);
tryangleDef.density = 1.0;
tryangleDef.friction = 0.5;
tryangleDef.restitution = 0.2;
graphics.moveTo( -1 * radius, -1 * triangle_h / 2);
graphics.lineTo( radius, -1 * triangle_h / 2);
graphics.lineTo( 0, triangle_h / 2);
graphics.endFill();
bodyDef.userData = userData;
bodyDef.userData.width = rX * 2 * 30;
bodyDef.userData.height = rX * 2 * 30;
body = m_world.CreateBody(bodyDef);
body.CreateShape(tryangleDef);
}
body.SetMassFromShapes();
addChild(bodyDef.userData);
onUpdateHandler();
} |
4 シミュレーションの開始
今回のサンプルでは、ある一定の時間が経過したらオブジェクトを作成することを繰り返しています。
1 2 3 4 5
| var timer:Timer = new Timer(1000);
timer.addEventListener(TimeEvent.TIMER,onTimer); // onTimerは上記に書いた物理空間の物体作成へ
timer.start();
addEventListener(Event.ENTER_FRAME,onupdate); |
5. 描画
Box2DFlashAS3でWouldを生成し、Bodyを追加しただけでは何も表示されません。
ここでは、BodyのuserDataに登録されたDisplayオブジェクトにBodyの座標、回転角度を反映させることで、Worldの情報を表示に反映させています。
onupdateは物理空間(world)を更新する関数です。具体的には以下のようになっています。
1 2 3 4 5 6 7 8 9 10 11
| private function onupdate (event:Event = null):void{
var itrtations:int = 20; //物理動作の精度を指定
var timeStep:Number = 1.0 / 30.0; // フレームレート(1フレーム1/30秒)
m_world.Step(m_timeStep , iterations);
// 衝突判定
for(var bb:b2Body = m_world_bodyList; bb; bb= bb.m_next){
if(bb.m_userData is Sprite){
bb.m_userData.x = bb.GetPosition().x* 30;
bb.m_userData.y = bb.GetPosition().x* 30;
bb.m_userData.rotation = bb.GetAngle()* (180 / Math.PI); |
ここは意味不明な箇所。「1.0 / Flashで設定したフレームレート」という計算式を入れておけば違和感無く再生できるみたいです。今回はフレームレート(1フレーム1/30秒)です。
そもそもフレームレートとは:3次元グラフィックスの表示や動画の再生において、1秒間に何回画面を書き換えることができるかを表す指標。
こちらのサイトではこのように説明がしてあります。
1 2 3 4
| b2World.Step(dt:Number, iterations:int) : void-物理シミュレーションを更新する。
引数-dt:更新する時間(秒)を表す数値/iterations:精度を表す整数値。
戻り値-なし |
物理演算結果を表示に反映。これが無いと実際にステージ上に反映されない。
物理空間での情報を実際の画面のオブジェクトに当てはめている。
1 2 3 4 5 6 7 8 9
| //セットしたuserDataの位置と回転をオブジェクトのそれらにあわせる。
//
for(var bb:b2Body = m_world_bodyList; bb; bb= bb.m_next){
//m_userDataがSpriteオブジェクトなら。
if(bb.m_userData is Sprite){
bb.m_userData.x = bb.GetPosition().x* 30; //userDataの横軸をオブジェクトの横軸に
bb.m_userData.y = bb.GetPosition().x* 30; //userDataの縦軸をオブジェクトの縦軸に
bb.m_userData.rotation = bb.GetAngle()* (180 / Math.PI); ////userDataの回転をオブジェクトの回転に |
for(var bb:b2Body = m_world_bodyList; bb; bb= bb.m_next)がよくわからなかったんですが、強引に解釈してみます。
1、最初の値はm_world_bodyListにある任意のusuerData、
2、条件式はその取り出したuserData、
3、条件式での処理が終わると、m_nextとあるのでm_world_bodyListにある次のuserDataを呼び出す。
これの繰り返しです。
今回のサンプルは1秒ごとに四角、三角、丸いずれかのオブジェクトが上から落ちてくるので、この処理が永遠と繰り返されるのです。
条件式の中身は、上記2番で取り出したuserDataがSpriteならuserDataの各値を実際の表示オブジェクトに反映させている。
参考サイト
Box2D(Box2DFlashAS3)によるActionScript物理シミュレーション
Box2DFlashAS3(2.0.1) 衝突のテスト
[メモ]
SetAsBoxがわからない。
var boxDef:b2PolygonDef = new b2PolygonDef();
boxDef.SetAsBox(rX, rY);
サイズについて定義と物体をあわせなくてはならないと記載があったんですが、こちらもよくわからない所でした。
bodyDefは物理エンジンを搭載しているのでその中に形作ったuserDataを入れる
b2BodyDefとuserData のサイズをびったりあわせることが必要。サイズが違う何も無い所で衝突したりする。
Sprite オブジェクトはムービークリップと似ていますが、タイムラインを持ちません。Sprite は、タイムラインを必要としないオブジェクトに適した基本クラスです。たとえば、Sprite は、通常はタイムラインを使用しないユーザーインターフェイス (UI) コンポーネントの論理基本クラスになります。