20
Nov/09
0

Box 2D Tutorial

If you were out at Adobe Max this year, you might have seen the huge Max Companion walls. It’s a little bit difficult to see in the video, but tweets relating to Adobe Max would come in, they would be loaded and displayed for a few seconds, then they would fall as if gravity was applied to them. The introduction of physics into this application was done via a framework called Box-2D.

What I plan to show you is how to set up a very basic application that has physics applied to it. Setting up your “physical world” is definitely the most difficult part of using Box2D. Here’s the methods I use to create the Box2D world.

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
public function createWorld(event:Event = null):void
{
    var ab:b2AABB = new b2AABB();  //the bounds of the physics world
    ab.lowerBound.Set(-200, -200);
    ab.upperBound.Set(200, 200);

    var gravity:b2Vec2 = new b2Vec2(0, 4.0);  //the constant force of gravity

    world = new b2World(ab, gravity, false);  //create the world

    createBorderObject(0, this.height, this.width, 100); //floor
    createBorderObject(0, -this.height, this.width, 100); //ceiling
    createBorderObject(-100, -this.height, 100, 3*this.height); //left
    createBorderObject(this.width + 2, -this.height, 100, 3*this.height); //right

    this.addEventListener( Event.ENTER_FRAME, caluclateWorld );
}

private function createBorderObject(xp:Number, yp:Number, w:Number, h:Number):void
{  //these bodies will prevent objects from getting out of our physics world and being unusable
    var the_box:b2PolygonDef = new b2PolygonDef();
    the_box.SetAsBox(w/physicsScale/2, h/physicsScale/2);
    the_box.density = 0;
    the_box.friction = .4;
    the_box.restitution = 0;

    var gbd:b2BodyDef = new b2BodyDef();
    gbd.position.Set((xp+w/2)/physicsScale, (yp+h/2)/physicsScale);

    var ground:b2Body = world.CreateBody(gbd);
    ground.CreateShape(the_box);
    ground.SetMassFromShapes();

    var rect:Canvas = new Canvas();
    rect.graphics.beginFill(0xFF00FF, 0);
    rect.graphics.drawRect(xp, yp, w, h);
    addChild(rect);
}

In order to create the world you need to set up a few things. The first thing you have to set up is the boundaries of the world, which need to extend beyond what you are actually going to use. The reason it needs to be larger than your play area, is because if any objects exit this region, they’ll become unusable to the physics world. Next you’ll need to create gravity, which doesn’t need to go to the bottom of your screen. You could easily set up gravity to go to the top of the screen or to the side to one side. The last part of the constructor is whether or not you want objects at rest to go to sleep. I’ve always set it to false, but I’m by no means an expert on all of the options.

Within my createWorld method I’ve also created four border objects. These are invisible barriers that prevent objects from reaching my physics border. Lastly, I add an ENTER_FRAME event listener so that every time flash begins to redraw this component it will calculate any changes. What follows is my calculateWorld function.

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
private function updateWorld(e:Event):void
{
    world.Step((1/30),10);
    var bodyPos:b2Vec2;
    var bodyRot:Number;
    var renderers:ArrayCollection = getActors();
     //updates our visual components to coincide with their physics
    for( var i:Number = 0; i < renderers.length; i++ )
    {
        var renderer:Actor = renderers.getItemAt(i) as Actor;
        var body:b2Body = renderer.physicsBody;
        if( body )
        {
            bodyPos = body.GetPosition();
            renderer.rotation=0;

            var m:Matrix=renderer.transform.matrix;
            m.tx =- renderer.width/2;
            m.ty =- renderer.height/2;
            m.rotate( body.GetAngle() );   
            //m.scale(1,1);
            // Now set the position to the world position
            m.tx+=bodyPos.x*physicsScale;
            m.ty+=bodyPos.y*physicsScale;

            // ...and set the whole thing at once via the matrix.
            // ie. Update the sprite.
            renderer.transform.matrix=m;
            renderer.scaleX = 1;
            renderer.scaleY = 1;
        }
    }
}

My updateWorld function has a number of steps. First, I tell Box2D to calculate the changes, by passing in the number of refresh frequency (1/30) as well as the number of iterations to calculate. After Box2D has calculated the changes I have to loop through all of the objects in the “world” and update their renderer. The renderers are visual components that are tied to a physics body, which is necessary because the objects in the Box2D are not visible.

For the purposes of my sample, I’ve created a simple renderer which you can see here.

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
package com.softwarejesus.physics
{
    import Box2D.Dynamics.b2Body;
   
    import mx.containers.Canvas;

    public class Actor extends Canvas
    {
        public var physicsBody:b2Body;
        public var shape:String;
        public function Actor() {
           
        }
       
        public function drawShape():void
        {
            if (shape == Physics.SQUARE_SHAPE) {
                this.setStyle("backgroundAlpha", 1);
                this.setStyle("backgroundColor", 0x000000);
            } else {
                this.setStyle("backgroundAlpha", 0);
                graphics.beginFill(0x000000);
                graphics.drawCircle(this.width/2, this.height/2, this.width/2);
            }
        }
    }
}

Now that you’ve seen the definition for my renderers, let’s see how objects get added to the world.

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
public function addShape():void
{
    if(this.selectedShape == CIRCLE_SHAPE)
        addBall();
    else
        addBox()
}

public function addBox():void
{
    var newCanvas:Actor = new Actor();
    newCanvas.width = this.diameter;
    newCanvas.height = this.diameter;
    newCanvas.x = Math.random() * ( this.width - this.diameter);
    newCanvas.y = -newCanvas.height;
    newCanvas.shape = this.selectedShape;
    newCanvas.drawShape();

    var b1:b2PolygonDef = new b2PolygonDef();
    b1.SetAsBox((diameter/2)/physicsScale , (diameter/2) / physicsScale);
    b1.density = density;
    b1.friction = friction;
    b1.restitution = restitution;

    var bd:b2BodyDef = new b2BodyDef();
    bd.position.Set( (newCanvas.x+(newCanvas.width/2))/physicsScale, (newCanvas.y+(newCanvas.height/2))/physicsScale );
    bd.angle = 0;

    var physicsBody:b2Body = world.CreateBody(bd);
    physicsBody.CreateShape(b1);
    physicsBody.SetMassFromShapes();
    physicsBody.ApplyImpulse( b2Vec2.Make(Math.floor(Math.random() * (10 -  -10)) + -10,0), b2Vec2.Make(10,5) );

    newCanvas.physicsBody = physicsBody;
    newCanvas.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
    this.addChild(newCanvas)
}

public function addBall():void
{
    var newCanvas:Actor = new Actor();
    newCanvas.width = diameter;
    newCanvas.height = diameter;
    newCanvas.x = Math.random() * ( this.width - diameter);
    newCanvas.y = -newCanvas.height;
    newCanvas.shape = selectedShape;
    newCanvas.drawShape();

    var b1:b2CircleDef = new b2CircleDef();
    b1.radius = (diameter/2)/ physicsScale;
    b1.density = density;
    b1.friction = friction;
    b1.restitution = restitution;

    var bd:b2BodyDef = new b2BodyDef();
    bd.position.Set( (newCanvas.x+(newCanvas.width/2))/physicsScale, (newCanvas.y+(newCanvas.height/2))/physicsScale );
    bd.angle = 0;

    var physicsBody:b2Body = world.CreateBody(bd);
    physicsBody.CreateShape(b1);
    physicsBody.SetMassFromShapes();
    physicsBody.ApplyImpulse( b2Vec2.Make(Math.floor(Math.random() * (10 + 10)) + -10,0), b2Vec2.Make(10,5) );

    newCanvas.physicsBody = physicsBody;
    newCanvas.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
    this.addChild(newCanvas)
}

I start by creating my renderer. Next, I create the shape that I want to add to the world, for this example I’ve limited the shapes to circles and squares for the sake of simplicity. I set a few options on the shape that determine how the shape interacts with it’s surroundings in our world. Those options are density, friction, restitution (bounciness), and size. You can see the effects of each of these options in my example below.

The last element of my functions for adding my shapes is the addition of a MOUSE_DOWN event listener. The purpose of this is to allow the user to actually interact with the objects in the world. Here’s my mouse event handlers.

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
public function handleMouseDown(evt:MouseEvent):void
{
    var newX:int = (evt.stageX+15)/physicsScale;
    var newY:int = (evt.stageY+15)/physicsScale;
    var renderer:Actor;
    if (evt.target is Actor) {
        renderer = evt.target as Actor;
    } else {
    }
    if( renderer )
    {
        var body:b2Body = renderer.physicsBody;
        var md:b2MouseJointDef = new b2MouseJointDef();
        md.body1 = world.GetGroundBody();
        md.body2 = body;
        md.target.Set(newX, newY);
        md.maxForce = 5000.0;
        md.timeStep = (1/30);

        var newMouseJoint:b2MouseJoint = world.CreateJoint(md) as b2MouseJoint;
        mouseJoint = newMouseJoint;
        body.WakeUp();
        this.addEventListener(MouseEvent.MOUSE_MOVE, handleMouseMove);
        this.addEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
    }
}

public function handleMouseMove(event:MouseEvent):void
{
    var newX:int = (event.stageX+10)/physicsScale;
    var newY:int = (event.stageY+10)/physicsScale;
    var p2:b2Vec2 = new b2Vec2(newX, newY);

    if(mouseJoint)
        mouseJoint.SetTarget(p2);

}  

public function handleMouseUp(event:MouseEvent):void    
{
    this.removeEventListener(MouseEvent.MOUSE_MOVE, handleMouseMove);
    this.removeEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
    if (mouseJoint) {
        world.DestroyJoint(mouseJoint);
        mouseJoint = null;
    }


}

When the user mouses down on one of the renderers a mouse joint is created and we listen for a mouse move event. A mouse joint is a means for us to apply an external force to our objects.

When you put all this together, you have the goofy little sample below.

Get Adobe Flash player

You can also see it here
Or download this source from here

Comments (0) Trackbacks (0)

No comments yet.

Sorry, the comment form is closed at this time.

No trackbacks yet.