Friday, December 21, 2012

Box2D in Flash and AS3.. explained for beginners Part 1

Here I go with my first tutorial! I have thought about writing tutorials along time ago, But my English language wasn't as that good, But it got a lot better over the last few months so I hope this tutorial would be helpful for you.

In this series of tutorial we will be creating a basic box2d game then we will extend that to create a very organized OO box2d project using the help of design patterns! But we will leave that for another series.

The final result: Click any where on the stage, as the mouse go far away from the box, the applied impulse increases.



Steps :
  1. Download and setup box2d physics engine for as3
  2. Understanding some box2d basics
  3. Creating the world
  4. Creating the player
  5. Make things come alive! ( the fun part )
  6. Creating walls
  7. Adding mouse listener and applying Impulse to the player
  8. The conclusion

Download and setup box2d physics engine for as3

Create a new folder and put your flash file in. Click here and download the latest box2d version that is compatible with your flash version ( I am using box2d v2.1a ), Then unzip the box2d compressed file anywhere and navigate to "Box2DFlashAS3 2.1a\Source", you should find a Box2D folder in there , copy that and into your flash project folder paste it, my flash project folder looks like this :-
That's it!, now let's make sure everything is working. Open your fla file and from properties write a name for the class, I usually call it Main ( some developers called it with the name of the project ), then create your main class file "Main.as", and write this basic code in the Main.as file

package  
{
    public class Main extends MovieClip
    {
        
        public function Main() 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        public function init(e:Event = null):void
        {
             trace("Working...");
        }
    }
}

Run your flash file and your should see "Working..." in the output window.

Understanding some box2d basics

Before we get into the code, we need first to understand how box2D works?, box2D is a physics engine that calculates all the positions and rotations for the objects you create through it.
e.g. if we needed to create a car with mass = 1000 kg that moves with a speed = 20 m/s, we will first need to create the world where our car should be, set its gravity and update the world every frame, Then we will create the body of the car, give it some physical properties and create a ground also give it some physical properties then we would apply an impulse on the car and it should move with a constant speed ( assuming no resistance ).
Calculating the Impulse :- Impulse = Mass * Velocity ( see wikipedia for more information ).
Impulse is a vector quantity ( which means it has x and y values ), The mass of the car is 1000 kg and we need the velocity to be equal to 20 m/s, we would need to apply an impulse = ( 20000 , 0 ), in another words 20000 in x direction and 0 in y direction.

Things to know About box2D

" Box2D works with meters not pixels "
How is that ?! If your stage size was 800 x 600 pixels, Box2D will assume each pixel = 1 meter.
Okay what is wrong with that ? the answer is simple, how tall are you? I am 1.7 meters so I would fill 1.7 pixels on the screen and that is just a dot! That is not good right..
There is a very simple approach to solve this. It is to work with pixels not meters and divide by a constant to convert to meters ( usually the constant equals 30 which means 30 pixels per meter ).

" Box2D doesn't display any graphics for you "
Yes it contains a debugdraw class that can be used to draw all the box2d objects you created, but this is usually used for testing, because in the real life you would need real graphics ( Sprites and MovieClips ) to represent objects in your game.
How to solve this? We will somehow make box2d objects contain reference for the sprites that act as the displays for this box2d objects. You have all the rights to not understand that at this level, but once we get into the code everything will be clear.

" Box2D object registration point is located at the center "
 You should be familiar with registration points in flash, e.g. if we were to create a box in box2d and give it a start position of (200 ,300), it will be placed like this
Why is this a problem? It's not but we have to make sure that all our sprites have the registration point at the center ( more about that later ).

Also you should know that all box2D classes start with 'b2', e.g. b2Body, b2World, b2Shape, ... etc

We can now simplify the steps to Implement our game
  1. Creating the world
  2. Creating the player ( car )
  3. Creating the ground
  4. Creating walls and obstacles.
  5. Update the world ( Make things come alive )
  6. Applying impulse on the player when mouse is clicked

Creating the world

To create the world we have to define a new object from the b2World class, and let's define it as a class variable to be able to access it anywhere in the project, also let's make a class constant PM which will be used to convert pixels to meters

public static var world:b2World;
public static const PM:uint = 30;

We will be creating each step in a separate method, so create a method call it createWorld()

public function createWorld():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 9.8);
    var sleep:Boolean = true;
    
    world = new b2World(gravity, sleep);
}

Box2D has a class to represent vectors called b2Vec2.
The b2World constructor needs two parameters :
  • gravity : the force that will act on all the box2d objects, forces are represented by vectors, in our example we created a normal gravity vector with a y direction equals to 9.8.
  • sleep : when set to true the world will not simulate inactive bodies, so by setting it to true you increase the performance.

Creating the player ( car )

That's actually the messy part....
When creating physical objects in box2D we have to go through 5 steps :
  1. Create body definition
  2. Create body from world using body definition
  3. Create shape
  4. Create fixture definition
  5. Pass the fixture definition to the CreateFixture method in the body object
But when applying forces, impulses or changing anything on this box2d object we will need only its b2body object.

As we have said before we will create each step in a separate method, so make a method that takes 4 arguments ( x, y, width, height ) and call it createPlayer which returns the b2body to be used later.

public function createPlayer(_x:Number , _y:Number, _width:Number, _height:Number):b2Body
{
    // Create body definition
    var bodyDef:b2BodyDef = new b2BodyDef();
    bodyDef.type     = b2Body.b2_dynamicBody;
    bodyDef.position.Set(_x / PM, _y / PM);
    
    // Create body from world using bodyDef
    var body:b2Body = world.CreateBody(bodyDef);
    
    // Create shape
    var shape:b2PolygonShape = new b2PolygonShape();
    shape.SetAsBox(_width / 2 / PM, _height / 2 / PM);
    
    // Create fixtureDef giving shape
    var fixtureDef:b2FixtureDef = new b2FixtureDef();
    fixtureDef.shape       = shape;
    fixtureDef.restitution = 0.2;
    fixtureDef.friction    = 1;
    fixtureDef.density     = 0.5;
    
    // Pass the fixtureDef to the createFixture method in the body object
    body.CreateFixture(fixtureDef);
    
    return body;
} 

As you can see we first created bodyDef where we set the type ( dynamic ) and position ( remember to divide by the PM to convert pixels to meters ).
Then we created a body from the world using bodyDef.
Then we created a shape of type polygonShape, box2D has 3 types of shapes ( edge, circle and polygon ), In this tutorial we will use the polygon shape, b2PolygonShape has a method to easily create boxes called SetAsBox which takes two parameters ( half width and half height ), also don't forget to convert pixels to meters.
The fourth step which is creating the fixtureDef, it takes the shape and sets some physical properties, lets talk about each property we have set:
  1. Restitution : this video shows you the effect of increasing the restitution of bodies, for more information about the coefficient of restitution watch this video.
  2. Friction : by increasing this you increase the resistance between this object and any other object colliding with and therefore decreasing the time needed for this object to come to rest.
  3. Density : as you may know that mass = density * Volume, box2d compute the mass of an object from its density and its area multiplied by 4, Why 4? I don't know, maybe because it's convenient as the depth of the object. For our example here, the mass of this car will be equal to ( _height * _width * density * 4 ).
Finally, The last step is passing this fixtureDef we created to the createFixture method in the body object.
Don't forget to return the body for us to have full control over this object later.

That is it? No, we haven't created the sprite that will act as the display yet, so let's do that now, In the createPlayer method add this lines before creating the bodyDef

// Create car sprite
var carSprite:Sprite = new Sprite();
carSprite.graphics.beginFill(0xafafaf, 1);
carSprite.graphics.drawRect( -_width / 2, -_height / 2 , _width, _height);
carSprite.graphics.endFill();
carSprite.x = _x;
carSprite.y = _y;
addChild(carSprite);

That is a very basic way of drawing a rectangle in a sprite, Note that we have set the registration point to the center by drawing around the center ( - width / 2 and - height / 2).
Okay we now created our box2d object and the display sprite, how to make a reference to the sprite in the box2d object? Fairly easy, just add this line after creating bodyDef object

bodyDef.userData = carSprite;

 

Part 1: All come together

Nothing Impressive has happened yet, just a box hanging there on the air , but as you will see in the first section of the second part where we make everything come alive ...
Part1 final code :-

public class Main extends MovieClip
{
    
    public static var world:b2World;
    public static const PM:uint = 30;
    private var player:b2Body;
    
    public function Main() 
    {
        if (stage) init();
        else addEventListener(Event.ADDED_TO_STAGE, init);
    }
    
    public function init(e:Event = null):void
    {    
        createWorld();
        
        player = createPlayer(40, 50, 50, 10);
        
    }
    
    public function createWorld():void
    {
        var gravity:b2Vec2 = new b2Vec2(0, 9.8);
        var sleep:Boolean = true;
        
        world = new b2World(gravity, sleep);
    }
    
    public function createPlayer(_x:Number , _y:Number, _width:Number, _height:Number):b2Body
    {
        // Create car sprite
        var carSprite:Sprite = new Sprite();
        carSprite.graphics.beginFill(0xafafaf, 1);
        carSprite.graphics.drawRect( -_width / 2, -_height / 2 , _width, _height);
        carSprite.graphics.endFill();
        carSprite.x = _x;
        carSprite.y = _y;
        addChild(carSprite);
        
        // Create body definition
        var bodyDef:b2BodyDef = new b2BodyDef();
        bodyDef.userData = carSprite;
        bodyDef.type     = b2Body.b2_dynamicBody;
        bodyDef.position.Set(_x / PM, _y / PM);
        
        // Create body from world using bodyDef
        var body:b2Body = world.CreateBody(bodyDef);
        
        // Create shape
        var shape:b2PolygonShape = new b2PolygonShape();
        shape.SetAsBox(_width / 2 / PM, _height / 2 / PM);
        
        // Create fixtureDef giving shape
        var fixtureDef:b2FixtureDef = new b2FixtureDef();
        fixtureDef.shape       = shape;
        fixtureDef.restitution = 0.2;
        fixtureDef.friction    = 1;
        fixtureDef.density     = 0.5;
        
        // Pass the fixtureDef to the createFixture method in the body object
        body.CreateFixture(fixtureDef);
        
        return body;
    }
    
}

2 comments:

  1. Thank you so much very useful post for begginer. I really appreciate that. Keep going please ! :)

    ReplyDelete
  2. If i've put a movieclip in the stage, what's code that could work for it?

    ReplyDelete