Understanding Box2D debug draw

When you are about to create a complex script with Box2D – or even a simple one, but that’s complex for your skills – I suggest to enable debug draw.

Debug draw will provide you everything you need in order to verify your script is working correctly, and once you are satisfied with the result, you can start attaching movieclips to skin the shapes.

So I am going to explain everything you need to know about debug draw, and probably something more.

The following movieclip is the HelloWorld.fla file you can find in the distribution package, with all movieclips removed and the debug draw enabled:

Let’ see how did I modify the script in order to enable debug draw… interesting lines range from line 21 to line 35

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	import Box2D.Dynamics.Joints.*;
	import flash.events.MouseEvent;
	public class HelloWorld extends Sprite {
		var body:b2Body;
		var mouseJoint:b2MouseJoint;
		public function HelloWorld() {
			addEventListener(Event.ENTER_FRAME, Update, false, 0, true);
			var worldAABB:b2AABB = new b2AABB();
			worldAABB.lowerBound.Set(-100.0, -100.0);
			worldAABB.upperBound.Set(100.0, 100.0);
			var gravity:b2Vec2=new b2Vec2(0.0,10.0);
			var doSleep:Boolean=true;
			m_world=new b2World(worldAABB,gravity,doSleep);
			// debug draw start
			var m_sprite:Sprite;
			m_sprite = new Sprite();
			addChild(m_sprite);
			var dbgDraw:b2DebugDraw = new b2DebugDraw();
			var dbgSprite:Sprite = new Sprite();
			m_sprite.addChild(dbgSprite);
			dbgDraw.m_sprite=m_sprite;
			dbgDraw.m_drawScale=30;
			dbgDraw.m_alpha = 1;
			dbgDraw.m_fillAlpha=0.5;
			dbgDraw.m_lineThickness=1;
			dbgDraw.m_drawFlags=b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit|b2DebugDraw.e_coreShapeBit|b2DebugDraw.e_aabbBit|b2DebugDraw.e_obbBit|b2DebugDraw.e_pairBit|b2DebugDraw.e_centerOfMassBit;
			m_world.SetDebugDraw(dbgDraw);
			// debug draw end
			var bodyDef:b2BodyDef;
			var boxDef:b2PolygonDef;
			var circleDef:b2CircleDef;
			bodyDef = new b2BodyDef();
			bodyDef.position.Set(10, 12);
			boxDef = new b2PolygonDef();
			boxDef.SetAsBox(30, 3);
			boxDef.friction=0.3;
			boxDef.density=0;
			body=m_world.CreateBody(bodyDef);
			body.CreateShape(boxDef);
			body.SetMassFromShapes();
			for (var i:int = 1; i < 10; i++) {
				bodyDef = new b2BodyDef();
				bodyDef.position.x=Math.random()*12+2;
				bodyDef.position.y=Math.random()*5;
				var rX:Number=Math.random()+0.5;
				var rY:Number=Math.random()+0.5;
				if (Math.random()<0.5) {
					boxDef = new b2PolygonDef();
					boxDef.SetAsBox(rX, rY);
					boxDef.density=1.0;
					boxDef.friction=0.5;
					boxDef.restitution=0.2;
					body=m_world.CreateBody(bodyDef);
					body.CreateShape(boxDef);
				} else {
					circleDef = new b2CircleDef();
					circleDef.radius=rX;
					circleDef.density=1.0;
					circleDef.friction=0.5;
					circleDef.restitution=0.2;
					body=m_world.CreateBody(bodyDef);
					body.CreateShape(circleDef);
				}
				body.SetMassFromShapes();
			}
			stage.addEventListener(MouseEvent.MOUSE_DOWN, createMouse);
			stage.addEventListener(MouseEvent.MOUSE_UP, destroyMouse);
		}
		public function createMouse(evt:MouseEvent):void {
			var body:b2Body=GetBodyAtMouse();
			if (body) {
				var mouseJointDef:b2MouseJointDef=new b2MouseJointDef  ;
				mouseJointDef.body1=m_world.GetGroundBody();
				mouseJointDef.body2=body;
				mouseJointDef.target.Set(mouseX/30, mouseY/30);
				mouseJointDef.maxForce=30000;
				mouseJointDef.timeStep=m_timeStep;
				mouseJoint=m_world.CreateJoint(mouseJointDef) as b2MouseJoint;
			}
		}
		public function destroyMouse(evt:MouseEvent):void {
			if (mouseJoint) {
				m_world.DestroyJoint(mouseJoint);
				mouseJoint=null;
			}
		}
		private var mousePVec:b2Vec2 = new b2Vec2();
		public function GetBodyAtMouse(includeStatic:Boolean=false):b2Body {
			var mouseXWorldPhys = (mouseX)/30;
			var mouseYWorldPhys = (mouseY)/30;
			mousePVec.Set(mouseXWorldPhys, mouseYWorldPhys);
			var aabb:b2AABB = new b2AABB();
			aabb.lowerBound.Set(mouseXWorldPhys - 0.001, mouseYWorldPhys - 0.001);
			aabb.upperBound.Set(mouseXWorldPhys + 0.001, mouseYWorldPhys + 0.001);
			var k_maxCount:int=10;
			var shapes:Array = new Array();
			var count:int=m_world.Query(aabb,shapes,k_maxCount);
			var body:b2Body=null;
			for (var i:int = 0; i < count; ++i) {
				if (shapes[i].GetBody().IsStatic()==false||includeStatic) {
					var tShape:b2Shape=shapes[i] as b2Shape;
					var inside:Boolean=tShape.TestPoint(tShape.GetBody().GetXForm(),mousePVec);
					if (inside) {
						body=tShape.GetBody();
						break;
					}
				}
			}
			return body;
		}
		public function Update(e:Event):void {
			m_world.Step(m_timeStep, m_iterations);
			if (mouseJoint) {
				var mouseXWorldPhys=mouseX/30;
				var mouseYWorldPhys=mouseY/30;
				var p2:b2Vec2=new b2Vec2(mouseXWorldPhys,mouseYWorldPhys);
				mouseJoint.SetTarget(p2);
			}
		}
		public var m_world:b2World;
		public var m_iterations:int=10;
		public var m_timeStep:Number=1.0/30.0;
	}
}

So now we are going to look at lines 22-34

var m_sprite:Sprite;
m_sprite = new Sprite();
addChild(m_sprite);
var dbgDraw:b2DebugDraw = new b2DebugDraw();
var dbgSprite:Sprite = new Sprite();
m_sprite.addChild(dbgSprite);
dbgDraw.m_sprite=m_sprite;
dbgDraw.m_drawScale=30;
dbgDraw.m_alpha = 1;
dbgDraw.m_fillAlpha=0.5;
dbgDraw.m_lineThickness=1;
dbgDraw.m_drawFlags=b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit|b2DebugDraw.e_coreShapeBit|b2DebugDraw.e_aabbBit|b2DebugDraw.e_obbBit|b2DebugDraw.e_pairBit|b2DebugDraw.e_centerOfMassBit;
m_world.SetDebugDraw(dbgDraw);

Line 22: Declaring a new sprite called m_sprite

Line 23: Creating the sprite itself

Line 24: Adding the sprite to stage

Line 25: Declaring a new b2DebugDraw variable called dbgDraw

Line 26: Declaring and creating a new sprite called dbgSprite. This line acts like lines 22 and 23

Line 27: Adding this sprite to stage as a child of m_sprite

Line 28: Setting m_sprite as the sprite to use to render the world

You should not change anything until line 28

Now, it's time to look at the ways to custom the debug draw. We will custom it by changing its attributes.

m_sprite as said, defines the sprite to be used for rendering

m_drawScale (line 29) sets the drawing scale of the rendering. As seen in Understanding pixels and meters with Box2D and how to select an object with mouse - part 2, one meter is equal to 30 pixels, so setting m_drawscale to 30 allows me to use pixel units.

This is how my movie looks like if I don't set m_drawscale:

m_alpha (line 30) sets the alpha (from 0 to 1) for objects drawing color

m_fillAlpha (line 31) sets the alpha (from 0 to 1) for objects filling color

m_lineThickness (line 32) sets the thickness in pixels for objects drawing line

m_drawFlags (line 33) allows you to set some drawing flags. Before I explain every flag, let's see how our movieclip will look like if I don't set m_drawFlags

and now let's see the flags you can assign, separed by a pipe |:

e_shapeBit: draws shapes
e_jointBit: draws joint connections
e_coreShapeBit: draws core (TOI) shapes
e_aabbBit: draws axis aligned bounding boxes
e_obbBit: draws oriented bounding boxes
e_pairBit: draws broad-phase pairs
e_centerOfMassBit: draws center of mass frame

The only thing you cannot change from here is the color of the objects: by default, Box2D assigns a green to static objects, a red to sleeping ones and a grey for moving ones.

You can change the colors editing b2World.as at lines 1060-1074 (line numbers may vary according to Box2D distribution)

for (s = b.GetShapeList(); s; s = s.m_next)
{
	if (b.IsStatic())
	{
		DrawShape(s, xf, new b2Color(0.5, 0.9, 0.5), core);
	}
	else if (b.IsSleeping())
	{
		DrawShape(s, xf, new b2Color(0.5, 0.5, 0.9), core);
	}
	else
	{
		DrawShape(s, xf, new b2Color(0.9, 0.9, 0.9), core);
	}
}

You just have to change values inside b2Color with values from 0 to 1 where 1 is the 255 value we are used to deal with when working with colors.

We are almost ready to make our first Box2D game...

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

215 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stairs
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers