がりらぼ

WindowsRuntimeの応援ブログ

FarseerPhysicsEngine入門

XNAでよくつかわれる物理エンジンFarseerPhysicsEngineを最近使うので簡易入門。

  • FarseerPhysicsEngineって?

XNAで2Dの物理エンジンゲームを作ることができる良いライブラリです。
ただしとてつもなく資料が少ない。
公式のマニュアルはバージョン3.3に対応していないし、クラスドキュメントもない。サンプルはある程度充実しているが難しい。
作り始めると簡単。WindowsPhone7にも対応。
大まかな流れは

ワールドを作る

物体のBodyを配置する。

ワールドの時間を進める

描画

のような感じです。

  • インストール

公式サイトよりFarseer Physics Engine 3.3.1 Samples XNA.zipをDLして
FarseerPhysicsXNA.dllをプロジェクトの参照に追加。

  • ワールド
World world=new World(new Vector2(0,9.8f));

Worldクラスのインスタンスを生成します。コントラクタにワールドの重力を入れます。

  • Body作成
Body body=BodyFactory.CreateRectangle(world,25/MeterInPixels,25/MeterInPixels,1f,new Vector2(50/MeterInPixels,50/MeterInPixels));

body.Restitution = 0.3f;
body.BodyType = BodyType.Dynamic;
body.Friction = 0.5f;

BodyFactoryクラスのCreateRectangleメソッドでBodyを作成します。
第一引数にワールド、第二にBodyの幅、第三にBodyの高さ,第四に密度、第五に座標です。
ここで注意しなければいけないのがこの物理エンジンでは単位がメートルであるということです。
座標などを指定するときは今までのピクセル単位をメートルに変換しなければいけません。変換するときの定数は別になんでもいいのですが、64ぐらいが妥当なので、

const float MeterInPixels=64f;

と定義しておいて、物理エンジンに座標を渡すときは必ずメートルに変換して渡しましょう。

各パラメータの設定をします。

  • Restitution→反発力
  • BodyType→BodyType.Dynamicで動くオブジェクト、BodyType.Staticで動かないオブジェクト
  • Friction→摩擦力
  • ワールドの時間を進める

updateメソッド内に

world.Step((float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.001f);

ワールドの時間を進めます。引数の謎の数字は私にもわかりません。

  • 描画する

Bodyクラスを作っただけではテクスチャは描画されません。
あたり判定など、あくまでワールドでの存在を定義しただけです。
これと対応したテクスチャを描画しなければいけません。

 spriteBatch.Begin();
            
spriteBatch.Draw(texture,body.Position*MeterInPixels,null,Color.White,body.Rotation,new Vector2(texture.Width/2f,texture.Height/2f),1f,SpriteEffects.None,0.5f);

spriteBatch.End();

座標を指定するときはメートルからピクセルに逆変換する必要があります。
64乗算するだけですが。

サンプルコード

public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        World world;
        Body body;
        Texture2D texture;
        const float MeterInPixels = 64f;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

       
        protected override void Initialize()
        {

            world = new World(new Vector2(0,9.8f));
            texture = Content.Load<Texture2D>("texture");
            body = BodyFactory.CreateRectangle(world, texture.Width / MeterInPixels, texture.Height / MeterInPixels, 1f, new Vector2(50 / MeterInPixels, 50 / MeterInPixels));

            body.Restitution = 0.3f;
            body.BodyType = BodyType.Dynamic;
            body.Friction = 0.5f;
            base.Initialize();
        }

       
        protected override void LoadContent()
        {
            
            spriteBatch = new SpriteBatch(GraphicsDevice);
            
           
        }

      
        protected override void UnloadContent()
        {
            
        }

      
        protected override void Update(GameTime gameTime)
        {
            
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            world.Step((float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.001f);

            base.Update(gameTime);
        }

       
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();

            spriteBatch.Draw(texture, body.Position * MeterInPixels, null, Color.White, body.Rotation, new Vector2(texture.Width / 2f, texture.Height / 2f), 1f, SpriteEffects.None, 0.5f);

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }