Understanding how Box2D manages collisions
Filed Under Actionscript 3, Box2D, Flash, Game design • 7 Comments
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | /* * 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
They can be easily customized to meet the unique requirements of your project.
7 Responses to “Understanding how Box2D manages collisions”
Leave a Reply
Trackbacks
-
Creation of a Flash Stabilize! clone using Box2D – part 4 : Emanuele Feronato on
November 23rd, 2009 8:28 pm
[...] already showed you how Box2D manages collisions but I made it directly editing the built in b2ContactListener.as [...]
- Get up to $100,000 for your next Flash game with Mochi GAME Developer Fund
- Create a dynamic content animated footer ad for your site in just 9 jQuery lines – 17 lines version
- Sell sitelocked version of your Flash games and even .fla sources to Free Online Games
- Protect your work from ActionScript code theft with SWF Protector
- Create a dynamic content animated footer ad for your site in just 9 jQuery lines
- Understanding Box2D’s one-way platforms, aka CLOUDS
- Triqui MochiAds Arcade plugin for WordPress upgraded to 1.2
- Box2D Flash game creation tutorial – part 2
- 11 Flash isometric engines you can use in your games
- Monetize your Flash games with GamesChart
- Create a Lightbox effect only with CSS - no javascript needed
- Flash game creation tutorial - part 1
- Create a Flash Racing Game Tutorial
- Flash game creation tutorial - part 2
- Make a Flash game like Flash Element Tower Defense - Part 2
- Flash game creation tutorial - part 3
- Make a Flash game like Flash Element Tower Defense - Part 1
- Create a flash draw game like Line Rider or others - part 1
- Triqui MochiAds Arcade plugin for WordPress official page
- Create a flash artillery game - step 1
- Flash game creation tutorial – part 5.2 (4.87/5)
- Create a flash artillery game – step 1 (4.79/5)
- Create a Flash Racing Game Tutorial (4.76/5)
- Create a flash artillery game – step 2 (4.74/5)
- Create a survival horror game in Flash tutorial – part 1 (4.73/5)
- Creation of a Flash arcade site using WordPress – step 2 (4.73/5)
- Flash game creation tutorial – part 2 (4.71/5)
- Flash game creation tutorial – part 1 (4.70/5)
- Create a flash draw game like Line Rider or others – part 1 (4.69/5)
- Creation of a platform game with Flash – step 2 (4.68/5)

(14 votes, average: 4.50 out of 5)





Wanted to take a moment to thank you for your continuing box2d tutorials. Seems like right around the time I reach a question, you post a tutorial about how to deal with it.
Keep up the great work and I love the blog!
Thanks your great work. I am very happy that your
Box2D tutorial updates quickly. I am reading itO(∩_∩)O
Really nice tutorial. The collisions were one of the things I found difficult to understand about Box2DFlash. That’s why I did a fast game clone with all the source code available, but it’s not as incremental to learn as your tutorials, but I think it can be useful.
The job you are doing for the Flash game community is amazing, keep up the awesome work!
Just a small comment on the way you have directly edited the b2ContactListener file.
The file has set these up as virtual functions so you should write your own listener and extend b2ContactListener. then you write the functions as:
public override function Add(){}
That way you can keep your Box2D folder nice and clean.
Thanks for the great tutorials, they’ve been a great help.
: D
Hey really cool tutorial Thanks.
Although, I have a question:
I would like to fire my personal function in the main class file (“myFunction()”) directly from:
b2ContactListener.as
I tried many statements, but I can’t link to my main class file “mike.as”
Your help would be much appreciated.
Cheers!
Mike
As Danny Parker mentioned, you shouldn’t modify the b2ContactListener.as file directly. Just create a new class that inherits from b2ContactListener, create an instance of that, then pass it to b2World.SetContactListener().