Box2D 2.1a Tutorial – Part 4 (Collision Detection)

Up until this point we have learnt how to create a Box2D world, add bodies, add joints to those bodies, and to texture them. While Box2D handles all the collision detection and resolution of the physics, it would also be useful for us to be able to determine when and what objects collide. So this will be what we will learn today.

In this tutorial we will create a simple Box2D world and populate it with a red and blue ball that drops down from the sky. When each ball collides with the ground we will reposition them at their original location from which they fell.

Once again we will extend MouseJointTutorial to save us from coding from scratch.

To be able to detect when collisions happen requires use of a b2ContactListener. The b2ContactListener can be considered an abstract class (even though AS3 doesn’t actually support them) as we do not instantiate it directly but have to extend it first. It would probably be a good idea to have a look at the b2ContactListener class yourself. You will observe the following functions that we can override.

public virtual function BeginContact(contact:b2Contact):void { }
public virtual function EndContact(contact:b2Contact):void { }
public virtual function PreSolve(contact:b2Contact, oldManifold:b2Manifold):void {}
public virtual function PostSolve(contact:b2Contact, impulse:b2ContactImpulse):void { }

We will only be overriding BeginContact today since that is all we need for determining when the balls hit the ground. So lets get started.
First, we will create the b2Body balls and textures. You should know this process pretty well by now so I won’t go into lengths to explain it.

package
{
	import flash.display.MovieClip;
	import flash.events.Event;
	import General.Input;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.b2FixtureDef;
	import Box2D.Collision.Shapes.b2CircleShape;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;

	public class CollisionDetectionTutorial extends MouseJointTutorial
	{
		public const RADIANS_TO_DEGREES:Number = 57.2957795;
		public const DEGREES_TO_RADIANS:Number = 0.0174532925;
		private var _blueBall:b2Body;
		private var _redBall:b2Body;
		private var _blueBallTexture:MovieClip;
		private var _redBallTexture:MovieClip;

		override protected function setup():void
		{
			_blueBall = createBall(300, 200, 50);
			_redBall = createBall(500, 300, 50);

			_redBallTexture = new RedBallTexture();
			addChild(_redBallTexture);
			_blueBallTexture = new BlueBallTexture();
			addChild(_blueBallTexture);
		}

		protected function createBall(x:Number, y:Number, radius:Number):b2Body
		{
			var robotBody:b2BodyDef = new b2BodyDef();
			robotBody.type = b2Body.b2_dynamicBody;
			robotBody.fixedRotation = true;
			robotBody.position.Set(x / PIXELS_TO_METRE, y / PIXELS_TO_METRE);
			var body:b2Body = _world.CreateBody(robotBody);

			var robotBodyDef:b2CircleShape = new b2CircleShape();
			robotBodyDef.SetRadius(radius / PIXELS_TO_METRE);

			var robotBodyFixtureDef:b2FixtureDef = new b2FixtureDef();
			robotBodyFixtureDef.shape = robotBodyDef;
			robotBodyFixtureDef.restitution = 0.7;
			robotBodyFixtureDef.friction = 0.5;

			body.CreateFixture(robotBodyFixtureDef);
			return body;
		}

		override protected function update(e:Event):void
		{
			var timeStep:Number = 1 / 60;
			var velocityIterations:int = 6;
			var positionIterations:int = 2;

			UpdateMouseWorld();
			MouseDestroy();
			MouseDrag();

			_world.Step(timeStep, velocityIterations, positionIterations);
			_world.ClearForces();
			updateTextures();
			General.Input.update();
		}

		private function updateTextures():void
		{
			_redBallTexture.x = _redBall.GetPosition().x * PIXELS_TO_METRE;
			_redBallTexture.y = _redBall.GetPosition().y * PIXELS_TO_METRE;
			_redBallTexture.rotation = _redBall.GetAngle() * RADIANS_TO_DEGREES;

			_blueBallTexture.x = _blueBall.GetPosition().x * PIXELS_TO_METRE;
			_blueBallTexture.y = _blueBall.GetPosition().y * PIXELS_TO_METRE;
			_blueBallTexture.rotation = _blueBall.GetAngle() * RADIANS_TO_DEGREES;
		}
	}
}

Now we can get to the actual collision detection code. Create a class and call it BallContactListener, have it extend b2ContactListener and override the BeginContact function .

package
{
	import Box2D.Dynamics.Contacts.b2Contact;
	import Box2D.Dynamics.b2ContactListener;

	public class BallContactListener extends b2ContactListener
	{
		public function BallContactListener()
		{
		}

		override public function BeginContact(contact:b2Contact):void
		{
		}
	}
}

In our setup function we will create an instance of this BallContactListener and pass it to our world via the SetContactListener function.

		override protected function setup():void
		{
			var ballContactListener = new BallContactListener();
			_world.SetContactListener(ballContactListener);

			_blueBall = createBall(300, 200, 50);
			_redBall = createBall(500, 300, 50);

			_redBallTexture = new RedBallTexture();
			addChild(_redBallTexture);
			_blueBallTexture = new BlueBallTexture();
			addChild(_blueBallTexture);
		}

At the moment our BeginContact function in the BallContactListener is empty. We know that it takes in a b2Contact as a parameter and we can take at a guess that it will supply us with the information we need to determine who collided with who.

So lets look what is available. We can see that we can call GetFixtureA and GetFixtureB to return a b2Fixture. From those fixtures we can retrieve a b2Body. The problem is, while we have the b2Bodies that have collided, how do we determine which bodies they correspond with? The answer is at the moment we can’t. Fortunately b2Bodies have two functions that will solve this problem for us, those being GetUserData and SetUserData which allow us to set any data we want as a property. We can leverage this to pass in a string to the body. So in our setup function lets assign the names to the relevant bodies. For best practise I am going to make a class called BodyType and have it contain public static const strings of the names.

		override protected function setup():void
		{
			_world.SetContactListener(ballContactListener);
			_groundBody.SetUserData(BodyType.GROUND);

			_blueBall = createBall(300, 200, 50);
			_blueBall.SetUserData(BodyType.BLUE_BALL);
			_redBall = createBall(500, 300, 50);
			_redBall.SetUserData(BodyType.RED_BALL);

			_redBallTexture = new RedBallTexture();
			addChild(_redBallTexture);
			_blueBallTexture = new BlueBallTexture();
			addChild(_blueBallTexture);
		}

We can now go back into your BallContactListener since we have all the data we need. The one remaining question unanswered is which body will be in which fixture, fixture A or B? We can’t really be sure, so we have to test for both cases.

		override public function BeginContact(contact:b2Contact):void
		{
			if((contact.GetFixtureA().GetBody().GetUserData() == BodyType.BLUE_BALL && contact.GetFixtureB().GetBody().GetUserData() == BodyType.GROUND)
			||(contact.GetFixtureA().GetBody().GetUserData() == BodyType.GROUND && contact.GetFixtureB().GetBody().GetUserData() == BodyType.BLUE_BALL))
			{

			}

			if((contact.GetFixtureA().GetBody().GetUserData() == BodyType.RED_BALL && contact.GetFixtureB().GetBody().GetUserData() == BodyType.GROUND)
			|| (contact.GetFixtureA().GetBody().GetUserData() == BodyType.GROUND && contact.GetFixtureB().GetBody().GetUserData() == BodyType.RED_BALL))
			{

			}
		}

Now that we have the logic in our BeginContact function for determining if a ball collides with the ground we need a way of handling this in our main class. For now we will just use a event dispatcher.

package
{
	import flash.events.Event;
	import Box2D.Dynamics.Contacts.b2Contact;
	import flash.events.EventDispatcher;
	import Box2D.Dynamics.b2ContactListener;

	public class BallContactListener extends b2ContactListener
	{
		public static const BLUE_BALL_START_CONTACT:String = "blueBallStartContact";
		public static const RED_BALL_START_CONTACT:String = "redBallStartContact";
		public var eventDispatcher:EventDispatcher;

		public function BallContactListener()
		{
			eventDispatcher = new EventDispatcher();
		}

		override public function BeginContact(contact:b2Contact):void
		{
			if((contact.GetFixtureA().GetBody().GetUserData() == BodyType.BLUE_BALL && contact.GetFixtureB().GetBody().GetUserData() == BodyType.GROUND)
			||(contact.GetFixtureA().GetBody().GetUserData() == BodyType.GROUND && contact.GetFixtureB().GetBody().GetUserData() == BodyType.BLUE_BALL))
			{
				eventDispatcher.dispatchEvent(new Event(BLUE_BALL_START_CONTACT));
			}

			if((contact.GetFixtureA().GetBody().GetUserData() == BodyType.RED_BALL && contact.GetFixtureB().GetBody().GetUserData() == BodyType.GROUND)
			|| (contact.GetFixtureA().GetBody().GetUserData() == BodyType.GROUND && contact.GetFixtureB().GetBody().GetUserData() == BodyType.RED_BALL))
			{
				eventDispatcher.dispatchEvent(new Event(RED_BALL_START_CONTACT));
			}
		}
	}
}

After adding the listeners in the CollisionDetectionTutorial class it would be fair to assume that we could add in the code for repositioning the bodies in them. However, if you try to do that you will see that this does not work the way intended. The problem is when the bContactListener is called, it is happening at some stage during which all the physics calculations are happening and so modifying the Box2D world causes problems. We need to wait until it is safe to update items in the world. A good place that is safe is in the update function. So our listeners instead will set a Boolean flag so that in our update function can check those values and then know what to do.

package
{
	import flash.display.MovieClip;
	import flash.events.Event;
	import General.Input;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.b2FixtureDef;
	import Box2D.Collision.Shapes.b2CircleShape;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;

	public class CollisionDetectionTutorial extends MouseJointTutorial
	{
		public const RADIANS_TO_DEGREES:Number = 57.2957795;
		public const DEGREES_TO_RADIANS:Number = 0.0174532925;
		private var _blueBallContact:Boolean = false;
		private var _redBallContact:Boolean = false;
		private var _blueBall:b2Body;
		private var _redBall:b2Body;
		private var _blueBallTexture:MovieClip;
		private var _redBallTexture:MovieClip;

		private function onRedBallStartContact(e:Event):void
		{
			_redBallContact = true;
		}

		private function onBlueBallStartContact(e:Event):void
		{
			_blueBallContact = true;
		}

		override protected function setup():void
		{
			var ballContactListener = new BallContactListener();
			ballContactListener.eventDispatcher.addEventListener(BallContactListener.BLUE_BALL_START_CONTACT, onBlueBallStartContact);
			ballContactListener.eventDispatcher.addEventListener(BallContactListener.RED_BALL_START_CONTACT, onRedBallStartContact);
			_world.SetContactListener(ballContactListener);
			_groundBody.SetUserData(BodyType.GROUND);

			_blueBall = createBall(300, 200, 50);
			_blueBall.SetUserData(BodyType.BLUE_BALL);
			_redBall = createBall(500, 300, 50);
			_redBall.SetUserData(BodyType.RED_BALL);

			_redBallTexture = new RedBallTexture();
			addChild(_redBallTexture);
			_blueBallTexture = new BlueBallTexture();
			addChild(_blueBallTexture);
		}

		protected function createBall(x:Number, y:Number, radius:Number):b2Body
		{
			var robotBody:b2BodyDef = new b2BodyDef();
			robotBody.type = b2Body.b2_dynamicBody;
			robotBody.fixedRotation = true;
			robotBody.position.Set(x / PIXELS_TO_METRE, y / PIXELS_TO_METRE);
			var body:b2Body = _world.CreateBody(robotBody);

			var robotBodyDef:b2CircleShape = new b2CircleShape();
			robotBodyDef.SetRadius(radius / PIXELS_TO_METRE);

			var robotBodyFixtureDef:b2FixtureDef = new b2FixtureDef();
			robotBodyFixtureDef.shape = robotBodyDef;
			robotBodyFixtureDef.restitution = 0.7;
			robotBodyFixtureDef.friction = 0.5;

			body.CreateFixture(robotBodyFixtureDef);
			return body;
		}

		override protected function update(e:Event):void
		{
			var timeStep:Number = 1 / 60;
			var velocityIterations:int = 6;
			var positionIterations:int = 2;

			UpdateMouseWorld();
			MouseDestroy();
			MouseDrag();

			_world.Step(timeStep, velocityIterations, positionIterations);
			_world.ClearForces();
			updateTextures();
			General.Input.update();
			checkCollisions();
		}

		private function updateTextures():void
		{
			_redBallTexture.x = _redBall.GetPosition().x * PIXELS_TO_METRE;
			_redBallTexture.y = _redBall.GetPosition().y * PIXELS_TO_METRE;
			_redBallTexture.rotation = _redBall.GetAngle() * RADIANS_TO_DEGREES;

			_blueBallTexture.x = _blueBall.GetPosition().x * PIXELS_TO_METRE;
			_blueBallTexture.y = _blueBall.GetPosition().y * PIXELS_TO_METRE;
			_blueBallTexture.rotation = _blueBall.GetAngle() * RADIANS_TO_DEGREES;
		}

		private function checkCollisions():void
		{
			if(_blueBallContact)
			{
				_blueBall.SetPosition(new b2Vec2(300 / PIXELS_TO_METRE, 200 / PIXELS_TO_METRE));
				_blueBall.SetLinearVelocity(new b2Vec2(0, 0));
				_blueBallContact = false;
			}

			if(_redBallContact)
			{
				_redBall.SetPosition(new b2Vec2(500 / PIXELS_TO_METRE, 300 / PIXELS_TO_METRE));
				_redBall.SetLinearVelocity(new b2Vec2(0, 0));
				_redBallContact = false;
			}
		}
	}
}

EDIT: I would like to thank SticksStones for going to the effort to improve this code. You can find his version here. Please note that these Box2D tutorials are aimed at teaching the Box2D concepts and are in no way meant to be an indication of how you should structure your code unless otherwise stated. Quite often I will write a tutorial to minimize the amount of new/foreign code compared to the previous one. Of course this can come at the expense of code quality so please do not directly implement the code without thinking about how you can design it better! :)

That is it. This wraps up the basics of Box2D.

  • Cjboy1984

    Hello!
    Have you ever used this framework?
    http://www.sideroller.com/wck/

  • http://twitter.com/AllanBishop Allan Bishop

    Not as yet, although it looks like an attractive option for creating Box2D worlds. The ability to layout items via the FlashIDE could well improve productivity. I decided not learning that way just so that I was not reliant on the FlashIDE for development and to gain a good grasp of exactly what Box2D was doing.

  • Ivan

    Hi! I have problem here:

    import General.Input;

    I haven’t such class… Can anybody help me?

  • John

    Can you tell me how to work out the exact point of the collision please? I can’t figure it out. I can find the location of the two bodies involved in the collision, but I’d like to know the location where they have hit, so that I can toss in some particles at that point so it looks like the two objects collided with a little “bang”!

  • http://twitter.com/AllanBishop Allan Bishop

    Sure thing. I might update this tutorial with that info as it is useful, but in the meantime here is the answer. We make use of the b2Manifold for finding out the collision points. The following code goes in the contact listener function:
    ————————————————————————-

    var worldManifold:b2WorldManifold = new b2WorldManifold(); //create a new instance
    contact.GetWorldManifold(worldManifold); //the contact is the parameter of the function of course

    var points:Vector. = worldManifold.m_points;

    for each(var p:b2vec2 in points)
    {
    trace(“x”,p.x*PIXELS_TO_METRE,”y”,p.y*PIXELS_TO_METRE);
    }
    ————————————————————————

    Sorry, I can’t get rid of this so please just ignore it –>

  • http://twitter.com/AllanBishop Allan Bishop

    When you extract the zip of the Box2D package you should have 4 folders and a readme. Under the examples folder is the General package that contains the Input. Technically they are not part of Box2D engine but are useful and so are included.

    Also, for the tutorials I have written I modified the Input class a bit to make everything a bit more self contained (so far so good but if you run into troubles bear that in mind). You can find the modified file here http://blog.allanbishop.com/wp-content/uploads/2010/09/Input.as

  • Pingback: Box2D 2.1 Collision Point Demo/ Tutorial | Allan Bishop's Developer Blog

  • SticksStones

    Hi Allan,
    I thought I should let you know that I’ve rewritten the code for tutorial without event dispatcher and made it easier to add as many balls as you like, in response to a question posted on the box2D forum’s.

    box2d.org/forum/viewtopic.php?f=8&p=27546#p27546

    I hope that’s ok. As far as I’m concerned much the code is yours, so you can do whatever you want with it.

    …I thought I should let you know and maybe you’ll have a use for it!

  • http://twitter.com/AllanBishop Allan Bishop

    That is fine =). Yes, setting a collide flag on the object directly, rather than dispatching and listening for an event is a much better solution. Thanks for fixing it!

    At the time I knew my approach was ugly (a public eventdispatcher variable – yuck!) but my aim is to help demonstrate the concepts of Box2D rather than (and in some cases, at the expense of) code structure.

    In saying so, perhaps people might take my code as gospel and copy it exactly, rather than taking the concepts and ideas to write their own code. So I think I need to address that. I might update this tutorial to mention that and have a link to the zip of your code to show how it can be improved on.

    It might also be a good reason to write a tutorial (or even series) that explores ways of structuring a game (with Box2D in mind), with good practices and taking into account some of the more popular game design patterns.

    Anyway thanks again :)

  • SticksStones

    I’d love a tutorial on finding out the strength of collisions, for applying damage to objects and playing sounds. I’m really struggling to find anything on it!

    Glad you like the code!

  • http://twitter.com/AllanBishop Allan Bishop

    ah sounds like a challenge :) I will see what I can come up with.

  • Mj_azani

    great post

  • splashdoodle

    Hi,
    Thanks for the codes.It is something im looking for.
    I would like to ask about using Bodytype as class to contain the string of names. Is there other way to do it?
    Thanks

  • Pingback: Richelle

  • Pingback: Chantelle

  • Pingback: sander

  • Pingback: Vehmer

  • Pingback: TadWinett

  • Pingback: Chantelle

  • Pingback: venzingS

  • Pingback: Erederic

  • Pingback: Ehantelle

  • Pingback: kander

  • Pingback: Xehmer

  • your Blog is great

    i was puzzled about the virtual so i looked it up
    and learned that the “virtual” keyword does absolutly nothing in as3
    as3 reserve this word for an optional disigned of itself in the future
    i dont know where u took it from but virtual in c++ means that the function designed to be overriden in the future in java all function is virtual so there is no such keyword
    since as3 is based on java all function is virtual here as well

  • allanbishop

    Yup that is correct, the virtual keyword is just a place holder. BorisTheBrave who ported Box2D to Flash is the author of that and not me. I guess because Box2D started of as C++ he just ported it to look like the original even if the virtual keyword is pointless.