いまさらながらBox2Dを勉強してみる。物理計算(特に力学系)をやる場合、毎回必要な分だけ生書きしてたのですが、やはりライブラリ使ったほうが効率的かなぁ(特にプロトタイプとか)、という当たり前な理由でまずは触ってみようということで。
勉強するにあたって「Flash OOP本の10章」と、「Box2DでActionScript物理プログラミング」にお世話になります。わかりやすいなぁ。
使用したのは、いつのまにかバージョンが上がっていた、Box2DFlashAS3 2.0.1です。
上記「OOP本」と「Box2DでActionScript物理プログラミング」を眺めるに、大体の流れは、
1.ワールドをつくる
2.BodyなDefとShapeなDefで位置とか形状をつくる
3.BodyとShapeの実体をつくる
4.計算してみる
5.表示に反映させる
ってことみたいですね(今回はJointとか衝突イベントやってないです)。「5.表示に反映させる」については、テスト用に「b2DebugDraw」というクラスがあるので、それを使えばすぐに確認できてお手軽です。
※「OOP本」はBox2Dのバージョンがわかりませんが1.x系なんでしょうか、ちょっとメソッドとか異なるようです。でも流れは同じようなので参考になります。「Box2DでActionScript物理プログラミング」のほうは 2.0.0 なのでそのままいけそうなんですが、b2World の CreateStaticBody,CreateDynamicBodyが 2.0.1 ではなくなってる様子。CreateBodyにしとけば大丈夫ぽい。
で、とりあえず試してみたかったのは、重加速度とShape属性(サイズとか)の動的変更で、その辺りはBox2Dのソースを追いながらアテを探してみることに。ソースを追うにはFlashDevelopが便利ですね。しばらく見たところ、重加速度は、b2World の m_gravity 、Shapeの属性は「3.BodyとShapeの実体をつくる」でつくったb2Shapeから辿ればいけるっぽい。感じ。
そんな流れでつくってみた、ちょっと気持ち悪いBox2Dのサンプルです。
マウスの位置で重加速度が変化して、クリックで円が増えます。で、円をsinで大きさを変えてます。ソースは以下のような感じ。
TestWorld.as
package { import Box2D.Collision.b2AABB; import Box2D.Common.Math.b2Vec2; import Box2D.Dynamics.b2World; import Box2D.Dynamics.b2BodyDef; import Box2D.Collision.Shapes.b2PolygonDef; import Box2D.Collision.Shapes.b2CircleDef; import Box2D.Dynamics.b2Body; import Box2D.Collision.Shapes.b2Shape; import Box2D.Collision.Shapes.b2CircleShape; import Box2D.Dynamics.b2DebugDraw; import flash.geom.Rectangle; import flash.display.MovieClip; import flash.events.Event; import flash.events.MouseEvent; /** * Box2Dなテスト * @author nutsu */ public class TestWorld extends MovieClip { private var world:b2World; private var WORLD_SCALE:Number = 100; // 100pixel/m private var VIEW_RECT:Rectangle; private var half_width:Number; private var half_height:Number; private var circles:Array; public function TestWorld() { super(); VIEW_RECT = new Rectangle( 0, 0, stage.stageWidth/WORLD_SCALE, stage.stageHeight/WORLD_SCALE ); half_width = stage.stageWidth * 0.5; half_height = stage.stageHeight * 0.5; //動的オブジェクト用の配列 :b2CircleShape[] circles = []; //とりあえずワールドつくる initWorld(); //静的なオブジェクト(四方壁)をつくる initObjects(); //テストドローな設定 initDraw(); //とりあえず1こオブジェクトつくる addCircle( VIEW_RECT.width*0.5, VIEW_RECT.height*0.5 ); addEventListener( Event.ENTER_FRAME, onEnterFrame ); stage.addEventListener( MouseEvent.CLICK, onClick ); stage.quality = "low"; } private function initWorld():void { //物理計算エリア? お約束らしい var aabb:b2AABB = new b2AABB(); aabb.lowerBound.Set( -1000, -1000 ); aabb.upperBound.Set( 1000, 1000 ); //重加速度。後でマウス位置で変えるので0にしとく var gravity:b2Vec2 = new b2Vec2( 0, 0 ); //力が作用してないオブジェクトをスリープするか否か。今回はずっと動いてるのでfalse var dosleep:Boolean = false; //ワールドつくる world = new b2World( aabb, gravity, dosleep ); } private function initObjects():void { //四方壁つくる //大きさの単位はメートルなそうな var wall_size:Number = 0.025; //ShapeなDef(形状決め) var wall_side_shape:b2PolygonDef = new b2PolygonDef(); wall_side_shape.SetAsBox( wall_size, VIEW_RECT.height * 0.5 ); wall_side_shape.density = 0; //静的な物体は0なそうな var wall_vt_shape:b2PolygonDef = new b2PolygonDef(); wall_vt_shape.SetAsBox( VIEW_RECT.width*0.5, wall_size ); wall_vt_shape.density = 0; //BodyなDef(位置決め) var wall_left:b2BodyDef = new b2BodyDef(); var wall_right:b2BodyDef = new b2BodyDef(); var wall_bottom:b2BodyDef = new b2BodyDef(); var wall_top:b2BodyDef = new b2BodyDef(); //set position wall_left.position.Set( wall_size, VIEW_RECT.height * 0.5 ); wall_right.position.Set( VIEW_RECT.width - wall_size, VIEW_RECT.height * 0.5 ); wall_bottom.position.Set( VIEW_RECT.width * 0.5 , VIEW_RECT.height - wall_size); wall_top.position.Set( VIEW_RECT.width*0.5 , wall_size); //worldに入れてく world.CreateBody( wall_left ).CreateShape( wall_side_shape ); world.CreateBody( wall_right ).CreateShape( wall_side_shape ); world.CreateBody( wall_bottom ).CreateShape( wall_vt_shape ); world.CreateBody( wall_top ).CreateShape( wall_vt_shape ); } private function initDraw():void { //デバッグ用のドロー設定 var debug_draw:b2DebugDraw = new b2DebugDraw(); debug_draw.m_sprite = this; debug_draw.m_drawScale = WORLD_SCALE; debug_draw.m_fillAlpha = 1; debug_draw.m_lineThickness = 0; debug_draw.m_drawFlags = b2DebugDraw.e_shapeBit; world.SetDebugDraw( debug_draw ); } private function addCircle( ax:Number, ay:Number ):void { //任意の位置にCircleオブジェクト追加してく //手順 ShapeなDef→BodyなDef→worldで実体をつくる感じ var s:b2CircleDef = new b2CircleDef(); s.density = 1; s.restitution = 0.2; s.radius = 0.2; var b:b2BodyDef = new b2BodyDef(); b.position.Set( ax, ay ); var bb:b2Body = world.CreateBody( b ); var bs:b2Shape = bb.CreateShape( s ); bb.SetMassFromShapes(); //Shapeはあとでいじるので配列にいれとく bs.m_userData = Number(0); circles.push( b2CircleShape(bs) ); } private function onClick( e:MouseEvent ):void { addCircle(mouseX/WORLD_SCALE, mouseY/WORLD_SCALE); } private function onEnterFrame( e:Event ):void { if ( world ) { //マウス位置で重加速度変更 world.m_gravity.x = -2 * (half_width - mouseX) / half_width; world.m_gravity.y = -2 * (half_height - mouseY) / half_height; //円をうにょうにょ for (var i:int = 0; iなんと言いますか、お手軽だなぁという印象です。ライブラリって便利。
誰かに向けたメモ:
Box2Dのソースみていると、virtual があるんですが、AS に virtual ってあったんですか?自分で書いてみたらコンパイル通るみたいなんですけど、何か意味あるのかな…。