Understanding how Box2D manages collisions

Box2D is a great library because you don’t have to care for collisions, the library manages all by itself.

But sometimes, specially in games, you may need to know when the player hits the coin, or the enemy, or whatever.

Box2D has a class called b2ContactListener that you have to rewrite to make it work as you want.

Let’s take the old crate test example explained at Dragging objects with Box2D Flash, and rewrite the code this way:

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	import Box2D.Dynamics.Joints.*;
	public class cratetest extends Sprite {
		public var m_world:b2World;
		public var m_iterations:int = 10;
		public var m_timeStep:Number = 1/30;
		public var mousePVec:b2Vec2 = new b2Vec2();
		public var real_x_mouse:Number;
		public var real_y_mouse:Number;
		public var pixels_in_a_meter = 30;
		public var worldAABB:b2AABB = new b2AABB();
		public var gravity:b2Vec2 = new b2Vec2(0.0, 10.0);
		public var mouseJoint:b2MouseJoint;
		public function cratetest() {
			addEventListener(Event.ENTER_FRAME, Update, false, 0, true);
			stage.addEventListener(MouseEvent.MOUSE_DOWN, on_mouse_down);
			stage.addEventListener(MouseEvent.MOUSE_UP, on_mouse_up);
			worldAABB.lowerBound.Set(-100.0, -100.0);
			worldAABB.upperBound.Set(100.0, 100.0);
			m_world = new b2World(worldAABB, gravity, true);
			var m_contactListener=new b2ContactListener();
			m_world.SetContactListener(m_contactListener);
			var body:b2Body;
			var bodyDef:b2BodyDef;
			var boxDef:b2PolygonDef;
			bodyDef = new b2BodyDef();
			bodyDef.position.Set(8.5, 13.5);
			boxDef = new b2PolygonDef();
			var ground_width = 8.5;
			var ground_height = 0.5;
			boxDef.SetAsBox(ground_width, ground_height);
			boxDef.friction = 0.3;
			boxDef.density = 0;
			bodyDef.userData = new floor();
			bodyDef.userData.width = ground_width * 2 * pixels_in_a_meter;
			bodyDef.userData.height = ground_height * 2 * pixels_in_a_meter;
			bodyDef.userData.name = "Floor";
			addChild(bodyDef.userData);
			body = m_world.CreateBody(bodyDef);
			body.CreateShape(boxDef);
			body.SetMassFromShapes();
			for (var i:int = 1; i <=5; i++) {
				bodyDef = new b2BodyDef();
				bodyDef.position.x = Math.random() * 15 + 1;
				bodyDef.position.y = Math.random();
				var crate_width:Number = 1;
				var crate_height:Number = 1;
				boxDef = new b2PolygonDef();
				boxDef.SetAsBox(crate_width, crate_height);
				boxDef.density = 1.0;
				boxDef.friction = 0.5;
				boxDef.restitution = 0.2;
				bodyDef.userData = new crate();
				bodyDef.userData.width = crate_width * 2 * pixels_in_a_meter;
				bodyDef.userData.height = crate_height * 2* pixels_in_a_meter;
				bodyDef.userData.name = "Crate "+i;
				body = m_world.CreateBody(bodyDef);
				body.CreateShape(boxDef);
				body.SetMassFromShapes();
				addChild(bodyDef.userData);
			}
		}
		public function Update(e:Event):void {
			m_world.Step(m_timeStep, m_iterations);
			if (mouseJoint) {
				var mouseXWorldPhys = mouseX/pixels_in_a_meter;
				var mouseYWorldPhys = mouseY/pixels_in_a_meter;
				var p2:b2Vec2 = new b2Vec2(mouseXWorldPhys, mouseYWorldPhys);
				mouseJoint.SetTarget(p2);
			}
			for (var bb:b2Body = m_world.m_bodyList; bb; bb = bb.m_next) {
				if (bb.m_userData is Sprite) {
					bb.m_userData.x = bb.GetPosition().x * pixels_in_a_meter;
					bb.m_userData.y = bb.GetPosition().y * pixels_in_a_meter;
					bb.m_userData.rotation = bb.GetAngle() * (180/Math.PI);
				}
			}
		}
		public function on_mouse_down(evt:MouseEvent):void {
			var body:b2Body = GetBodyAtMouse();
			if (body) {
				var mouse_joint:b2MouseJointDef = new b2MouseJointDef;
				mouse_joint.body1 = m_world.GetGroundBody();
				mouse_joint.body2 = body;
				mouse_joint.target.Set(mouseX/pixels_in_a_meter, mouseY/pixels_in_a_meter);
				mouse_joint.maxForce = 10000;
				mouse_joint.timeStep = m_timeStep;
				mouseJoint = m_world.CreateJoint(mouse_joint) as b2MouseJoint;
			}
		}
		public function on_mouse_up(evt:MouseEvent):void {
			if (mouseJoint) {
				m_world.DestroyJoint(mouseJoint);
				mouseJoint = null;
			}
		}
		public function GetBodyAtMouse(includeStatic:Boolean=false):b2Body {
			real_x_mouse = (stage.mouseX)/pixels_in_a_meter;
			real_y_mouse = (stage.mouseY)/pixels_in_a_meter;
			mousePVec.Set(real_x_mouse, real_y_mouse);
			var aabb:b2AABB = new b2AABB();
			aabb.lowerBound.Set(real_x_mouse - 0.001, real_y_mouse - 0.001);
			aabb.upperBound.Set(real_x_mouse + 0.001, real_y_mouse + 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].m_body.IsStatic() == false || includeStatic) {
					var tShape:b2Shape = shapes[i] as b2Shape;
					var inside:Boolean = tShape.TestPoint(tShape.m_body.GetXForm(), mousePVec);
					if (inside) {
						body = tShape.m_body;
						break;
					}
				}
			}
			return body;
		}
	}
}

Apparently it's the same, but let's take a look to new lines:

Lines 28-29: Adding the built-in contact listener to the world.

Line 44: Giving the floor object a name. Naming our objects will make it easier to determine which ones are colliding.

Line 63: Same thing for the crates.

And that's all.

Now, let's see how to rewrite the b2ContactListener class.

You can find the b2ContactListener.as file in Box2D -> Dynamics folder, and you must rewrite it this way:

/*
* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com
*
* This software is provided 'as-is', without any express or implied
* warranty.  In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/

package Box2D.Dynamics{


	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Dynamics.Contacts.*;
	import Box2D.Dynamics.*;
	import Box2D.Common.Math.*;
	import Box2D.Common.*;


	/// Implement this class to get collision results. You can use these results for
	/// things like sounds and game logic. You can also get contact results by
	/// traversing the contact lists after the time step. However, you might miss
	/// some contacts because continuous physics leads to sub-stepping.
	/// Additionally you may receive multiple callbacks for the same contact in a
	/// single time step.
	/// You should strive to make your callbacks efficient because there may be
	/// many callbacks per time step.
	/// @warning The contact separation is the last computed value.
	/// @warning You cannot create/destroy Box2D entities inside these callbacks.
	public class b2ContactListener {

		/// Called when a contact point is added. This includes the geometry
		/// and the forces.
		public virtual function Add(point:b2ContactPoint):void {
			trace("Collision between "+point.shape1.GetBody().GetUserData().name+" and "+point.shape2.GetBody().GetUserData().name);
			point.shape1.GetBody().GetUserData().alpha=0.5
			point.shape2.GetBody().GetUserData().alpha=0.5
		}

		/// Called when a contact point persists. This includes the geometry
		/// and the forces.
		public virtual function Persist(point:b2ContactPoint):void {
			point.shape1.GetBody().GetUserData().alpha=0.5
			point.shape2.GetBody().GetUserData().alpha=0.5
		}

		/// Called when a contact point is removed. This includes the last
		/// computed geometry and forces.
		public virtual function Remove(point:b2ContactPoint):void {
			point.shape1.GetBody().GetUserData().alpha=1
			point.shape2.GetBody().GetUserData().alpha=1
		}

		/// Called after a contact point is solved.
		public virtual function Result(point:b2ContactResult):void {
		}
	}

}

Let's see the lines I added:

Lines 45-47: When a new contact point is added, I trace "Collision between [object_name] and [object name]", then I set the alpha of both objects to 0.5, making them semi-transparent.

Lines 53-54: As long as a contact point persists, I keep the alpha to 0.5

Lines 60-61: Finally, when a contact point is removed, I set the objects alpha to 1

And this is the result... objects colliding with other ones are semi-transparent, while objects that aren't colliding with anything have full opacity.

Try to drag crates to see how does it work

Download the source code and enjoy.

In the zip file you will find the entire Box2D library... just to make it easier for you to find the b2ContactListener.as file

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