Beginning Programming in Box2DFlash 2.1a in AS3
A new version of Box2DFlash has been out for a while now, with some pretty major changes that mean your old projects won’t compile without a mass of errors. This tutorial aims to create a similar demo to my previous Box2DFlash tutorial, which was a beginners step into Box2DFlash 2.0.2.
The biggest difference you will notice to begin with is the introduction of a new variable type that sits between the shape and body: the fixtureDef. This variable now holds the material properties such as friction, density and restitution that were previously in the shape definition variable.
Setting up your project to include the Box2D classes is the same process as in the last tutorial, although with the new source files, so I will gloss over that and assume you have set it all up yourself and are ready to code in your main document class.
The demo I have created will simply create a world and a ground body to begin with. Boxes will be added every few ticks until a maximum number of bodies in the world has been reached. Debug draw will be used in order to get quicker visual results rather than having to draw all sprites myself.
By the end of this tutorial, you should have something similar to this:
So without further ado, lets get started. Open up your chosen flash programming tool (I favour the features of FlashDevelop personally, and still compile into Adobe Flash CS4 to get the best of both worlds, but you are free to just use FlashDevelop with Flex or the Adobe Flash IDE if you wish)
Create a new AS3 project, and add a document class which will contain all of your code. Make sure to add the Box2DFlash source as a classpath in order to utilise the variable types before you begin.
Our demo is going to be split up into a few simple steps:
- Create the world function
- Create the ground function
- Create the box adding function
- Set up debug draw
- Add an updating event listener
Before we start the world setup function, we are going to declare some global variables and constants that we will use throughout the functions:
1 2 3 4 5 | private var world:b2World; //our world object private var debugSprite:Sprite; //our sprite that will hold all of the debug draw data private var debugDraw:b2DebugDraw; //the actual debugDraw object public static const PTM:Number = 30; //our pixels to metre ration that will be used throughout the demo private var nextBlock:Number = 10; //the update rate for the block adding function we will create later |
Now we are ready to create our world, in our setupWorld() function:
1 2 3 4 5 6 | private function setupWorld():void { world = new b2World(new b2Vec2(0, 9.81), true); setupDebugDraw(); world.SetDebugDraw(debugDraw); } |
All of this should be relatively recognisable if you followed the similar tutorial in the older version of box2d. The first line declares the new world object, and passes in its gravity vector, which is a b2Vec2 variable, and a boolean which controls whether none-moving objects will be put to sleep. You should set this to true for performance purposes.
Now seems like a good time to set up the debug draw function, as it is called in the setupWorld() function, so we will go ahead and do that now:
1 2 3 4 5 6 7 8 9 10 11 | private function setupDebugDraw():void { debugSprite = new Sprite(); stage.addChild(debugSprite); debugDraw = new b2DebugDraw(); debugDraw.SetDrawScale(PTM); debugDraw.SetFillAlpha(0.4); debugDraw.SetFlags(b2DebugDraw.e_shapeBit); debugDraw.SetLineThickness(1.0); debugDraw.SetSprite(debugSprite); } |
Here we are setting up the stage to show the debug data from Box2DFlash. For that we will need a sprite, debugSprite, and it will need to be added to the stage. Don’t forget this step, as without it your stage will look completely blank but with no errors, and you may wonder why the hell nothing is showing on the screen (speaking from personal experience)
Once the sprite is added to the stage we can set up our b2DebugDraw object: debugDraw.
Firstly, we set the scale at which it draws all objects (remember that Box2D reads all positional values as metres rather than pixels, so this set to the same pixels to metres ratio we set up as a constant in our document class)
Secondly, we set the transparency of the filled shapes. Remember 0.0 is completely transparent, 1 is completely opaque. I’ve gone for a relatively middle ground option, which is usually best.
Next we set what type of objects will actually be drawn. Here we want the shapes drawn, so we use the flag b2DebugDraw.e_shapeBit.
Afterwards we set the line thickness, which is pretty self explanatory, and then finally we attach our debug sprite to the debugDraw object. Then we’re done! Much quicker than drawing all our objects ourselves.
Our next step is to create our ground shape, which is done in our addGround() function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | private function addGround():void { var groundBodyDef:b2BodyDef; var groundBody:b2Body; var groundFixture:b2FixtureDef; var groundShape:b2PolygonShape; groundShape = new b2PolygonShape(); groundShape.SetAsBox(stage.stageWidth / 2 / PTM, 15 / PTM); groundBodyDef = new b2BodyDef(); groundBodyDef.position.Set(stage.stageWidth / 2 / PTM, 380 / PTM); groundBodyDef.type = b2Body.b2_staticBody; groundFixture = new b2FixtureDef() groundFixture.shape = groundShape; groundFixture.friction = 0.8; groundFixture.density = 1.0; groundFixture.restitution = 0.3; groundBody = world.CreateBody(groundBodyDef); groundBody.CreateFixture(groundFixture); } |
Here we need 4 variables to store all of our data we need. These are:
- Our body definition (b2BodyDef)
- Our body (b2Body)
- Our fixture definition (b2FixtureDef)
- Our shape (b2PolygonShape)
First of all we’ll tackle the shape. We use the SetAsBox method to tell Box2D that we are using a simple, 4 sided box. The properties of this method are half the width and half the height. Don’t forget to divide by your pixel to metres constant. Here the box is going to be the full length of the stage, so half the width is stage.stageWidth / 2 / PTM, and the height will be about 30 pixels, so the half height is 15 / PTM.
Now that the shape has been created, we can move on to our body definition. This is used to set the position and type of the body. The position is passed using position.Set(x, y) method of the b2BodyDef. Our ground will be in the middle of the screen, at the bottom, so our passed in values are (stage.stageWidth / 2 / PTM, 380 / PTM)
New in this version of Box2DFlash is the type method, which must be used to tell the world what type of body this is going to be. In this case, our body is a static body, as we never want it to move. Previously this was set using the density value, but this is no longer the case. Later on we will be making our boxes, which must move, hence they will be dynamic bodies.
After our body definition is complete, we can move on to our fixture definition. Here we are setting the material properties of the body: the friction, density and restitution. We are also linking the fixture definition and the shape object we created earlier.
Once that is done we can move onto the main object: the body. To create the body, we must call the world.CreateBody method, and pass in the body definition we set up earlier. We then link the body and the fixture definition using the CreateFixture method, and we’re done. Your world now has a fully functioning ground body.
Next I think we should tackle our update function, which will be called once every tick from the flash movie. The speed of which will be controlled by the fps setting in our movie, which I have set to 30.
In the class main function, underneath all other calls to the functions we have created, we create an event listener:
1 | addEventListener(Event.ENTER_FRAME, Update); |
Our Update() then looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | private function Update(e:Event):void { world.Step(1 / 30, 10, 10); world.ClearForces(); world.DrawDebugData(); if (nextBlock -- <= 0 && world.GetBodyCount() < maxBodies) { addBlock(Math.random() * stage.stageWidth, -10); nextBlock = 10; } } |
First, we set the updating time for the world. The first property is the time step, which should be 1/your fps, which is why mine is 1/30. The second property is the velocity iterations and then the position iterations third. Try to leave these at around 10 on normal projects as extreme values will cause your world to function strangely, though feel free to experiment!
Next we call the ClearForces() method of the world. This is new in Box2DFlash 2.1a, which must be called after each step now. We then tell the world to draw all of the debug data each step too. No b2DebugDraw object is passed into the method as we already handled that when we set up our world.
Next we have our if statement, which is used to add a new box every few ticks. It works by decrementing the nextBlock variable until it is less than or equal to zero, and also as long as the total number of bodies in the world is less than our maximum value we set in our constructor.
Inside the if statement, we call our addBlock() function we will create next and reset the counter back to its default value. In the call to addBlock() we pass in two variables: the x position and y position. The x position will be a random number between 0 and the stage width, and the y value will always be -10, just above the stage. Don’t worry about the pixel to metres ratio, as this will be calculated inside the function itself, which we will tackle now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | private function addBlock(posX:Number, posY:Number):void { var blockBodyDef:b2BodyDef = new b2BodyDef(); blockBodyDef.position.Set(posX / PTM, posY / PTM); blockBodyDef.type = b2Body.b2_dynamicBody; blockBodyDef.angle = (Math.random() * 360) * Math.PI / 180; var blockShape:b2PolygonShape = new b2PolygonShape(); blockShape.SetAsBox(20 / PTM, 20 / PTM); var blockFixture:b2FixtureDef = new b2FixtureDef(); blockFixture.shape = blockShape; blockFixture.density = 1.0; blockFixture.friction = 0.4; blockFixture.restitution = 0.3; var blockBody:b2Body = world.CreateBody(blockBodyDef); blockBody.CreateFixture(blockFixture); trace("block added at " + posX + " " + posY); } |
I won’t bother explaining all of this function as its core is essentially the same as our addGround() function we tackled earlier. However, there are a couple of differences to note: firstly, the position is set using the posX and posY values that are passed into the function (don’t forget to divide by your ratio again!) and the angle of the body definition is randomly set from a number between 0 and 2pi (0 and 360 degrees – don’t forget, angles are calculated using radians instead of degrees)
The only other difference is that type, which instead of being static is now dynamic. This means the world object will apply world physics to this object, so it will move. Very important not to forget this one!
Now you’re done! Here is the complete code listing for reference:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | package { import Box2D.Collision.Shapes.b2PolygonShape; import Box2D.Common.Math.b2Vec2; import Box2D.Dynamics.b2Body; import Box2D.Dynamics.b2BodyDef; import Box2D.Dynamics.b2DebugDraw; import Box2D.Dynamics.b2FixtureDef; import Box2D.Dynamics.b2World; import flash.display.Sprite; import flash.events.Event; /** * ... * @author Matthew Hurst */ public class Main extends Sprite { private var world:b2World; private var debugSprite:Sprite; private var debugDraw:b2DebugDraw; public static const PTM:Number = 30; private var nextBlock:Number = 10; public static const maxBodies:Number = 50; public function Main() { setupWorld(); addGround(); addEventListener(Event.ENTER_FRAME, Update); } private function addGround():void { var groundBodyDef:b2BodyDef; var groundBody:b2Body; var groundFixture:b2FixtureDef; var groundShape:b2PolygonShape; groundShape = new b2PolygonShape(); groundShape.SetAsBox(stage.stageWidth / 2 / PTM, 15 / PTM); groundBodyDef = new b2BodyDef(); groundBodyDef.position.Set(stage.stageWidth / 2 / PTM, 380 / PTM); groundBodyDef.type = b2Body.b2_staticBody; groundFixture = new b2FixtureDef() groundFixture.shape = groundShape; groundFixture.friction = 0.8; groundFixture.density = 1.0; groundFixture.restitution = 0.3; groundBody = world.CreateBody(groundBodyDef); groundBody.CreateFixture(groundFixture); } private function Update(e:Event):void { world.Step(1 / 30, 10, 10); world.ClearForces(); world.DrawDebugData(); if (nextBlock -- <= 0 && world.GetBodyCount() < maxBodies) { addBlock(Math.random() * stage.stageWidth, -10); nextBlock = 10; } } private function addBlock(posX:Number, posY:Number):void { var blockBodyDef:b2BodyDef = new b2BodyDef(); blockBodyDef.position.Set(posX / PTM, posY / PTM); blockBodyDef.type = b2Body.b2_dynamicBody; blockBodyDef.angle = (Math.random() * 360) * Math.PI / 180; var blockShape:b2PolygonShape = new b2PolygonShape(); blockShape.SetAsBox(20 / PTM, 20 / PTM); var blockFixture:b2FixtureDef = new b2FixtureDef(); blockFixture.shape = blockShape; blockFixture.density = 1.0; blockFixture.friction = 0.4; blockFixture.restitution = 0.3; var blockBody:b2Body = world.CreateBody(blockBodyDef); blockBody.CreateFixture(blockFixture); } private function setupWorld():void { world = new b2World(new b2Vec2(0, 9.81), true); setupDebugDraw(); world.SetDebugDraw(debugDraw); } private function setupDebugDraw():void { debugSprite = new Sprite(); stage.addChild(debugSprite); debugDraw = new b2DebugDraw(); debugDraw.SetDrawScale(PTM); debugDraw.SetFillAlpha(0.4); debugDraw.SetFlags(b2DebugDraw.e_shapeBit); debugDraw.SetLineThickness(1.0); debugDraw.SetSprite(debugSprite); } } } |
Your first steps into the new version of Box2DFlash should now be complete! Test your (and my) coding skills by compiling your flash document and your world should randomly place small boxes that are added every few ticks until there are a grand total of 50 bodies in the world.
Try changing a few things around to see what happens. Why not make all the boxes material properties random values, so some boxes will bounce high off the ground and others will drop like they’re made of lead. You could also adjust the size of the boxes in a similar fashion. Also, try testing your knowledge by adding two walls to the sides of the stage to create a giant container for our boxes, ensuring none will fall out and off the stage.



