source code

Final result of Box2D Chapter 1

I mention in my last post that Todd Kerpelman created a set of Box2D video tutorials (http://www.kerp.net/box2d/), I just finished up the first chapter and here is what I came up with…

As you can see it isn’t a whole lot, just a bunch of boxes falling down, but damn does it look good :) .

Here is the source code for the project

package
{
	import Box2D.Collision.b2AABB;
	import Box2D.Collision.Shapes.b2PolygonDef;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;
	import Box2D.Dynamics.b2DebugDraw;
	import Box2D.Dynamics.b2World;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.ui.Keyboard;
	import fl.controls.Button;
	import flash.events.MouseEvent;

	/**
	 * Final result of Todd Kerpelman's chapter 1 box2d video tutorial
	 * (http://www.kerp.net/box2d/).
	 * The code here is pretty much line for line from Kerpelman's tutorial
	 * I added a small modification so the simulation may be restarted when
	 * the user clicks the restart button in the top left of the screen.
	 * @author Edgar Miranda
	 */
	public class HelloBoxWorld extends MovieClip
	{

		protected var _world:b2World;

		// Ratio of pixels to meters
		public static const RATIO:Number = 40;

		// Number of frames until we launch another crate
		private var _nextCrateIn: int;

		// Button to restart simulation
		public var restartButton: Button;

		public function HelloBoxWorld()
		{
			// 1. Set up the world
			setupWorld();
			// 2. Creating walls and floor
			createWallsAndFloor();

			setupDebugDraw();

			_nextCrateIn = 0;

			addEventListener(Event.ENTER_FRAME, newFrameEvent);

			// Put the restart button on the top most index
			// so it isn't covered by the falling crates
			addChild(restartButton);

			// Event listener in order to restart
			// simulation when the restart button is pressed
			restartButton.addEventListener(MouseEvent.CLICK, onRestartButtonClick)
		}

		private function onRestartButtonClick(e: MouseEvent): void {

			// Remember that the world counts as one body so you
			// want to remove bodies until there is only one body left
			while (_world.GetBodyCount() > 1) {
				_world.DestroyBody(_world.GetBodyList());
			}

			// recreate the walls again
			createWallsAndFloor();

			// set to default value
			_nextCrateIn = 0;
		}

		private function setupDebugDraw():void
		{
			var spriteToDrawOn:Sprite = new Sprite();
			addChild(spriteToDrawOn);

			var artistForHire:b2DebugDraw = new b2DebugDraw();
			artistForHire.m_sprite = spriteToDrawOn;
			artistForHire.m_drawScale = RATIO;
			artistForHire.SetFlags(b2DebugDraw.e_shapeBit);
			artistForHire.m_lineThickness = 2.0;
			artistForHire.m_fillAlpha = 0.6;

			_world.SetDebugDraw(artistForHire);
		}

		private function newFrameEvent(e:Event):void
		{
			_world.Step( 1.0 / 30, 10);

			// Every few frames, until a certain numer of crates have been added
			if (_nextCrateIn-- <= 0 && _world.m_bodyCount < 80) {

				// add a random create to the world
				addARandomCrate();
				_nextCrateIn = 1;
			}

			//trace("Falling crate is at", _fallingCrate.GetPosition().x, ",", _fallingCrate.GetPosition().y);
		}

		private function addARandomCrate():void
		{
			// Add a falling block to the world.
			var fallingCrateDef:b2PolygonDef = new b2PolygonDef();
			fallingCrateDef.SetAsBox(randomInt(5, 40) / RATIO, randomInt(5, 40) / RATIO);
			fallingCrateDef.friction = 0.8;
			fallingCrateDef.restitution = 0.3;
			fallingCrateDef.density = 0.7;

			// Set it to, 250, -30
			var fallingBodyDef:b2BodyDef = new b2BodyDef();
			fallingBodyDef.position.Set(randomInt(15, 530) / RATIO, randomInt(-100, -10) / RATIO);
			fallingBodyDef.angle = randomInt(0,360) * Math.PI / 180;

			var fallingCrate:b2Body = _world.CreateBody(fallingBodyDef);
			fallingCrate.CreateShape(fallingCrateDef);
			fallingCrate.SetMassFromShapes();

		}

		// Return a random number between lowVal and highVal, INCLUSIVE
		private function randomInt(lowVal:int, highVal:int): int
		{
			if (lowVal <= highVal) {
				return (lowVal + Math.floor(Math.random() * (highVal - lowVal + 1)));
			}else
			{
				throw(new Error("OMG! Wrong values passed to randomInt!!"));
			}
		}

		private function createWallsAndFloor():void
		{
			// Create the shape (polygon) definition
			var bigLongShapeDef: b2PolygonDef = new b2PolygonDef();
			bigLongShapeDef.vertexCount = 4;
			b2Vec2(bigLongShapeDef.vertices[0]).Set(0 / RATIO, 0 / RATIO);
			b2Vec2(bigLongShapeDef.vertices[1]).Set(550 / RATIO, 0 / RATIO);
			b2Vec2(bigLongShapeDef.vertices[2]).Set(550 / RATIO, 10 / RATIO);
			b2Vec2(bigLongShapeDef.vertices[3]).Set(0 / RATIO, 10 / RATIO);
			bigLongShapeDef.friction = 0.5;
			bigLongShapeDef.restitution = 0.3;
			bigLongShapeDef.density = 0.0;

			// Create the body definition
			var floorBodyDef:b2BodyDef = new b2BodyDef();
			floorBodyDef.position.Set(0 / RATIO, 390 / RATIO);

			// Create the body
			var floorBody:b2Body = _world.CreateBody(floorBodyDef);

			// Create the shape
			floorBody.CreateShape(bigLongShapeDef);
			floorBody.SetMassFromShapes();

			var tallSkinnyShape:b2PolygonDef = new b2PolygonDef();
			tallSkinnyShape.SetAsBox( 5 / RATIO, 195 / RATIO);
			tallSkinnyShape.friction = .5;
			tallSkinnyShape.restitution = 0.3;
			tallSkinnyShape.density = 0.0;

			var wallBodyDef:b2BodyDef = new b2BodyDef();
			wallBodyDef.position.Set(5 / RATIO, 195 / RATIO);

			var leftWall:b2Body = _world.CreateBody(wallBodyDef);
			leftWall.CreateShape(tallSkinnyShape);
			leftWall.SetMassFromShapes();

			wallBodyDef.position.Set(545 / RATIO, 195 / RATIO);
			var rightWall:b2Body = _world.CreateBody(wallBodyDef);
			rightWall.CreateShape(tallSkinnyShape);
			rightWall.SetMassFromShapes();

			//trace("My world now has", _world.m_bodyCount, "bodies in it! Wow!");
		}

		private function setupWorld():void
		{
			// 1. Set up the size of the universe
			var universeSize:b2AABB = new b2AABB();
			universeSize.lowerBound.Set( -3000 / RATIO, -3000 / RATIO);
			universeSize.upperBound.Set( 3000 / RATIO, 3000 / RATIO);			

			// 2. Define gravity
			var gravity: b2Vec2 = new b2Vec2(0, 9.8);

			// 3. Ignore sleeping objects
			var ignoreSleeping:Boolean = true;

			_world = new b2World(universeSize, gravity, ignoreSleeping);

			//trace("My world has", _world.GetBodyCount(), "bodies in it!");
		}

	}

}

You can get the Zip file containing all the project resources (source code, FLA, FlashDevelop project file) by clicking the link below.

Box2dChapter1_EdgarMirandadotnet.zip

My next goal is to finish up chapter 2, which is a bit longer then chapter 1 (50 lessons instead of 22). I’m currently on chapter 2 lesson 4, and I’m aiming to do around 5-10 lessons a day. I find it it’s easier to learn if I do about an hour or so of lessons each day instead of doing the whole batch in a day or two, there is also so much I can take of just watching video tutorials all day :) .

Look out for my final result for chapter 2!