Collapse all
tutorial code

A Box2D platform game tutorial

This tutorial is not completed, last update 18 januari 2010

Tutorial result

Click once on the game to enable the keyboard.
Move the player with the four arrow keys.

Before we start

For this tutorial you will need the following:

If you have questions about Box2D details read the wiki or the manual
While the wiki manual is down you can download the manual from me.

Create the following folder structure:

In this tutorial we will make a small platform game
We will try to use the most of Box2D and we won't create specific game rules.
For example we will use Box2D to simulate gravity, so the player can fall of objects, but we won't determine when the player should die.

Our goal

For creating a game that is maintainable the best thing we could do is to design a good structure in our code.
I tried to think of all the thing the code should do and with that information made up some files.

At first we need a file that chooses what level we are going to play: Main.as
All the levels have a lot in common so if we put these similarities in one seperate file we don't have to type this for every single level: Level.as
Then we need a file for every differnt level: Starting with Level1.as
All the objects also have some functions and variables in common, lets put these variables in a seperate file to: Actor.as
Same as with the levels we need a file for every different object: Circle.as, Player.as, Ground.as etc.
When creating all these files i discovered they share a few variables: LevelVars.as
Last but not least we need the hitTest functionality that comes with Box2D: ContactListener.as

As you can see the fla opens Main.as directly and Main.as opens up the Level1.as which extends Level.as
Let start working on these files first.

file diagram

Diagram that displays the files we are going to create and their relations

Step 1: The FLA file

Open up the FLA and take a look at the Library
It contains 4 movieclips.
If you look at the properties of one of these movieclips you will see that the movieclip linkage has been set to 'Export for ActionScript'
This enables us to use these movieclips in ActionScript later on.
To make the game flexible for development and maintainability we will not work with the timeline.
In order to start an actionscript file without using the timeline we use the built-in document class from Flash.
The document class opens the given class when starting the file.
This is all we need to do with the fla for now.

properties

When looking at the document properties the 'Class:' defines the document class

Step 2: Main.as

This is the class that will be handling which level is going to show up
I thought it should be usefull to have a single MovieClip to use as a container for the levels.
To this MovieClip we can add the event listeners for the keyDown and keyUp.
Create the main.as in the same folder as the fla file.

Main.as code:

package{
   
    import flash.events.*;
    import flash.display.*;
    import Levels.*
   
    public class Main extends Sprite
    {
        public var m_curLevel:MovieClip;
       
        public function Main()
        {
            trace('Main.as constructor');
        }
    }
}

Lets debug that and watch the output window!

Step 3: Lets create Level1.as

You made a folder called 'Levels', it will contain classes that all use the standard level.as.
The classes should only define the differences between levels
So the only code in the Level1.as should define which objects are present in the level and at what location

Level1.as code:

package Levels {
   
    import Classes.*;
    import flash.geom.Point;
    import LevelElements.*;
    import flash.display.MovieClip;
   
    public class Level1 extends MovieClip{

        public function Level1 (){
            trace('Level1.as contstructor');
            //*objects we want to create:*
           
            //ground surface
           
            //single platform
           
            //two tall high walls for walljumping
           
            //hight platform to land on after walljumping
           
            //the player
           
            //a triangle
           
            //a small qube
           
            //a small circle
        }
       
    }
}
Now open main.as again and add the following lines:

Main.as code:

        public function Main()
        {
            trace('Main.as constructor');
            m_curLevel = new Level1(); //add this line
            addChild(m_curLevel); // add this line
        }
 
Lets debug again and watch if the output says: 'level1.as constructor'.

Step 4: The level

Lets start making the level class as level.as in the Classes folder
Every level you make should extend the level class, that way you don't have to make code twice.

Level.as code:

package Classes{
   
    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.events.KeyboardEvent;

    public class Level extends MovieClip{
   
        public function Level() {
            trace('Level.as constructor');
            //This is the constructor of this class
            //We define a sprite in which the world resides, we can use the sprite for camera movement
            //Then we define and create the world for our level.
            //We want an Event Listener that calls Update on entering each new frame
        }
       
        public function Update(e:Event):void {
            //On every frame we want the world to change.
        }
       
        public function keyPressed(e:KeyboardEvent):void {
            //This function is called by a Event listener in Main.as
            //The purpose is to add the pressed keys in an array.
        }
       
        public function keyUnpressed(e:KeyboardEvent):void {
            //This function is also called by an event listener in Main.as
            //The purpose from this function is to remove released keys from the keypressed array
        }
       
        private function updateCamera(xpos:Number,ypos:Number):void {
            //use this function to keep the player at the center of the screen
        }
    }
}
 

As said before every level should extend this level.as, so open up Level1.as and change the following

Level1.as code:

package Levels {
   
    import Classes.*;
    import flash.geom.Point;
    import LevelElements.*;
    // remove 'import flash.display.MovieClip;' because we don't need it anymore
   
    public class Level1 extends Level{// change 'extend MovieClip' to 'extend Level'

            //no changes here
       
    }
}

And again debug and watch the output for the trace statements.

Step 5: Creating LevelVars

I have been thinking about what to do next. Create the world in Level.as or create the LevelVars.as first?
The best learning curve would be achieved if we would create the world and all its object and then find out we had to rewrite a lot of our code. But this is a tutorial and I could also just tell you that after a while you would find out that there are a few variables that are needed by multiple classes and it would be nice to have a global variable storage class for them.

I'll give you all the variables in advance, before you know that you will need them.
For these variables we create the LevelVars.as in the Classes folder

LevelVars.as code:

package Classes
{
    import Box2D.Dynamics.b2World;
    import Box2D.Dynamics.b2Body;
    import flash.display.Sprite;

    public class LevelVars
    {
       
        public static const m2p:Number=20; //defines the box2d metric to pixel ratio
        public static var player:b2Body; //the box2d body of the player used for collision detection
        private static var m_sprite:Sprite; //should contain all the visual objects, used for camera movement
        private static var m_world:b2World; //the world that contains all the box2d objects
        public static var jump:Boolean; //tells the Level.as if the player may jump
        public static var jumpdirection:int; //contains the direction that the player should jump

        public function LevelVars()
        {
            // empty constructor
        }
       
        static public function get world():b2World { return m_world; }
       
        static public function set world(value:b2World):void
        {
            if(m_world==null){//if no world exists
                m_world = value;
            }
        }
       
        static public function get sprite():Sprite { return m_sprite; }
       
        static public function set sprite(value:Sprite):void
        {
            if(m_sprite==null){//if no sprite has been given yet
                m_sprite = value;
            }
        }
       
    }
}

As you can see the LevelVars has 6 variables, 4 of them are public, 2 of them are private.
The public ones are directly available for every other class.
The two variables which are private both have a get and a set function. (lines 22 to 38)
If a class wants to set the world or the m_sprite it has to pass the variable to the setter.
This is done by 'LevelVars.sprite = sprite';
The setter makes it easy to check if the variable is as desired, in this case we check if the variable allready exists.
For more information on get and set functionality try some books or other learning material.

Step 6: Creating the world

The previous steps are all general ways to improve flexible code and maintainability
The most of what we are going to make now demands specific knowledge of Box2D.
I used only the manual on the wiki and some more detailed pages on the wiki for finding solutions to my problems.
So if you have questions how i made up some weird variables, read the manual first and use Google.

This next step will be in the Level.as in the constructor.

Level.as constructor code:

    import flash.display.*;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import Box2D.Collision.b2AABB;
    import Box2D.Common.Math.b2Vec2;
    import Box2D.Dynamics.b2Body;
    import Box2D.Dynamics.b2DebugDraw;
    import Box2D.Dynamics.b2World;

    public class Level extends MovieClip{
   
        public function Level() {
            trace('Level.as constructor');
            //Sprite is used as the container for all the level elements and camera movement
            LevelVars.sprite = new Sprite();
            addChild(LevelVars.sprite);
           
            //Add an event listener to update the level every frame
            addEventListener(Event.ENTER_FRAME, Update, false, 0, true);
           
            var worldAABB:b2AABB = new b2AABB();
            worldAABB.lowerBound.Set(-5000 / LevelVars.m2p, -5000 / LevelVars.m2p);
            worldAABB.upperBound.Set(5000 / LevelVars.m2p, 5000 / LevelVars.m2p);
            var gravity:b2Vec2=new b2Vec2(0,9.8);
            var doSleep:Boolean=true;
            LevelVars.world = new b2World(worldAABB, gravity, doSleep);
           
            //The debugdraw enables the view at objects as box2d sees them,
            //Uncomment if you created a new element-class and if you want to check if its displayed correctly
            /*var dbgDraw:b2DebugDraw = new b2DebugDraw();
            dbgDraw.m_sprite= LevelVars.sprite;
            dbgDraw.m_drawScale=LevelVars.m2p;
            dbgDraw.m_fillAlpha=0.3;
            dbgDraw.m_lineThickness=1.0;
            dbgDraw.m_drawFlags=b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit;
            LevelVars.world.SetDebugDraw(dbgDraw);*/
           
        }
    }

Box2D Shapes & sprites

One more thing to know about Box2D is how it handles sprites. In fact Box2D doens't handle with sprites at all.
It is our concern and task to make sure that the sprite and the Box2D shape line-up and move the same.
Box2D is just a very good engine which will calculate collisions and much more but it doesn't do anything with sprites. Lukely there is debugdraw to see what Box2D sees, so we can make our sprites' sizes the same as Box2D's sizes. And there are simple functions to find the position of the Box2D object so we can make our object move the same. So it isn't much of a problem but it is important to know what really happens.

shape-sprite example

As you can see the circles' shape and the sprite need some fine tuning.

Step 7: Creating the Actor class

When creating the Actor class I used the best practice method by watching what others made. Offcourse it's not a real challenge anymore but you will be sure that it is the best way to do it.
I tried to add enough comments to explain the meaning of most of what happens.
Create the Actor.as file in the Classes folder.

Actor.as code:

package Classes
{
   
    import Box2D.Dynamics.b2Body;
    import flash.display.DisplayObject;
    import flash.events.EventDispatcher;

    public class Actor extends EventDispatcher
    {
       
        private var _body:b2Body; // contains the Box2D body
        private var _costume:DisplayObject; // contains our sprite
       
        public function Actor()
        {
            //empty constructor
        }
       
        //Simple function for comfort, will be proven later.
        public function getHalfHeight():Number
        {
            return _costume.height / 2;
        }
       
        //The Level.as function Update will call this function on every frame update
        public function updateNow():void
        {
            //To improve performance we only want to change sprites of bodies which can move.
            //The static bodies are solid grounds that won't move at all.
            if(!_body.IsStatic()){
                updateMyLook();
                //If the body is probable to move update the sprite's location
            }
           
            childSpecificUpdating(); //regular function call.
        }
       
        //In case we want to destroy our body
        public function destroy():void
        {
            cleanUpBeforeRemoving();
            _costume.parent.removeChild(_costume); // remove the sprite
            LevelVars.world.DestroyBody(_body); // remove the Box2D body
        }
       
        public function cleanUpBeforeRemoving():void
        {
            // to be overwritten by children
            // the child can use this function for example
            // for displaying a 'death' animation
        }
       
        public function childSpecificUpdating():void
        {
            // to be overwritten by children
            // the child can use this function most likely to use some AI
            // because this function is called every EnterFrame it is a good place for AI
        }
       
        //Private function because it only needs to be accesible within this class
        //The only purpose of this function is to update the sprite's location and angle
        private function updateMyLook():void
        {
            _costume.x = _body.GetPosition().x * LevelVars.scale;
            _costume.y = _body.GetPosition().y * LevelVars.scale;
            _costume.rotation = _body.GetAngle() * 180 / Math.PI;
        }
       
        //This function enables us to easily determine the player.
        public function setPlayer():void
        {
            LevelVars.player = _body;
        }
       
        //This is not a regular function as you can see 'set', this is called a property
        //It is a robuste way of assigning a value to a variable in another class
        //We force ourselves to use this function an therefore we know exactly when it occurs without having listeners
        //We could add some sort of value validation but I don't find it necessary now,
        //I found it very useful to know when the b2Body was ready so i could assign the userData
        public function set body(value:b2Body):void
        {
            _body = value;
            //The userData will later on be our only reference to the specific child details
            //For example the userData can tell us later on that the b2Body is an instance of Ground
            _body.SetUserData(this);
        }
       
        //I found it nice to know when the sprite was assigned so i could directly update the position.
        //instead of waiting for the EnterFrame for updating the position of the sprite.
        public function set costume(value:DisplayObject):void
        {
            _costume = value;
            updateMyLook();
        }
       
    }

}

Step 8: Creating our first LevelElement: Ground!

Finally after all those hours (or maybe minutes) we will finaly have some visual progress.
Writing a class for creating blocks is really easy if you copy paste some code from the phystest. It would look something like this:

Example code for Ground.as:

package LevelElements
{
    import Box2D.Collision.Shapes.b2PolygonDef;
    import Box2D.Dynamics.b2Body;
    import Box2D.Dynamics.b2BodyDef;
    import Classes.*;
   
    public class Ground extends Actor
    {
       
        public function Ground(xloc:Number, yloc:Number)
        {
            // create a Box2D ShapeDefenition
            var shapedef:b2PolygonDef = new b2PolygonDef();
            // The next properties vary per Shape, these are specific for the b2PolygonDef
            shapedef.density = 1.0;
            shapedef.friction = 0.5;
            shapedef.restitution = 0.1;
            shapedef.SetAsBox(10 , 10); //these two are the width and height
           
            // create a Box2D BodyDefenition
            var bodydef:b2BodyDef = new b2BodyDef();
            bodydef.position.Set(xloc, yloc);
           
            // create a Box2D Body
            // use _body instead of body to prevent errors with Actor.body
            var _body:b2Body;
            //The LevelVars.world is created in Level.as
            _body = LevelVars.world.CreateBody(bodydef);
            _body.CreateShape(shapedef);
            _body.SetMassFromShapes();
            //By super.body we refer to Actor.body
            super.body = _body;
           
            //This is the instantiation of our sprite
            //Again use _clip instead of clip to prevent problems with Actor.clip
            //PhysGround is the Classname of a MovieClip in the Fla libary
            _clip = new PhysGround();
            _clip.width = 20;
            _clip.height = 20;
            //super.costume refers to Actor.costume
            super.costume = _clip;
            //don't forget to add the sprite to the displaylist.
            //and it should be attached in the displaylist in the Levelvars.sprite sprite
            //The LevelVars.sprite is defined at the start of Level.as
            LevelVars.sprite.addChild(_clip);
        }
    }
}

Center orientated

Beter example code of Ground.as:

....

Changeable properties

Final example code of Ground.as:

....

The Ground.as we created will be the only example I'll give about creating LevelElements.
It isn't that I don't want to tell you how to write all the other elements, but they are all allmost the same.
Except some minor differences between shapes; a circle uses radius where our ground uses width and height.
Take a look at the finished tutorial code to see the other LevelElements.