Part 1 – Hello Physics World

As our game will be a physics based game, the first post will introduce the basic concepts of the physics engine.

We will not implement our own engine but rather use the same one used by Angry Birds, called Box2D.
Box2D is an open-source physics engine, which has been ported to almost all common programming environments. For the .NET platform, we will use Farseer, which is based on Box2D and adds some awesomesauce on top of it.

This being said, let’s dive into code.

Start by creating a new WP7 XNA project. I have called mine ‘PigsFly’.

part1-new project

You should now have a solution with actually 2 projects instead of 1: PigsFly and PigsFlyContent. As the name implies, PigsFlyContent will house all of our content such as images and audio.

The main project contains a class called Game1. This class will act as the entry point of our game and contains an Update and Draw method.
All XNA games follow the gameloop pattern: as our games runs at 30 frames per second, each 30th of a second XNA will call the Update method, followed by the Draw method. Update lets us process input and run our game logic.
Draw will render our scene on screen. Here is a major difference with classic applications: every frame needs to be redrawn from scratch!

See if you can run the ‘game’ at this point in time. Make sure to select the Windows Phone 7 Emulator if you don’t have access to an actual device.
As we are going to make a game which will be played in landscape mode, rotate the emulator accordingly:

part1-emu_cornflowerblue

If you made it to this point, we can continue adding the Farseer library to our game.

Referencing Farseer is as easy as installing a NuGet package:

Right click on References and select Add Library Reference.

Click on Online on the left side and in the upper right corner search for “Farseer”.

part1-addfarseer

We will now create the ‘hello world’ equivalent of the physics engine: two boxes will drop from the sky to the ground and react to each other upon impact.

In order to do this we need only a few objects
– a Texture2D which will represent the boxes
– a physics World which will run the physics simulation
– a list of Body objects which represent the boxes in our simulation

First, let’s add the following using statements to the Game1 class:

using FarseerPhysics.Dynamics;
using FarseerPhysics.Factories;

Add the following fields to your Game1 class:

private Texture2D _box;
private List<Body> _bodies = new List<Body>();
private World _world;
private const float Scale = 100f;

Notice how we also define a Scale field set to 100f. This is because the physics engine works best when thinking in terms of units such as kilograms and meters.

When we define a box of 1 meter by 1 meter in Farseer, we don’t want to represent it on the screen with a 1 pixel texture. Instead, we will scale all simulation units by 100, thus representing our box by a 100×100 pixels texture.
For our ‘hello world’ we will use a 40×40 pixels square texture. Taking the scale into account means we will create boxes of 0.4×0.4 in our physics engine.
You can create your own image for this or grab this beautiful texture I have created for your convenience:

box

To add the image as a texture to our game, right click on the content project and Select Add | Existing item. Now select the image (called box.png, if you use the image above). The image will be converted to a Texture and will be given an asset name. In our case its name will be ‘box’.

XNA provides us with 3 methods to complete in the Game1 class: LoadContent, Update and Draw.

First, we will implement LoadContent as such:

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

	// Load texture
	_box = Content.Load<Texture2D>("box");

	// Create new simulation world
	_world = new World(new Vector2(0, 6f));

	// Define the ground
	float simulatedHeight = GraphicsDevice.Viewport.Height / Scale;
	float simulatedWidth = GraphicsDevice.Viewport.Width / Scale;
	BodyFactory.CreateEdge(_world, new Vector2(0.0f, simulatedHeight), new Vector2(simulatedWidth, simulatedHeight));

	// Create 2 boxes
	Body box1 = BodyFactory.CreateRectangle(_world, 0.4f, 0.4f, 1f);
	box1.Position = new Vector2(4, 0.75f);
	box1.BodyType = BodyType.Dynamic;
	_bodies.Add(box1);

	Body box2 = BodyFactory.CreateRectangle(_world, 0.4f, 0.4f, 1f);
	box2.Position = new Vector2(4.2f, 0.0f);
	box2.BodyType = BodyType.Dynamic;
	_bodies.Add(box2);
}

We load the texture on line 07, which is nothing out of the ordinary.

On line 10 we create our physics world and initialize it with a gravity Vector2 set to (0, 6). This means gravity will go straight down (x set to 0) and with a acceleration factor of 6m/s².

Lines 13-15 contain the creation of the ground for our world. In this case we set it equal to the bottom of the WP7 screen. Remember our simulated word runs at 1/100th the size of the screen.

Finally, on lines 18 though 26 we create two bodies: rectangular boxes of 0.4×0.4m. The first one is located 4m to the right, the second 4.2m and a little bit higher. As our screen is 800 pixels wide, 4m in simulation world equals 400 pixels in screen world. In other words: the middle of the screen.
We keep both Body objects in a List so we can iterate over them in our Draw method later on.

Taking into account the coordinate system XNA uses, this is what the world we defined looks like:

part1-emu_coords

Now, we can complete the Update method to let the simulation run every frame.

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

	// Run physics simulation
	_world.Step(Math.Min((float)gameTime.ElapsedGameTime.TotalSeconds, (1f / 30f)));

	base.Update(gameTime);
}

Finally, in order to see the result we draw the textures according to the position held by the Body objects. Since we are projecting the simulated Body positions to screen coordinates we need to multiply by our Scale factor.

The position of a Body is always set to the center of the object. This is why we draw our box using its center point as origin.

protected override void Draw(GameTime gameTime)
{
	GraphicsDevice.Clear(Color.CornflowerBlue);
	Vector2 origin = new Vector2(_box.Width / 2f, _box.Height / 2f);

	spriteBatch.Begin();
	foreach (var body in _bodies)
	{
		spriteBatch.Draw(_box, body.Position * Scale, null, Color.White, body.Rotation, origin, 1f,
							   SpriteEffects.None, 0f);
	}
	spriteBatch.End();

	base.Draw(gameTime);
}

If everything went according to plan you should now have this as a result:

This concludes Part 1.
I suggest you play around with some values (gravity, size of the boxes, …) and see what their effect is to get a feel of the simulated world we just created.

Advertisements

8 Responses to Part 1 – Hello Physics World

  1. Pingback: Develop an Angry Birds look-alike for windows phone 7 | Suddenelfilio.net

  2. shamtest says:

    I can’t seem to fully load this page from my iphone!!

  3. b16b says:

    i have a problem with box.png
    i take this error

    Error 1 Building content threw FileNotFoundException: Could not load file or assembly ‘System.Xml, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e’ or one of its dependencies. The system cannot find the file specified.
    at System.ModuleHandle.ResolveType(RuntimeModule module, Int32 typeToken, IntPtr* typeInstArgs, Int32 typeInstCount, IntPtr* methodInstArgs, Int32 methodInstCount, ObjectHandleOnStack type)
    at System.ModuleHandle.ResolveTypeHandleInternal(RuntimeModule module, Int32 typeToken, RuntimeTypeHandle[] typeInstantiationContext, RuntimeTypeHandle[] methodInstantiationContext)
    at System.Reflection.RuntimeModule.ResolveType(Int32 metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments)
    at System.Reflection.CustomAttribute.FilterCustomAttributeRecord(CustomAttributeRecord caRecord, MetadataImport scope, Assembly& lastAptcaOkAssembly, RuntimeModule decoratedModule, MetadataToken decoratedToken, RuntimeType attributeFilterType, Boolean mustBeInheritable, Object[] attributes, IList derivedAttributes, RuntimeType& attributeType, IRuntimeMethodInfo& ctor, Boolean& ctorHasParameters, Boolean& isVarArg)
    at System.Reflection.CustomAttribute.IsCustomAttributeDefined(RuntimeModule decoratedModule, Int32 decoratedMetadataToken, RuntimeType attributeFilterType, Boolean mustBeInheritable)
    at System.Reflection.CustomAttribute.IsDefined(RuntimeType type, RuntimeType caType, Boolean inherit)
    at System.RuntimeType.IsDefined(Type attributeType, Boolean inherit)
    at Microsoft.Xna.Framework.Content.Pipeline.TypeScanner.d__0.MoveNext()
    at Microsoft.Xna.Framework.Content.Pipeline.TypeHandlerFactory`1.Initialize()
    at Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate.IntermediateSerializer..ctor()
    at Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate.IntermediateSerializer.get_SingletonInstance()
    at Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate.IntermediateSerializer.Serialize[T](XmlWriter output, T value, String referenceRelocationPath)
    at Microsoft.Xna.Framework.Content.Pipeline.BuildItemCollection.Save(String filename)
    at Microsoft.Xna.Framework.Content.Pipeline.BuildCoordinator.Dispose()
    at Microsoft.Xna.Framework.Content.Pipeline.Tasks.BuildContent.RemoteProxy.RunTheBuild(BuildCoordinatorSettings settings, TimestampCache timestampCache, ITaskItem[] sourceAssets, String[]& outputContent, String[]& rebuiltContent, String[]& intermediates, Dictionary`2& dependencyTimestamps, KeyValuePair`2[]& warnings) c:\users\kostas\documents\visual studio 2010\Projects\PigsFly\PigsFly\PigsFlyContent\box.png PigsFly

  4. b16b says:

    i think need the wp7.1beta to work?

  5. Pingback: ??? ?? ????? ????? 29-?? ????! ?? ??????? «????????» ????! - MSDN Blogs

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: