カテゴリー : 物理エンジン

2/14 Box2DFlashAS3 メモ3

Box2DFlashAS3

やる気の低下でさぼってましたが、「メディア芸術祭」を見てモチベーション新たに頑張っていきます。
Webで飯が食えるようにしたい。

重心の設定

前回のエントリーで物理空間の設定から動体、静体の作り方に関しては大体理解したつもりです。

  1. 物理空間の作成(b2AABBクラス、重力はb2Vec2クラス)
  2. 静体の作成。b2BodyDefクラス、b2SpriteDef(b2PolygonDef,b2CircleDefなど)クラスを使う
  3. 動体の作成。静体と同じようにb2BodyDef,b2SpriteDefを使う。動体の場合は密度(density)や反発関数(restitution)を設定する。SetMassFromShapes()を使い質量を設定する
  4. 描画する。b2DeBugDraw()を使う。

今回はその物体をどのように動かしていくかなどを、このサイトを参考にして勉強していきたいです。

重心の設定。
形が複雑な図形の場合重心がづれて自然の動きを表現することが難しい場合があります。
その際にはb2MassDataを使います。

また物体の慣性を加えるとより自然に見えます。
b2MassData.Iプロパティは「回転の慣性」を制御するプロパティです

1
2
3
4
5
var massData:b2MassData = new b2MassData();
massData.mass = _bArrow.GetMass(); //重心を再設定
massData.center = new b2Vec2(0,0);  //新しい重心を決める
massData.I = _bArrow.GetInertia(); //b2MassData.Iプロパティは「回転の慣性」を制御するプロパティです
_bArrow.SetMass(massData) //新しい重心をセット

物体に力を加える

b2BodyのメソッドSetLinearVelocity()で物体にかかる力を設定します。
物体に力を加える際にはApplympulse()を使います。
継続的に力を加える場合はApplyForce()を使います。
逆に原則の場合はlinearDamping()を使います。これはb2Bodyではなく、b2BodyDefで設定している。

1
2
b2Bobyl.SetLinearVelocity(new b2Vec2(5, 0));
b2Boby.ApplyImpulse(new b2Vec2(5, 0), b2Body.GetWorldCenter().Copy());

以前にも勉強したがb2Vec2は重力を表しています。b2Vec2(水平にかかる力、垂直にかかる力)
通常物理世界には重力としてb2Vec2(0,9.8);の力がかかっています。

ApplyImpluse()に関しては作用点を第二引数で設定できます。今回の例ではb2Bodyの中心を作用点にしています。
重心以外に設定をすると、物体に回転力が加わり回転をつけることができます。

物体に回転を加える

力を加えるように回転も加えることができます。

b2Body.SetAngularVelocity(1秒あたりの回転角度(ラジアン)を表す数値。)メソッド、
継続的に回転を加える場合はb2Body.ApplyTorque(物体に与える回転力を表す数値。)メソッドになります。

※スリープするしないがわかりにくいのでよう勉強する必要ありです。

2/9 Box2DFlashAS3 メモ2

Box2DFlashAS3

今日はこのサイトこのサイトを使ってさらにBox2DFlashAS3に関する認識を深めていきたいです。

まずは物理エンジンの設定

1
2
3
4
5
6
7
var  worldAABB : b2AABB = new b2AABB();
world.lowerBound.Set(-100,-100);
world.upperBound.Set(100,100);

var gravity:b2Vec2 = new b2Vec2(0,9.8);

_world = new b2World(worldAABB,gravity,true);

b2AABBクラスをインスタンス化して、シミュレーションの範囲を作り、lowerBound.Set(左上の座標) , upperBound.Set(右下の座標)で設定している。
注意点はこの際の数値はPixelではなくM(メートル)で表示されています。これは後で適正な大きさに設定しています。

gravityは物理空間の重力を表しています。
b2Vec2は第一引数に水平位置、第二引数に垂直位置がしていできます。今回は重力ということで垂直位置にだけ値が(メートル)で設定されています。

b2Worldは物理空間を作っています。
第一引数は、b2AABBオブジェクト
第二引数は、シミュレーションに常時かかる力を表すb2Vec2オブジェクト
第三引数は、doSleepを有効にするかどうか。よほどのことが無い限りtrueにしておく。

物体の作成

固定された静体と、動ける動体とあるが基本作成方法は同じです。

まずは静体の作成。

1
2
3
4
5
6
7
8
            var bdFloor:b2BodyDef = new b2BodyDef();
            bdFloor.position.Set(5.5 / 2, 3);
           
            var pdFloor:b2PolygonDef = new b2PolygonDef();
            pdFloor.SetAsBox(2, 0.15);
           
            var bFloor:b2Body = _world.CreateBody(bdFloor);
            bFloor.CreateShape(pdFloor);

b2BodyDefを使い物体の位置を指定します。

b2SpriteDefを使い物体の形を指定します。
b2SpriteDefには、四角形を表すb2PolygonDefや、丸を表すb2CircleDefなどがあります。
b2PolygonDefの場合はSetAsBox()を使い範囲を設定します。
b2CircleDefの場合はradiusで半径をしていします。

最後にb2Bodyを使って、b2BodyDef、b2SpritDefを当てはめ物体を生成します。

続きまして動体を見てみましょう。

1
2
3
4
5
6
7
8
9
10
11
            var bdBox:b2BodyDef = new b2BodyDef();
            bdBox.position.Set(5.5 / 2, 1);
         
            var pdBox:b2PolygonDef = new b2PolygonDef();
            pdBox.SetAsBox(0.3, 0.2);
            pdBox.density = 1;    //密度(1平方メートルあたりの重量(kg))
           
            _bBox = _world.CreateBody(bdBox);
            _bBox.CreateShape(pdBox);
         
            _bBox.SetMassFromShapes();

静体と同じようにb2BodyDef,b2SpriteDef,b2Bodyクラスを使っています。

違いは質量を設定している所です。
densityは物体の密度を表しています。SetMassFromShapes()を使って質量を有効にしています。

実際に画面に表示する

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
     var dd:b2DebugDraw = new b2DebugDraw();
     dd.m_sprite = this;
     dd.m_drawScale = 100;      //1m を100ピクセルにする
     dd.m_fillAlpha = 0.3;        //塗りのアルファ値
     dd.SetFlags(b2DebugDraw.e_shapeBit);      //物体を表示する設定に
     _world.SetDebugDraw(dd);      //物理ワールドにセット

            //▼イベントリスナーの追加
            addEventListener(Event.ENTER_FRAME, _enterFrameHandler);
        }

        //▼イベントリスナー定義
        private function _enterFrameHandler(eventObj:Event):void {
            _world.Step(1 / 24, 10);//物理シミュレーションの更新
}

b2DebugDrawクラスを使って作成した物体を画面に表示していきます。
イベントリスナーではStep()を使って物理的な動きを適切に処理しています。
第一引数は更新する時間(秒)を表す数値
第二引数は精度を表す整数値。

2/7 Box2DFlashAS3 メモ

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) コンポーネントの論理基本クラスになります。