Ioannis Panagopoulos blog

Tutorials on HTML5, Javascript, WinRT and .NET

XNA for Windows Phone 7 and Physics

by Ioannis Panagopoulos

In this post, we will see how easy it is to create an XNA world for Windows Phone 7 that obeys the laws of physics. To our quest, our weapon will be the Farseerphysics engine. You will be amazed when you realize how interesting a simple circle on the phone’s display becomes when you add some physics to its world! So let our quest begin…

Download the zip file from the project’s site. Unzip its contents and locate the folder containing the sources of the Farseer engine (at the time of this writing the folder is named “Farseer Physics Engine 3.2 XNA”).

Open Visual Studio and create a new project of “Windows Phone Game'” type (important note: if you are a newbie in XNA game development you are strongly encouraged to follow thisseries of blog posts and also download the developer tools from here).

The game we will be making will be as follows: The user sees a sprite on the screen and it gives momentum to the sprite (yellow ball) by applying a gesture to the phones display. The sprite should be bouncing at randomly placed obstacles on the display and it should hit the randomly placed silver balls (ouaou that’s interesting!)

 

SpaceGame

 

In an XNA Game for WP7 your canvas is 800x480 pixels size. Therefore we have created a background image in Photoshop of this size. The sprites that will be used in our game are the following:

 

Sprites

 

And now it is time to get started. When you have created your “Windows Phone Game” project, Visual Studio has created an extra project where you will add your sprites. We add all the aforementioned images (sprites) there following the well known “Add Item” procedure:

image

 

Now go to your solution’s folder and copy the Farseer project you have located before along with its containing folder to your solution folder. Right-click on your solution and select “Add/Existing Project”. Select the .csproj file for WP7 of the Farseer physics engine located in the folder you ‘ve just copied. Finally add a reference at your XNA game project to the engine (if you feel lost download the project at the end of this post and you will see the final state of the folders/projects). If you did everything correctly the solution will compile successfully. And this is all the plumbing you have to do. Now, let us do some physicz!

 

First we create the definitions of our sprites along with some lists of the positions of the obstacles and balls:

 

private Texture2D _background;
private Texture2D _myBall;
private Texture2D _floor;
private Texture2D _otherBall;
private Texture2D _obstacle;

private List<Vector2> _otherBallPositions;
private List<Vector2> _obstaclePositions;

private Random _r;

 

The definition of the random number generator will be used in the creation of the scene. In the LoadContent() method we create out Textures and select their positions on the display (note the random creation of a number of obstacles and balls and their positions):

 

protected override void LoadContent()
{
    spriteBatch = new SpriteBatch(GraphicsDevice);
    
    _background = Content.Load<Texture2D>("Background");
    _floor = Content.Load<Texture2D>("Floor");
    _myBall = Content.Load<Texture2D>("MyBall");
    _obstacle = Content.Load<Texture2D>("Obstacle");
    _otherBall = Content.Load<Texture2D>("OtherBall");

    _obstaclePositions = new List<Vector2>();
    for (int i = 0; i < _r.Next(1, 6); i++)
        _obstaclePositions.Add(new Vector2(_r.Next(100, 700), _r.Next(100, 400)));

    _otherBallPositions = new List<Vector2>();
    for (int i = 0; i < _r.Next(1, 6); i++)
        _otherBallPositions.Add(new Vector2(_r.Next(100, 700), _r.Next(100, 400)));

}

 

Initially we will draw everything on the display in the Draw() method as follows:

 

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);
    spriteBatch.Begin();
    spriteBatch.Draw(_background, new Vector2(0, 0), Color.White);
    spriteBatch.Draw(_floor, new Vector2(0, 459), Color.White);
    spriteBatch.Draw(_myBall, new Vector2(40, 432), Color.White);
    for (int i = 0; i < _obstaclePositions.Count; i++)
        spriteBatch.Draw(_obstacle, _obstaclePositions[i], Color.White);
    for (int i = 0; i < _otherBallPositions.Count; i++)
        spriteBatch.Draw(_otherBall, _otherBallPositions[i], Color.White);
    spriteBatch.End();
    base.Draw(gameTime);
}

 

This will generate a nice scene but everything will be static. Here is where we apply out physics engine to bring everything to life. Define a World object and create it in the Initialize method:

 

private World _world;

protected override void Initialize()
{
    _r = new Random();
    _world = new World(new Vector2(0,9.81F));
    base.Initialize();
}

 

The parameter is the gravity which we set it as a vector perpendicular to our x-axis (as is in real life). Now for every sprite in your world  you need to define its Body (mass, physical properties) and its Shape (its geometry). In the Farseer physics world you have to define the two and then connect them (to represent a single object) with a Fixture, actually saying that a body with mass X will be represented as a rectangle (for example) with Y,Z dimensions. Since this may be too much code to write, you may use a factory to do the job for you. For example for the floor (a rectangular body at the bottom of our game scene) we define:

In the LoadContent() method we create the Fixture for the floor which automatically defines a body of mass with density 1 and a rectangular shape of size the same as the floor’s texture:

 

private Fixture _floorFixture;
protected override void LoadContent()
{
    (…)
    _floor = Content.Load<Texture2D>("Floor");
    (…)
    _floorFixture=FixtureFactory.CreateRectangle(_world,_floor.Width,_floor.Height,1);
    _floorFixture.Body.BodyType = BodyType.Static; 
    _floorFixture.Body.Position=new Vector2(0, 459);

    (…)
}

 

Note that there is no connection with the texture, at least in code. The only thing we use the texture for is when we draw it on the position of the body. In the update method, we tell our world to process the position of the bodies (the Step method gets a parameter defining how much time has passed since the last step for the world) and then in the draw we use the result to draw the floor. The time that has passed is the actual-real time:

 

protected override void Update(GameTime gameTime)
{
    (…)
    _world.Step(gameTime.ElapsedGameTime.TotalSeconds);
    base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
    (…)
    spriteBatch.Draw(_floor, _floorFixture.Body.Position, Color.White);
    (…)
    base.Draw(gameTime);
}

 

Here is the first tricky part. If you use the aforementioned placement, the position of the floor texture will not correspond to the position of the floor body. This is because FarSeer considers the local shape’s origin point (0,0) to be at the center of the rectangle while XNA considers the same point (0,0) to be at the top left cornet of the rectangle. This is illustrated in the following figure:

image

Due to this, the “FarSeer world” and the “Visible world” have different opinions on where the body is located since the position of a body/texture defines the position of its local origin:

image

To compensate for this you need to do two things. First, place the floor to location (400,469), which will place the body in the correct position in the FarSeer world (left) and then change the local origin when drawing the texture from (0,0) to (400,10) (right) which will also position correctly the texture in the “Visible world”:

image

 

This in code is translated as follows:

 

    (…)
    // The following is in the LoadContent() method!
    _floorFixture=FixtureFactory.CreateRectangle(_world,_floor.Width,_floor.Height,100);
    _floorFixture.Body.BodyType = BodyType.Static;
    _floorFixture.Body.Position=new Vector2(400, 469); //THIS IS DIFFERENT NOW
    (…)
    // The following is in the Draw() method! 
    spriteBatch.Draw(_floor, _floorFixture.Body.Position, null, Color.White, _floorFixture.Body.Rotation, 
                      new Vector2(_floor.Width / 2,_floor.Height/2), 1, SpriteEffects.None, 1);
        

 

When you compile and run the program now nothing seems to have changed but believe me the physics world is there. To prove this just change the definition of Body.BodyType from BodyType.Static to BodyType.Dynamic and the floor will just fall from its position obeying the Newtonian rule of gravity! Let’s now follow the same approach for the other sprites in our world (the principle is exactly the same so below is the complete listing):

 

protected override void LoadContent()
{
    spriteBatch = new SpriteBatch(GraphicsDevice);
    
    _background = Content.Load<Texture2D>("Background");
    _floor = Content.Load<Texture2D>("Floor");
    _myBall = Content.Load<Texture2D>("MyBall");
    _obstacle = Content.Load<Texture2D>("Obstacle");
    _otherBall = Content.Load<Texture2D>("OtherBall");

    _floorFixture=FixtureFactory.CreateRectangle(_world,_floor.Width,_floor.Height,1);
    _floorFixture.Body.BodyType = BodyType.Static;
    _floorFixture.Restitution = 0.7f;
    _floorFixture.Body.Position=new Vector2(400, 469);

    _otherBallFixtures = new List<Fixture>();
    for (int i = 0; i < _r.Next(1, 16); i++)
    {
        Fixture Temp = FixtureFactory.CreateCircle(_world, _otherBall.Height / 2, 1);
        Temp.Body.Position = new Vector2(_r.Next(100, 700), _r.Next(100, 400));
        Temp.Body.IsStatic = false;
        Temp.Restitution = 0.7f;
        _otherBallFixtures.Add(Temp);
    } 

    _obstacleFixtures = new List<Fixture>();
    for (int i = 0; i < _r.Next(1, 6); i++)
    {
        Fixture Temp = FixtureFactory.CreateRectangle(_world,_obstacle.Width,_obstacle.Height,100);
        Temp.Body.BodyType = BodyType.Static;
        Temp.Body.Position=new Vector2(_r.Next(100, 700), _r.Next(100, 400));
        Temp.Restitution = 0.7f;
        _obstacleFixtures.Add(Temp);
    } 
}

 

And the Draw() is as follows:

 

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);
    spriteBatch.Begin();
        spriteBatch.Draw(_background, Vector2.Zero, Color.White);
        spriteBatch.Draw(_floor, _floorFixture.Body.Position, null, Color.White, 
                         _floorFixture.Body.Rotation, new Vector2(_floor.Width / 2,_floor.Height/2), 
                          1, SpriteEffects.None, 1);
        spriteBatch.Draw(_myBall, new Vector2(40, 432), Color.White);
        for (int i = 0; i < _obstacleFixtures.Count; i++)
            spriteBatch.Draw(_obstacle, _obstacleFixtures[i].Body.Position, null, Color.White, 
                             _obstacleFixtures[i].Body.Rotation, 
                             new Vector2(_obstacle.Width / 2, _obstacle.Height / 2), 1, 
                             SpriteEffects.None, 1);
        for (int i = 0; i < _otherBallFixtures.Count; i++)
            spriteBatch.Draw(_otherBall, _otherBallFixtures[i].Body.Position, null, Color.White, 
                             _otherBallFixtures[i].Body.Rotation, 
                             new Vector2(_otherBall.Width / 2, _otherBall.Height / 2), 1, 
                             SpriteEffects.None, 1);
    spriteBatch.End();
    base.Draw(gameTime);
}

 

Note that we have also defined the Restitution coefficient for our bodies which defines how much of the initial velocity is lost when the ball will bounce on the obstacle (1 for example will have the ball bouncing endlessly like being made from a very elastic rubber, while a 0 will make the ball behave like it is made from iron).

Now when you run the program all the balls will fall to the floor and will also bounce. But the first thing you will notice is that they are too slow. You can go experimenting by raising the gravity value or increasing the time in the Step() method but those approaches are all trial and error and you will not get to far by this. The actual problem is the fact that you are playing with very large numbers in the FarSeer engine. While gravity and mass are defined in Kg and m/sec2 without any issues, you are defining shapes like the floor that are 800 meters long and 20 meters tall since the pixel to meter ratio is 1:1. This causes the engine to overflow. To get more realistic results you need to define the pixels/meter ratio to something different. In our case, let’s say that 1 pixel is 50 meters. This means that our view will be 16m wide and 9,6m tall (800x480 pixels) which is fine. Define a new private field as follows:

 

private float _pixelsPerMeter = 50;

Now when you define a shape you need you divide by this. For example:

 

_floorFixture = FixtureFactory.CreateRectangle(_world, _floor.Width / _pixelsPerMeter, 
                                                       _floor.Height / _pixelsPerMeter, 1f);
_floorFixture.Body.BodyType = BodyType.Static;
_floorFixture.Restitution = 0.7f;
_floorFixture.Body.Position = new Vector2(400 / _pixelsPerMeter, 469 / _pixelsPerMeter);

 

That is you are translating everything from pixels to meters for the FarSeer world. When you draw you need to do the opposite as follows:

 

spriteBatch.Draw(_floor, _floorFixture.Body.Position * _pixelsPerMeter, null, Color.White, 
                         _floorFixture.Body.Rotation * _pixelsPerMeter, 
                          new Vector2(_floor.Width / 2, _floor.Height / 2) , 1, SpriteEffects.None, 1);

Now when you compile and run everything will make more sense. It takes approx. 1 sec for the ball to fall from the top to the bottom (4km) at 9.81 g. Well let’s see:

image

Well as you can see you have a perfectly realistic world that obeys the laws of physics without experimenting. If you change your ratio to 25 your world is bigger, your balls are higher and it will take them twice the time to reach the bottom. The only thing left to do is apply some force to the yellow ball by the use of gestures.

First we define the yellow ball as a body too, like we did for the other sprites and then we add in the Update() method the following:

 

while (TouchPanel.IsGestureAvailable)
{
    GestureSample gesture = TouchPanel.ReadGesture();
    if (gesture.GestureType == GestureType.Flick)
        _myBallFixture.Body.ApplyForce(Vector2.Divide(gesture.Delta, 2.0f));
}

 

This will constantly check for gestures and apply them as a force to the ball. The last thing to be done is to enable this type of gestures at the initialization method as follows:

 

TouchPanel.EnabledGestures = GestureType.Flick;

 

And this concludes our introduction to the physics engine. The project can be downloaded  

.

 

Shout it
blog comments powered by Disqus
hire me