Part 3b – supporting more prefab bodies

Last time we made our PrefabBodyFactory which allows us to easily create as many Body instances as we want.
However, we conveniently limited ourselves to 2 types of bodies which happened to be of the same size! We will rectify this in this post.

Remember that when we draw Bodies to screen, Farseer provides us with the position, based on the center point of the Body.
For a rectangle , the center point is

Vector2 origin = new Vector2(rectangle.width / 2f, rectangle.height / 2f);

In our current Draw method, we are taking advantage of the fact all of our Bodies have the same dimensions:

// pre-calculate the center point to use for all bodies, since we know they have the same dimensions.
Vector2 origin = new Vector2(_box.Width / 2f, _box.Height / 2f);

spriteBatch.Begin();

foreach (var body in _bodies)
{

	spriteBatch.Draw(
		_sheet.Texture,					// Texture
		body.Position * Scale,			// Position
		_sheet.SourceRectangle((string) body.UserData), // Source rectangle
		Color.White,					// Neutral
		body.Rotation,					// Rotation
		origin,							// Origin, relative to source rect size
		1f,								// Scale
		SpriteEffects.None,				// No effects
		0f);							// Z-layer
}

spriteBatch.End();

The code above breaks down once there is a item in _bodies which has different dimensions.

To solve this, we will store more information in the body.UserData:
– First of all we still need to store the sprite name
– Secondly, we want to store the center point

We will create a class to store these two properties:

public class PrefabUserData
{
	public string SpriteName { get; set; }
	public Vector2 Origin { get; set; }
}

Now we will adapt the PrefabBodyFactory to properly initialize the PrefabUserData:

private static Body CreateRectangle(World world, Vector2 position, string spriteName, float width, float height)
{
	Body body = BodyFactory.CreateRectangle(world, width, height, 1f, position);
	body.UserData = new PrefabUserData
			            {
			                Origin = new Vector2(width/2f, height/2f),
			                SpriteName = spriteName
			            };
	body.BodyType = BodyType.Dynamic;
	return body;
}

Similarly, we adapt our Draw method to make use of the PrefabUserData:

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

	spriteBatch.Begin();

	foreach (var body in _bodies)
	{
		var prefabUserData = (PrefabUserData) body.UserData;

		spriteBatch.Draw(
			_sheet.Texture,					// Texture
			body.Position * Scale,			// Position
			_sheet.SourceRectangle(prefabUserData.SpriteName), // Source rectangle
			Color.White,					// Neutral
			body.Rotation,					// Rotation
			prefabUserData.Origin * Scale,	// Origin, relative to source rect size
			1f,								// Scale
			SpriteEffects.None,				// No effects
			0f);							// Z-layer
	}

	spriteBatch.End();

	base.Draw(gameTime);
}

From now on you can add more rectangular PrefabTypes and they will all render properly.

Finally, we will add support for circular-shaped bodies. Now that our Draw method has been made generic, we only need to change our PrefabBodyFactory.

Add the following method to the factory class:

/// <summary>
/// Create a body with a circular shape
/// </summary>
/// <param name="world">World</param>
/// <param name="position">Position in sim-units</param>
/// <param name="spriteName">Sprite name to render with</param>
/// <param name="radius">radius of the body in sim-units</param>
/// <returns>new instance of a Body</returns>
private static Body CreateCircle(World world, Vector2 position, string spriteName, float radius)
{
	Body body = BodyFactory.CreateCircle(world, radius, 1f, position);
	body.UserData = new PrefabUserData
	{
		Origin = new Vector2(radius),
		SpriteName = spriteName
	};
	body.AngularDamping = 2f; //without this, our ball would keep on rolling regardless of friction
	body.BodyType = BodyType.Dynamic;
	return body;
}

The code above should be self-explanatory by now.
The only line worth extra mention is the AngularDamping one. For non-circular bodies, the physics world does a great job of taking friction into account. For circles however, we need to specify this property on top of regular friction, otherwise they keep on rolling forever.
Omit the line and see the effect for yourself.

The final step to add circles to our world is to create a Circle PrefabType:

static PrefabBodyFactory()
{
	Library.Add(PrefabType.GoldBox, (world, position) => CreateRectangle(world, position, "box1", 0.4f, 0.4f));
	Library.Add(PrefabType.GrayBox, (world, position) => CreateRectangle(world, position, "box2", 0.4f, 0.4f));
	Library.Add(PrefabType.Ball, (world, position) => CreateCircle(world, position, "ball", 0.3f)); // new Ball type
}

Here’s the artwork that goes with the new Ball. It should be clear by now the circle should have a radius of 30 pixels and the sprite should be added to the spritesheet xml asset.

Add the new Ball to your world like so:

Body ball = PrefabBodyFactory.CreateBody(PrefabType.Ball, _world, new Vector2(3.8f, -0.75f));
_bodies.Add(ball);

This should be the result:


This concludes the creation of the PrefabBodyFactory. From this point on you can add more sprites and shape types. Take a look at the other methods offered by Farseer’s BodyFactory. You can create ellipses, rounded rectangles and so many more to make your levels interesting.

Next time we will take a look at catapulting an pig into a pile of boxes. This will involve taking user input (touch) and applying a force to our bacon cannon ball.

Tweets? http://www.twitter.com/jodegreef

Advertisements

5 Responses to Part 3b – supporting more prefab bodies

  1. Pingback: Make your own Angry Birds for WP7 Part 3b – supporting more prefab bodies – www.nalli.net

  2. sander says:

    Great work….
    is it possible to have the source code (as zip to download) at the end of any session?
    best regards

  3. sander says:

    im have follow all steps but and i have not error during compilation .
    i have an error on draw method i got an NullReferenceException at spritebatch.draw methods

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

    // TODO: Add your drawing code here
    spriteBatch.Begin();

    foreach (var body in _bodies)
    {
    var prefabUserData = (PrefabUserData)body.UserData;

    spriteBatch.Draw(
    _sheet.Texture, // Texture
    body.Position * Scale, // Position
    _sheet.SourceRectangle(prefabUserData.SpriteName), // Source rectangle
    Color.White, // Neutral
    body.Rotation, // Rotation
    prefabUserData.Origin * Scale, // Origin, relative to source rect size
    1f, // Scale
    SpriteEffects.None, // No effects
    0f); // Z-layer
    }

    spriteBatch.Draw(
    _sheet.Texture,
    Constants.FloorPosition,
    _sheet.SourceRectangle(“floor”),
    Color.White
    );

    spriteBatch.End();

    base.Draw(gameTime);

    }

    • jodegreef says:

      If you provide me with the full project I’ll have a look at the bug, without the exact error message/line it happened on it’s hard to say

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: