Rewriting example from Flash/Box2D

Topics: Developer Forum, User Forum
Apr 13, 2012 at 4:47 AM

Hi guys,

I've been going over some great demos and examples from Emanuele Feranto (a Flash developer who's done a lot with Box2D). I took his original Flash example for beginners and tried porting it to Farseer and XNA on the phone.

Here's his original code:

http://www.emanueleferonato.com/2009/01/27/box2d-tutorial-for-the-absolute-beginners/

Here's my complete code (with comments):

public class Game1 : Game
{
    private readonly GraphicsDeviceManager graphics;
    private Texture2D blankTexture;
    private Body body;
    private Body floor;
    private SpriteBatch spriteBatch;
    private int timecounter;
    private float timer;
    private World world;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this)
                        {
                            PreferredBackBufferHeight = 800, 
                            PreferredBackBufferWidth = 480, 
                            IsFullScreen = true
                        };

        Content.RootDirectory = "Content";

        // Frame rate is 30 fps by default for Windows Phone.
        TargetElapsedTime = TimeSpan.FromTicks(333333);

        // Extend battery life under lock.
        InactiveSleepTime = TimeSpan.FromSeconds(1);
    }

    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);

        // Create a blank texture for drawing
        blankTexture = new Texture2D(GraphicsDevice, 5, 5, false, SurfaceFormat.Color);
        var color = new Color[25];
        for (int i = 0; i < color.Length; i++)
        {
            color[i] = Color.White;
        }
        blankTexture.SetData(color);

        // Create our World with a gravity of 10 vertical units
        if (world == null)
        {
            world = new World(Vector2.UnitY*10);
        }
        else
        {
            world.Clear();
        }

        // Create and position our floor
        floor = BodyFactory.CreateRectangle(
            world, 
            ConvertUnits.ToSimUnits(480), 
            ConvertUnits.ToSimUnits(50), 
            10f);
        floor.Position = ConvertUnits.ToSimUnits(240, 775);
        floor.IsStatic = true;
        floor.Restitution = 0.2f;
        floor.Friction = 0.2f;
    }

    protected override void Update(GameTime gameTime)
    {
        // Allows the game to exit
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            Exit();

        // Create a random box every second
        timer += (float) gameTime.ElapsedGameTime.TotalSeconds;
        timecounter += (int) timer;
        if (timer >= 1.0f)
        {
            // Reset our timer
            timer = 0f;

            // Determine a random size for each box
            var random = new Random();
            var width = random.Next(20, 100);
            var height = random.Next(20, 100);

            // Create it and store the size in the user data
            Body box = BodyFactory.CreateRectangle(
                world,
                ConvertUnits.ToSimUnits(width), 
                ConvertUnits.ToSimUnits(height), 
                10f,
                new Point(width, height));

            box.BodyType = BodyType.Dynamic;
            box.Restitution = 0.2f;
            box.Friction = 0.2f;
                
            // Randomly pick a location along the top to drop it from
            box.Position = ConvertUnits.ToSimUnits(random.Next(50,400), 0);
        }

        // Advance all the elements in the world
        world.Step(Math.Min((float) gameTime.ElapsedGameTime.TotalMilliseconds*0.001f, (1f/30f)));

        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.FromNonPremultiplied(51, 51, 51, 255));

        // Setup the drawing
        spriteBatch.Begin();

        // Draw the floor
        spriteBatch.Draw(
            blankTexture, 
            new Rectangle(
                (int)ConvertUnits.ToDisplayUnits(floor.Position).X-240, 
                (int)ConvertUnits.ToDisplayUnits(floor.Position).Y-25, 
                480, 
                50), 
            null,
            Color.FromNonPremultiplied(88, 139, 88, 255),
            floor.Rotation,
            new Vector2(0, 0), 
            SpriteEffects.None, 
            0);

        // Draw each box
        foreach (var box in world.BodyList)
        {
            // Only boxes are dynamic
            if (box.BodyType != BodyType.Dynamic) continue;

            // Get our saved size from our user data
            var size = (Point) box.FixtureList[0].UserData;

            // Create an XNA rectangle from the size and shape
            var destinationRectangle = new Rectangle(
                (int) (ConvertUnits.ToDisplayUnits(box.Position.X)-((int)(size.X/2))),
                (int) (ConvertUnits.ToDisplayUnits(box.Position.Y)-((int)(size.Y/2))), 
                size.X, 
                size.Y);

            // Draw it using our blank texture
            spriteBatch.Draw(blankTexture, 
                                destinationRectangle,
                                null,
                                Color.FromNonPremultiplied(139, 139, 139, 255),
                                box.Rotation,
                                new Vector2(0, 0),
                                SpriteEffects.None, 
                                0);
        }

        // Finish up
        spriteBatch.End();

        base.Draw(gameTime);
    }
}

It works, for the most part. There are two or three big problems that I'm hoping someone can help a n00b out with:

  1. The rotation of the boxes isn't right. I'm using the rotation of the object but not sure. If you put this into an application you'll see the boxes go spinning all over the place. I tried using MathHelper.WrapAngle(box.Rotation) but that didn't help
  2. The boxes don't look like they're quite right so I think what I'm drawing on the screen isn't matching what Farseer is producing (I thought I was doing all the conversions right?)
  3. This thing runs for awhile but then crawls, probably because in a few minutes there are hundreds of Body objects in the world. I'm assuming I should be doing something like checking if the box is on screen and if not remove it from the world?

The calls to ConvertUnits is just using the class from the WP7 samples.

Any help would be appreciated. I think I'm starting to grok this stuff. Thanks! 

Apr 13, 2012 at 2:24 PM

Update: After putzing around with transformations I just got lazy and dropped in the DebugViewXNA code from the sample and watched how it worked perfectly. I'm chasing down how it draws it's shape because I guess you have to do some matrix transformations (even with a simple 2D phone surface that I'm drawing to) to get the right co-ordinates. In any case, the DrawShape method of DebugViewXNA works great, I just have to figure out how to extrapolate that into a regular XNA Rectangle to do something like render a texture for the shape properly.

Apr 13, 2012 at 11:47 PM

Posted my XNA/Farseer version of the original tutorial online at

http://weblogs.asp.net/bsimser/archive/2012/04/13/farseer-tutorial-for-the-absolute-beginners.aspx

There's also a YouTube video of it. I think I got it pretty much rendering exactly like the original (cheating and using DebugViewXNA)

Apr 14, 2012 at 2:58 PM

Hi nice tutorial! Also I'd like to point out that stacking boxess is not an easy task for a physics engine for several reasons:
1. Number of objects and performance.
2. Tunnelling because of fall speed (fast moving objects).
3. Stacking stability in general.
4. Stacking potentially heavy things on top of lighter things (that's actually a non solvable issue).

So all in all great demonstration of Farseer in a pretty difficult scenario. If I might add a suggestion can you post the code itself as a zip or at least make it so that it can be copy-pasted without the line numbers (which are a pain to remove). Also add the usings section as beginners might have a problem with what references are needed.


As for your original code without the debugview there is something iffy. For example regarding rotation: in Farseer a body rotates around it's center but what about XNA? It rotates about the "origin" point which is passed as a parameter. It our case Vector(0,0) doesn't correspond to the center but to the top-left corner. This would affect translation too etc. You can try to improve that manually or use the code from the samples which you are probably looking at right now.

Apr 15, 2012 at 7:47 PM

@jerrysb thanks for the info. I'll update the post with a zip of the source code.