Advertisement

Introduction to Box2D for Flash and AS3

Box2D is a popular physics engine with a solid Flash port, which was used to create the excellent game Fantastic Contraption. In this tutorial, the first of a series, you'll get to grips with the basics of Box2D 2.1a for Flash and AS3.


Step 1: The Boring Setup

I'm going to assume you already know how to set up a basic Flash project using your editor and workflow of choice, whether that means creating a FLA with a document class, a pure AS3 project in a different editor, or whatever. I'm using FlashDevelop, but you should use whatever you feel comfortable with.

Create your project, and name the main class Main.as. Give it some boilerplate code; mine looks like this:

package 
{
	import flash.display.Sprite;
	import flash.events.Event;

	[Frame(factoryClass="Preloader")]
	public class Main extends Sprite 
	{

		public function Main():void 
		{
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}

		private function init(e:Event = null):void 
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			getStarted();
		}
		
		private function getStarted():void
		{
			
		}

	}

}

Don't worry about the [Frame] metatag -- that's just how FlashDevelop creates a preloader. All you need to know is that getStarted() is run when the SWF has fully loaded. Create a function of the same name that runs when your Main class has loaded.

I'm also going to assume you're comfortable with using a separate library or API in your project. Download Box2DFlash 2.1a from this page (I'm using the Flash 9 version), and extract the zip to wherever you normally put your APIs. Next, add a classpath to point to the \Source\ folder from the zip. Alternatively, you could just extract the contents of the \Source\ folder to the same directory as your Main class.

Great! That was easy. Let's start using Box2D.


Step 2: A Whole New b2World

"If you want to make an apple pie from scratch, you must first create the universe," wrote Carl Sagan; if we want to make a few physics objects, we only need to create a world.

In Box2D, a world is not a planet or an ecosystem; it's just the name given for the object that manages the overall physics simulation. It also sets the strength of gravity -- not just how quickly objects accelerate but also in which direction they fall.

We'll create a vector to set the gravity before creating the world itself:

import Box2D.Common.Math.b2Vec2;

//...

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
}

A b2Vec2 is a Box2D Euclidean vector (the second 2 stands for 2D again, because it's possible to have a 3D Euclidean vector). Daniel Sidhion wrote an excellent tutorial explaining what Euclidean vectors are (they are nothing to do with the Vector class in AS3), so check that out if you're not sure. But briefly, think of a Euclidean vector as an arrow:

This arrow shows b2Vec2(4, 9); it points down and to the right. Our gravity vector, then, will point straight down. The longer the arrow, the stronger the gravity, so later on in the tutorial you could try changing the gravity vector to b2Vec(0, 2) to simulate a much weaker gravity, much like that on the Moon.

Now we'll create the world itself:

import Box2D.Dynamics.b2World;

//...

public var world:b2World;

//...

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
	world = new b2World(gravity, true);
}

The second parameter we pass to b2World() tells it that it can stop simulating objects that it doesn't need to simulate any more, which helps speed up the simulation but is completely irrelevant to us at the moment.

Run the SWF to check there are no errors. You won't be able to see anything yet, though. The world is currently completely empty!


Step 3: Reinvent the Wheel

Let's create a simple circular object; this could represent a rock, a basketball, or a potato, but I'm going to see it as a wheel.

I'd love to say that doing this is as simple as:

var wheel:b2CircularObject = new b2CircularObject();

...but that would be a big fat lie. The thing about Box2D is, apparently simple things can require quite a bit of code to get sorted. This extra complexity is very useful for doing advanced work, but it kinda gets in our way for simple things like what we're trying to do at the moment. I'm going to quickly go over what we need to do to make this wheel; we can examine it in more detail in later tutorials.

To create a single "physics object" -- that is, an object with a shape and a mass that Box2D can simulate within a world -- we must construct it using five different classes:

  • A body definition, which is like a template for creating...
  • A body, which has a mass and a position, but doesn't have...
  • A shape, which could be as simple as a circle, that must be connected to a body using...
  • A fixture, which is created using...
  • A fixture definition which is another template, like the body definition.

Phew. Here's what that all looks like in code:

import Box2D.Dynamics.b2BodyDef;
import Box2D.Dynamics.b2Body;
import Box2D.Collision.Shapes.b2CircleShape;
import Box2D.Dynamics.b2Fixture;
import Box2D.Dynamics.b2FixtureDef;

//...

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
	world = new b2World(gravity, true);
	
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(5);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
}

The argument that we pass to b2CircleShape() specifies the radius of the circle. Everything else should make sense based on the list above, even if the reasoning behind this structure makes no sense.

Note that we never write new b2Body() or new b2Fixture(); the world is used to create the body from the body definition, and the body is used to create the fixture from the fixture definition. This means that the world knows about all the bodies that are created within it, and the body knows about all the fixtures that are created that connect shapes to it.

The actual physics object that we've created is the wheelBody; everything else is just part of a recipe to form that one object. So, to check that we've been successful, let's see whether the wheelBody exists:

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
	world = new b2World(gravity, true);
	
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(5);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	trace(wheelBody);
}

Result:

[object b2Body]

Good.


Step 4: Move Your Body

Do you know what I mean when I talk about the "game loop" and a "tick"? If not, go read my short article, Understanding the Game Loop, right now, because it's really important for what we're doing.

The Box2D world object has a method, Step(), which drives the simulation. You specify a tiny period of time (a fraction of a second), and Step() simulates the movement and collisions of every object in the world; once the function has run, all the body objects will have been updated with their new positions.

Let's see this in action. Instead of tracing wheelBody itself, we'll trace its position. Then, we'll run b2World.Step(), and trace the wheel's position again:

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
	world = new b2World(gravity, true);
	
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(5);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	trace(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
	world.Step(0.025, 10, 10);
	trace(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
}

(The GetPosition() method of a body returns a b2Vec2, so we need to trace the individual x and y properties rather than just calling trace(wheelBody.GetPosition().)

The first parameter we pass to Step() is the number of seconds to simulate passing in the Box2D world. The other two parameters specify how much accuracy Box2D should use in all the mathematical calculations it uses to simulate the passing of time. Don't worry about these right now; just know that bigger numbers could make Box2D take more time to simulate the world -- if you set these too high, it might even take longer than 0.025 seconds to run Step(), so we'd be getting out of sync with the real world!

Test the SWF, and look at your output window:

0 0
0 0

That's a little depressing. The wheel hasn't moved at all -- and yet, the world has gravity, so you'd think it would have fallen a little bit. What's going on?


Step 5: Being Dynamic

By default, all Box2D bodies are static, meaning that they don't move. Think of the actual platforms in a platform game; they're solid objects, but aren't affected by gravity and can't be pushed around.

We need our wheel to be dynamic, so that it can move. Guess how we define this property of the body? Using the body definition, of course!

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
	world = new b2World(gravity, true);
	
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	wheelBodyDef.type = b2Body.b2_dynamicBody;
	var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(5);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	trace(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
	world.Step(0.025, 10, 10);
	trace(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
}

Try the SWF now, and see what you get:

0 0
0 0.00625

It's moved! How exciting!


Step 6: Keep on Moving

We can't call this a game loop yet, though, because it's only running once; we need it to run over and over and over again. We can make this happen using a Timer and an event listener:

import flash.utils.Timer;
import flash.events.TimerEvent;

//...

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
	world = new b2World(gravity, true);
	
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	wheelBodyDef.type = b2Body.b2_dynamicBody;
	var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(5);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	var stepTimer:Timer = new Timer(0.025 * 1000);
	stepTimer.addEventListener(TimerEvent.TIMER, onTick);
	trace(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
	stepTimer.start();
}

private function onTick(a_event:TimerEvent):void
{
	world.Step(0.025, 10, 10);
	trace(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
}

(Note that I've given the timer a period of 0.025 seconds as well, so that it stays in sync with the world's steps. You don't have to do this -- in fact, if you make the two periods different, you can get some really cool time-related effects, like slow-motion.)

But, oops, that code won't work; we need wheelBody to be accessible in the onTick() function. While we're at it, we might as well make the timer accessible everywhere, too:

public var world:b2World;
public var wheelBody:b2Body;
public var stepTimer:Timer;

//...

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
	world = new b2World(gravity, true);
	
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	wheelBodyDef.type = b2Body.b2_dynamicBody;
	wheelBody = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(5);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	stepTimer = new Timer(0.025 * 1000);
	stepTimer.addEventListener(TimerEvent.TIMER, onTick);
	trace(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
	stepTimer.start();
}

Note that lines 14 and 20, above, have changed, now that the wheel body and timer are defined elsewhere.

Try it now:

0 0
0 0.00625
0 0.018750000000000003
0 0.037500000000000006
0 0.0625
0 0.09375
0 0.13125
0 0.17500000000000002
0 0.22500000000000003
0 0.28125000000000006
0 0.34375000000000006
0 0.4125000000000001
0 0.4875000000000001
0 0.5687500000000001

Hooray, it's falling forever! Okay, that's enough analysing the numbers; let's make something visual.


Step 7: Draw!

We've got all these coordinates, so why not join the dots?

Replace the traces with the code to draw a line from the wheel's old position to its new one:

private function onTick(a_event:TimerEvent):void
{
	graphics.moveTo(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
	world.Step(0.025, 10, 10);
	graphics.lineTo(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
}

In order to see the line, we'll need to set its lineStyle:

private function getStarted():void
{

	//...
	
	stepTimer = new Timer(0.025 * 1000);
	stepTimer.addEventListener(TimerEvent.TIMER, onTick);
	graphics.lineStyle(3, 0xff0000);
	stepTimer.start();
}

Now run the SWF. You'll see a red line accelerating down the left edge. I've added some trickery to my SWF that means it won't do anything until you click it, so you can check it out below:

Great! So, the wheel is free-falling, and getting faster due to gravity, as it should be. We should leave its vertical speed alone so that gravity can do its work, but we can manually alter its initial horizontal speed to make the line's shape a little more interesting:

private function getStarted():void
{

	//...
	
	var startingVelocity:b2Vec2 = new b2Vec2(50, 0);
	wheelBody.SetLinearVelocity(startingVelocity);
	
	stepTimer = new Timer(0.025 * 1000);
	stepTimer.addEventListener(TimerEvent.TIMER, onTick);
	graphics.lineStyle(3, 0xff0000);
	stepTimer.start();
}

Remember that a b2Vec2 looks like an arrow, so, in this case, the starting velocity is going to look like an arrow pointing directly to the right, as if we had just shot it out of a cannon from the top of a cliff. Our world has no air resistance (because there's no air!) so there's absolutely nothing to slow it down -- though, thanks to gravity, it is speeding up vertically.

Sounds confusing, but it should be clear once you run the SWF:

We've just inadvertently drawn a parabola. So... that's neat, I guess?


Step 9: The Illusion of Movement

Okay, okay, maybe you're not as thrilled to see a curved red line as I am. If we're going to simulate a world, we should make the objects look like, well, objects, right? So instead of seeing a line that traces the wheel's position through space, we should make it look as if the wheel is actually moving through space.

We can do this by repeatedly drawing and erasing a circle, centered on the wheel's location:

private function onTick(a_event:TimerEvent):void
{
	graphics.clear();
	graphics.lineStyle(3, 0xff0000);
	world.Step(0.025, 10, 10);
	graphics.drawCircle(wheelBody.GetPosition().x, wheelBody.GetPosition().y, 5);
}

We have to specify the line style in onTick() now, because graphics.clear() resets it, as well as erasing all the graphics from the screen. Note that I've set the radius of the circle to 5, which is the same as the radius of the circleShape we created earlier.

Try this SWF:

I think it's pretty obvious what the next step should be...


Step 10: Grounded

This infinite world of nothingness doesn't lend itself to many exciting situations. Sure, we could add new wheels, but without any ground or walls, they'd never do anything interesting.

We'll create some solid objects that the wheel can collide with, starting with a big flat piece of ground. For this, we'll use a rectangle.

Remember the objects we need to create a body?

  • A body definition, which is like a template for creating...
  • A body, which has a mass and a position, but doesn't have...
  • A shape, which could be as simple as a circle, that must be connected to a body using...
  • A fixture, which is created using...
  • A fixture definition which is another template, like the body definition.

You know how to do this for a circular body; now we need to make a rectangular one:

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
	world = new b2World(gravity, true);
	
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	wheelBodyDef.type = b2Body.b2_dynamicBody;
	wheelBody = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(5);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	var groundBodyDef:b2BodyDef = new b2BodyDef();
	var groundBody:b2Body = world.CreateBody(groundBodyDef);
	var groundShape:b2PolygonShape = new b2PolygonShape();
	groundShape.SetAsBox(stage.stageWidth, 1);
	var groundFixtureDef:b2FixtureDef = new b2FixtureDef();
	groundFixtureDef.shape = groundShape;
	var groundFixture:b2Fixture = groundBody.CreateFixture(groundFixtureDef);
	
	var startingVelocity:b2Vec2 = new b2Vec2(50, 0);
	wheelBody.SetLinearVelocity(startingVelocity);
	
	stepTimer = new Timer(0.025 * 1000);
	stepTimer.addEventListener(TimerEvent.TIMER, onTick);
	graphics.lineStyle(3, 0xff0000);
	stepTimer.start();
}

It's mostly the same, except that, where in creating a circular body we passed the desired radius to the b2CircleShape() constructor, here we must pass the desired width and height of the rectangle to the b2PolygonShape.SetAsBox() function. (Actually, we pass the desired half-width and half-height, so the box will be twice as wide as the stage and two pixels tall, but that's fine.) Remember that all bodies are static by default, so we don't have to worry about the ground falling away. You'll need to import Box2D.Collision.Shapes.b2PolygonShape for this to work.

By default, any new shape will be created at (0, 0), so we need to specify that the ground should be created at the bottom of the stage. We could manually alter the position of the ground body once it's been created, just as we manually altered the velocity of the wheel body, but it's neater if we set the position as part of the ground's body definition:

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
	world = new b2World(gravity, true);
	
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	wheelBodyDef.type = b2Body.b2_dynamicBody;
	wheelBody = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(5);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	var groundBodyDef:b2BodyDef = new b2BodyDef();
	groundBodyDef.position.Set(0, stage.stageHeight);
	var groundBody:b2Body = world.CreateBody(groundBodyDef);
	var groundShape:b2PolygonShape = new b2PolygonShape();
	groundShape.SetAsBox(stage.stageWidth, 1);
	var groundFixtureDef:b2FixtureDef = new b2FixtureDef();
	groundFixtureDef.shape = groundShape;
	var groundFixture:b2Fixture = groundBody.CreateFixture(groundFixtureDef);
	
	var startingVelocity:b2Vec2 = new b2Vec2(50, 0);
	wheelBody.SetLinearVelocity(startingVelocity);
	
	stepTimer = new Timer(0.025 * 1000);
	stepTimer.addEventListener(TimerEvent.TIMER, onTick);
	graphics.lineStyle(3, 0xff0000);
	stepTimer.start();
}

Make sure you set this before passing the body definition to world.CreateBody(), or the changes will be ignored.

Now test the SWF:

We can't see the ground because we haven't drawn it, but Box2D still simulates it, so we see its effect: the wheel collides with it and rolls to the side.


Step 11: Set Some Boundaries

We're going to add more wheels in the next step, but first, let's make this a completely enclosed space, with a ground, a ceiling, and two walls.

I'd like you to have a go at this yourself; create three more rectangular bodies of the right sizes and in the right positions. It'll be easiest to start with the one on the right, since the wheel is going to collide with it. To test the others, fiddle about with the initial velocity.

You'll need to change the wheel's initial position, or it'll overlap the left wall and the ceiling when they're created. You can do this in the same way that you set the position of the rectangular objects, through the position.Set() method of the body definition.

Good luck! If you get stuck, check out my code below:

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
	world = new b2World(gravity, true);
	
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	wheelBodyDef.type = b2Body.b2_dynamicBody;
	wheelBodyDef.position.Set(10, 10);
	wheelBody = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(5);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	var groundBodyDef:b2BodyDef = new b2BodyDef();
	groundBodyDef.position.Set(0, stage.stageHeight);
	var groundBody:b2Body = world.CreateBody(groundBodyDef);
	var groundShape:b2PolygonShape = new b2PolygonShape();
	groundShape.SetAsBox(stage.stageWidth, 1);
	var groundFixtureDef:b2FixtureDef = new b2FixtureDef();
	groundFixtureDef.shape = groundShape;
	var groundFixture:b2Fixture = groundBody.CreateFixture(groundFixtureDef);
	
	var rightWallBodyDef:b2BodyDef = new b2BodyDef();
	rightWallBodyDef.position.Set(stage.stageWidth, 0);
	var rightWallBody:b2Body = world.CreateBody(rightWallBodyDef);
	var rightWallShape:b2PolygonShape = new b2PolygonShape();
	rightWallShape.SetAsBox(1, stage.stageHeight);
	var rightWallFixtureDef:b2FixtureDef = new b2FixtureDef();
	rightWallFixtureDef.shape = rightWallShape;
	var rightWallFixture:b2Fixture = rightWallBody.CreateFixture(rightWallFixtureDef);
	
	var leftWallBodyDef:b2BodyDef = new b2BodyDef();
	leftWallBodyDef.position.Set(0, 0);
	var leftWallBody:b2Body = world.CreateBody(leftWallBodyDef);
	var leftWallShape:b2PolygonShape = new b2PolygonShape();
	leftWallShape.SetAsBox(1, stage.stageHeight);
	var leftWallFixtureDef:b2FixtureDef = new b2FixtureDef();
	leftWallFixtureDef.shape = leftWallShape;
	var leftWallFixture:b2Fixture = leftWallBody.CreateFixture(leftWallFixtureDef);
	
	var ceilingBodyDef:b2BodyDef = new b2BodyDef();
	ceilingBodyDef.position.Set(0, 0);
	var ceilingBody:b2Body = world.CreateBody(ceilingBodyDef);
	var ceilingShape:b2PolygonShape = new b2PolygonShape();
	ceilingShape.SetAsBox(stage.stageWidth, 1);
	var ceilingFixtureDef:b2FixtureDef = new b2FixtureDef();
	ceilingFixtureDef.shape = ceilingShape;
	var ceilingFixture:b2Fixture = ceilingBody.CreateFixture(ceilingFixtureDef);
	
	var startingVelocity:b2Vec2 = new b2Vec2(50, 0);
	wheelBody.SetLinearVelocity(startingVelocity);
	
	stepTimer = new Timer(0.025 * 1000);
	stepTimer.addEventListener(TimerEvent.TIMER, onTick);
	graphics.lineStyle(3, 0xff0000);
	stepTimer.start();
}

And the result:

Nice work.


Step 12: Create an Array

Now we're ready to add more bodies, slam them together, and see what happens.

We could create them all invididually, like I did with the rectangles, but it'd be neater to use an array to hold them all, and a single function to create them.

So, first, let's create that array, with a single element: the existing wheel.

public var world:b2World;
//public var wheelBody:b2Body; <-- delete this line
public var wheelArray:Array;
public var stepTimer:Timer;

//...

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
	world = new b2World(gravity, true);
	
	wheelArray = new Array();
	
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	wheelBodyDef.type = b2Body.b2_dynamicBody;
	wheelBodyDef.position.Set(10, 10);
	var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(5);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	wheelArray.push(wheelBody);
	
	//...
	
	var startingVelocity:b2Vec2 = new b2Vec2(50, 0);
	wheelBody.SetLinearVelocity(startingVelocity);
	
	stepTimer = new Timer(0.025 * 1000);
	stepTimer.addEventListener(TimerEvent.TIMER, onTick);
	graphics.lineStyle(3, 0xff0000);
	stepTimer.start();
}

private function onTick(a_event:TimerEvent):void
{
	graphics.clear();
	graphics.lineStyle(3, 0xff0000);
	world.Step(0.025, 10, 10);
	
	for each (var wheelBody:b2Body in wheelArray)
	{
		graphics.drawCircle(wheelBody.GetPosition().x, wheelBody.GetPosition().y, 5);
	}
}

If this works, your SWF will act exactly the same as it did before.


Step 13: Add One More Wheel

To test this out properly, we can add an extra wheel -- just one for now! Copy and paste the code for creating the wheel and change parts of it so the wheels aren't exactly the same:

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
	world = new b2World(gravity, true);
	
	wheelArray = new Array();
	
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	wheelBodyDef.type = b2Body.b2_dynamicBody;
	wheelBodyDef.position.Set(10, 10);
	var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(5);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	wheelArray.push(wheelBody);
	
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	wheelBodyDef.type = b2Body.b2_dynamicBody;
	wheelBodyDef.position.Set(100, 200);
	var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(5);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	wheelArray.push(wheelBody);
	
	//... create boundaries here
	
	var startingVelocity:b2Vec2 = new b2Vec2(50, 0);
	wheelBody.SetLinearVelocity(startingVelocity);
	
	stepTimer = new Timer(0.025 * 1000);
	stepTimer.addEventListener(TimerEvent.TIMER, onTick);
	graphics.lineStyle(3, 0xff0000);
	stepTimer.start();
}

You'll get a bunch of warnings about duplicate variable definitions, but it should still compile, depending on your settings:

The first wheel drops straight down, without an initial sideways velocity. This makes perfect sense when you look at the code; we're calling wheelBody.SetLinearVelocity() in the wrong place. Move it up:

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
	world = new b2World(gravity, true);
	
	wheelArray = new Array();
	
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	wheelBodyDef.type = b2Body.b2_dynamicBody;
	wheelBodyDef.position.Set(10, 10);
	var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(5);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	var startingVelocity:b2Vec2 = new b2Vec2(50, 0);
	wheelBody.SetLinearVelocity(startingVelocity);
	
	wheelArray.push(wheelBody);
	
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	wheelBodyDef.type = b2Body.b2_dynamicBody;
	wheelBodyDef.position.Set(100, 200);
	var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(5);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	var startingVelocity:b2Vec2 = new b2Vec2(-100, 0);
	wheelBody.SetLinearVelocity(startingVelocity);
	
	wheelArray.push(wheelBody);
	
	//... create boundaries here
	
	stepTimer = new Timer(0.025 * 1000);
	stepTimer.addEventListener(TimerEvent.TIMER, onTick);
	graphics.lineStyle(3, 0xff0000);
	stepTimer.start();
}

Okay! That's working well. We can wrap all that wheel creation code up in a function, now.


Step 14: Wheel Generator

To create this wheel generation function, you can basically copy and paste all the code we've been using. Here's mine:

private function createWheel(radius:Number, startX:Number, startY:Number, velocityX:Number, velocityY:Number):void
{
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	wheelBodyDef.type = b2Body.b2_dynamicBody;
	wheelBodyDef.position.Set(startX, startY);
	var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(radius);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	var startingVelocity:b2Vec2 = new b2Vec2(velocityX, velocityY);
	wheelBody.SetLinearVelocity(startingVelocity);
	
	wheelArray.push(wheelBody);
}

I've added some parameters so that we can specify the customisable properties of the wheels. While we're at it, I suggest we move all of the boundary creation code to a separate function, just to keep things tidy:

private function createBoundaries():void
{
	//insert your boundary code here
}

Now we can rewrite the entire getStarted() function like so:

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
	world = new b2World(gravity, true);
	wheelArray = new Array();
	
	createWheel(5, 10, 10, 50, 0);
	createWheel(5, 100, 200, -25, 0);
	
	createBoundaries();
	
	stepTimer = new Timer(0.025 * 1000);
	stepTimer.addEventListener(TimerEvent.TIMER, onTick);
	graphics.lineStyle(3, 0xff0000);
	stepTimer.start();
}

Try it out! Again, the SWF should act the same as it did before.


Step 15: Automatic Wheels

Now that we've simplified the wheel generation, we can make lots of them without much difficulty or mess:

private function getStarted():void
{
	var gravity:b2Vec2 = new b2Vec2(0, 10);
	world = new b2World(gravity, true);
	wheelArray = new Array();
	
	for (var i:int = 0; i < 20; i++)
	{
		createWheel(
			Math.random() * 10,
			Math.random() * (stage.stageWidth - 20) + 10,
			Math.random() * (stage.stageHeight - 20) + 10,
			(Math.random() * 100) - 50,
			0
		);
	}
	
	createBoundaries();
	
	stepTimer = new Timer(0.025 * 1000);
	stepTimer.addEventListener(TimerEvent.TIMER, onTick);
	graphics.lineStyle(3, 0xff0000);
	stepTimer.start();
}

This will create twenty wheels, each with a radius of between 0 and 10, a position of anywhere between (10, 10) and (stageWidth-10, stageHeight-10), and a horizontal speed of anywhere between -50 and +50. Try it out!

It almost works, but something's wrong. Let's figure out what's going on.


Step 16: Spoiler -- It's the Radius

Check out this image from when I ran the SWF:

Some of the wheels are sunk into the ground or wall, and some are overlapping each other. It's really weird. And on a related note, didn't we set the radii of the wheels to be anywhere between 0 and 10? Why are they all the same?

As you've probably figured out, they're the same because we hard coded the radius of the circle to draw in the onTick() event handler function. Oops.

Unfortunately we can't just use wheelBody.radius to find the radius of the wheel; remember that the body doesn't have a shape, but rather is connected to a shape through a fixture. To find the radius, then, we have to do something like this:

var wheelFixture:b2Fixture = wheelBody.GetFixtureList();
var wheelShape:b2CircleShape = wheelFixture.GetShape() as b2CircleShape;
var radius:Number = wheelShape.GetRadius();

Fortunately we can simplify this to (wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius(); let's use this in onTick():

private function onTick(a_event:TimerEvent):void
{
	graphics.clear();
	graphics.lineStyle(3, 0xff0000);
	world.Step(0.025, 10, 10);

	for each (var wheelBody:b2Body in wheelArray)
	{
		graphics.drawCircle(
			wheelBody.GetPosition().x, 
			wheelBody.GetPosition().y, 
			(wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius()
		);
		
	}
}

How's that?

Ah, much better!


Step 17: Have a Little Fun

The simulation is a little dull at the moment; the wheels just roll a little bit and then stop. Boring. Let's liven things up a bit by changing them from metal wheels to air-filled rubber tyres.

To do this, we can use a property called the coefficient of restitution. This is physics-talk for "bounciness"; the coefficient of restitution of an object shows how much it will bounce back after colliding with another object. Typically, this takes a value between 0 and 1, where 0 means it stops dead as soon as it touches another object, and 1 means it bounces back without losing any energy.

By default, all Box2D bodies have a coefficient of restitution of 0; let's liven things up by setting it a bit higher:

private function createWheel(radius:Number, startX:Number, startY:Number, velocityX:Number, velocityY:Number):void
{
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	wheelBodyDef.type = b2Body.b2_dynamicBody;
	wheelBodyDef.position.Set(startX, startY);
	var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(radius);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	wheelFixtureDef.restitution = (Math.random() * 0.5) + 0.5;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	var startingVelocity:b2Vec2 = new b2Vec2(velocityX, velocityY);
	wheelBody.SetLinearVelocity(startingVelocity);
	
	wheelArray.push(wheelBody);
}

This will set the coefficient of restitution of each wheel to a random value between 0.5 and 1. Note that this is a property of the fixture definition, rather than the shape or the body definition.

Let's see what happens:

That's more like it! What else can we set?


Step 18: Other Properties

We can also set the friction (how rough or smooth the outer surface is; a polished steel wheel will have less friction than a rough stone one) and the density (how heavy the body would be compared to another of the same size and shape; stone is denser than wood).

The friction coefficient should be between 0 and 1, where 0 means extremely smooth and 1 means extremely rough, and is 0.2 by default. The density can be any value you like, and is 1 by default. (Well, for static bodies, it's 0, but it doesn't really matter to them as they don't move anyway.)

Let's mess around with these values and see what happens:

private function createWheel(radius:Number, startX:Number, startY:Number, velocityX:Number, velocityY:Number):void
{
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	wheelBodyDef.type = b2Body.b2_dynamicBody;
	wheelBodyDef.position.Set(startX, startY);
	var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(radius);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	wheelFixtureDef.restitution = (Math.random() * 0.5) + 0.5;
	wheelFixtureDef.friction = (Math.random() * 1.0);
	wheelFixtureDef.density = Math.random() * 20;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	var startingVelocity:b2Vec2 = new b2Vec2(velocityX, velocityY);
	wheelBody.SetLinearVelocity(startingVelocity);
	
	wheelArray.push(wheelBody);
}

The result:

All good. Feel free to add parameters for restitution, friction, and density to the createWheel() function if you want a bit more control.

There's something that's bugging me, though...


Step 19: Heavy, Man

...Why does everything feel so floaty?

Seriously, it's like the whole world is moving through treacle. But there's no air resistance, let alone molasses resistance; gravity is at a sensible level; and the time step is matched to the timer's tick duration -- what's the issue?

If you're a scientist, you've probably noticed that I haven't mentioned any units in the whole tutorial. The first wheel's radius was just "5" -- not "5 inches" or whatever. We've just been working in pixels. But Box2D is a physics engine, and so it uses actual physical units. That wheel had a radius of five meters, not five pixels; that's roughly sixteen feet, not far off the height of an average house. Most wheels aren't that height, although there are exceptions, as this Flickr photo from Monochrome shows:

Even then, the tires in that photo have a radius of about 2.5 meters; we'd normally expect a wheel's radius to be somewhere between a few centimetres and half a meter. This means that every Box2D object we've created is much bigger than it appears on the screen, and -- as anyone who's watched Honey I Shrunk The Kids knows -- bigger objects move in slow motion.

So, we can get a much less floaty simulation by changing the radius of the wheels that we create:

for (var i:int = 0; i < 20; i++)
{
	createWheel(
		Math.random() * 0.5,
		Math.random() * (stage.stageWidth - 20) + 10,
		Math.random() * (stage.stageHeight - 20) + 10,
		(Math.random() * 100) - 50,
		0
	);
}

The resulting SWF feels a lot more realistic, but at the same time, the wheels are all drawn so tiny that it feels like we're watching from half a mile away:

There's a common trick that Box2D developers use here...


Step 20: Set the Scale Factor

To recap: Box2D uses meters, but Flash uses pixels. Therefore, a wheel of 0.5 meters in radius is drawn as a tiny circle, just over a pixel wide.

When you read it like that, perhaps the solution seems obvious: we need to scale up the conversion between meters and pixels, so that one meter is drawn as, let's say, 20 pixels.

Modify the graphics.drawCircle() call in onTick() to reflect this new scale factor:

private function onTick(a_event:TimerEvent):void
{
	graphics.clear();
	graphics.lineStyle(3, 0xff0000);
	world.Step(0.025, 10, 10);

	for each (var wheelBody:b2Body in wheelArray)
	{
		graphics.drawCircle(
			wheelBody.GetPosition().x,
			wheelBody.GetPosition().y,
			(wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius() * 20
		);
		
	}
}

Does that work?

Um, no. We're back to floatiness, plus the wheels aren't colliding with each other any more.

Ah, but, this is misleading. The graphical representations of the wheels appear to be overlapping, but actually, they're not. See, we can't just use this scale factor for the radii of the wheels and leave it at that; we have to use it for all lengths and distances -- and that includes the x- and y-positions of the wheels. So, try this:

private function onTick(a_event:TimerEvent):void
{
	graphics.clear();
	graphics.lineStyle(3, 0xff0000);
	world.Step(0.025, 10, 10);
	
	for each (var wheelBody:b2Body in wheelArray)
	{
		graphics.drawCircle(
			wheelBody.GetPosition().x * 20,
			wheelBody.GetPosition().y * 20,
			(wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius() * 20
		);
		
	}
}

Actually, hold on -- let's use a public variable instead of hard coding the number 20:

public var scaleFactor:Number = 20;		//pixels per meter

//...

private function onTick(a_event:TimerEvent):void
{
	graphics.clear();
	graphics.lineStyle(3, 0xff0000);
	world.Step(0.025, 10, 10);
	
	for each (var wheelBody:b2Body in wheelArray)
	{
		graphics.drawCircle(
			wheelBody.GetPosition().x * scaleFactor,
			wheelBody.GetPosition().y * scaleFactor,
			(wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius() * scaleFactor
		);
		
	}
}

Try the SWF now, and... hmm. Just a blank white canvas. Let's see what's going on under the hood:

for each (var wheelBody:b2Body in wheelArray)
{
	trace(wheelBody.GetPosition().x * 20, wheelBody.GetPosition().y * 20);
	graphics.drawCircle(
		wheelBody.GetPosition().x * scaleFactor,
		wheelBody.GetPosition().y * scaleFactor,
		(wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius() * scaleFactor
	);
	
}

Result:

2113.750333013013 5770.15641567111
9375.355269713327 3047.2614786848426
1777.8544335393235 7079.051908224821
5187.372552766465 3558.7625446990132
918.4206030098721 1295.5237261280417
6346.797422436066 4702.557933263481
5711.968449363485 6922.446605682373
3969.2852629581466 493.93064894527197
288.0119406618178 2824.054687216878
7364.996945206076 7429.401991263032
6689.968886575662 6163.817259043455
3496.4873051736504 3063.40289388597
7230.636887997389 1294.561620734632
5069.327887007967 7544.009161673486
7484.348242566921 3492.7868475466967
3882.391231972724 6486.211738668382
1641.0604398231953 2738.4121247828007
9631.735268328339 3224.4683633819222

Whoa! Those numbers are wayyy too big. But that makes sense -- they're 20 times bigger than they used to be.

Think about when we create the wheels -- and specifically, when we set their initial positions:

createWheel(
	Math.random() * 0.5,
	Math.random() * (stage.stageWidth - 20) + 10,
	Math.random() * (stage.stageHeight - 20) + 10,
	(Math.random() * 100) - 50,
	0
);

My stage is 500x400px. When I wasn't using a scale factor, a new wheel's x-position could be anywhere from 10px to 490px from the left of the stage. This was the same as being anywhere from 10 meters to 490 meters from the left of the stage, because each meter was represented as a single pixel long. But now, each meter is represented by twenty pixels -- so a new wheel's x-position could be anywhere from 200px to 9800px from the left of the stage! No wonder we're not seeing any of them.

To fix this, we'll use the scale factor in the code that creates a new wheel:

private function createWheel(radius:Number, startX:Number, startY:Number, velocityX:Number, velocityY:Number):void
{
	var wheelBodyDef:b2BodyDef = new b2BodyDef();
	wheelBodyDef.type = b2Body.b2_dynamicBody;
	wheelBodyDef.position.Set(startX / scaleFactor, startY / scaleFactor);
	var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
	var circleShape:b2CircleShape = new b2CircleShape(radius);
	var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
	wheelFixtureDef.shape = circleShape;
	wheelFixtureDef.restitution = (Math.random() * 0.5) + 0.5;
	wheelFixtureDef.friction = (Math.random() * 1.0);
	wheelFixtureDef.density = Math.random() * 20;
	var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
	
	var startingVelocity:b2Vec2 = new b2Vec2(velocityX, velocityY);
	wheelBody.SetLinearVelocity(startingVelocity);
	
	wheelArray.push(wheelBody);
}

Does it make sense to you that we're dividing it, rather than multiplying it? Remember, Box2D uses meters, Flash uses pixels, and our scale factor is in pixels/meter.

Try the SWF now:

Much better -- but the right wall and ground appear to have disappeared.

...Have you already guessed what's happening?

It's because, again, the distances are specified in meters, and we haven't applied the scale factor. So rather than the right wall being 500px from the left edge, it's 500 meters, or 10,000px. Again, we need to divide our distances by the scale factor to take this into account.

If you wrote this code yourself, I'm sure you can figure out how to alter it yourself :) Otherwise, here's mine:

private function createBoundaries():void
{
	var groundBodyDef:b2BodyDef = new b2BodyDef();
	groundBodyDef.position.Set(0, stage.stageHeight / scaleFactor);
	var groundBody:b2Body = world.CreateBody(groundBodyDef);
	var groundShape:b2PolygonShape = new b2PolygonShape();
	groundShape.SetAsBox(stage.stageWidth / scaleFactor, 1 / scaleFactor);
	var groundFixtureDef:b2FixtureDef = new b2FixtureDef();
	groundFixtureDef.shape = groundShape;
	var groundFixture:b2Fixture = groundBody.CreateFixture(groundFixtureDef);
	
	var rightWallBodyDef:b2BodyDef = new b2BodyDef();
	rightWallBodyDef.position.Set(stage.stageWidth / scaleFactor, 0);
	var rightWallBody:b2Body = world.CreateBody(rightWallBodyDef);
	var rightWallShape:b2PolygonShape = new b2PolygonShape();
	rightWallShape.SetAsBox(1 / scaleFactor, stage.stageHeight / scaleFactor);
	var rightWallFixtureDef:b2FixtureDef = new b2FixtureDef();
	rightWallFixtureDef.shape = rightWallShape;
	var rightWallFixture:b2Fixture = rightWallBody.CreateFixture(rightWallFixtureDef);
	
	var leftWallBodyDef:b2BodyDef = new b2BodyDef();
	leftWallBodyDef.position.Set(0, 0);
	var leftWallBody:b2Body = world.CreateBody(leftWallBodyDef);
	var leftWallShape:b2PolygonShape = new b2PolygonShape();
	leftWallShape.SetAsBox(1 / scaleFactor, stage.stageHeight / scaleFactor);
	var leftWallFixtureDef:b2FixtureDef = new b2FixtureDef();
	leftWallFixtureDef.shape = leftWallShape;
	var leftWallFixture:b2Fixture = leftWallBody.CreateFixture(leftWallFixtureDef);
	
	var ceilingBodyDef:b2BodyDef = new b2BodyDef();
	ceilingBodyDef.position.Set(0, 0);
	var ceilingBody:b2Body = world.CreateBody(ceilingBodyDef);
	var ceilingShape:b2PolygonShape = new b2PolygonShape();
	ceilingShape.SetAsBox(stage.stageWidth / scaleFactor, 1 / scaleFactor);
	var ceilingFixtureDef:b2FixtureDef = new b2FixtureDef();
	ceilingFixtureDef.shape = ceilingShape;
	var ceilingFixture:b2Fixture = ceilingBody.CreateFixture(ceilingFixtureDef);
}

Try it now:

Excellent! And since the scale factor is soft-coded, you could try increasing it:


Conclusion

Congratulations! If you've followed this far, then you understand the very important basic concepts of Box2D. (If you didn't follow this far, then post a comment explaining where you got stuck, and I'll help you out.) Okay, the final SWF might not look like much, but don't be fooled; you've given yourself an excellent framework for what you'll learn next.

Speaking of what's next... what do you want to learn? I've got plenty of ideas, and I'm happy to continue this series in whatever direction I'd like to explore, but it'd be cool to know what you think. Do you want to make a physics puzzler? A platformer? A side-scrolling racing game? Are there any core concepts you'd like to focus on, like creating joints?

The next tutorial in this series will most likely be about rendering objects using other methods than the built-in drawing methods -- in particular we'll look at using DisplayObjects, either drawn through Flash or imported as Bitmaps. We'll also create some actual boxes, because it's pretty weird to have done an entire Box2D tutorial without any.




You can vote on tutorial ideas through our Google Moderator page at http://bit.ly/Box2DFlashTutorialIdeas, or submit a brand new one, if nobody's thought of it yet. Looking forward to seeing what you suggest!