Understanding Box2D’s one-way platforms, aka CLOUDS
One of the new features introduced with Box2D 2.1a is the improved contact listener class which comes in hand when we want to create one-way platforms, or “clouds”.
This can be made thanks to a function called before the contact is processed… something like “hey, two bodies are about to collide, what should I do?”… so you can decide to disable the contact for every collision you want.
The function used to do this task is PreSolve, working for all awake bodies that aren’t sensors.
If you don’t know what is a Box2D sensor, check Box2D Flash game creation tutorial – part 2.
So the concept is: listen for collisions, if a collision involves the cloud wall and the player, then check if the player is higher or lower than the cloud. If it’s lower, don’t process the collision and let the player fly through the cloud.
Let’s see the script, directly taken from Box2D Flash game creation tutorial – part 2:
|
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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
package { import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import Box2D.Dynamics.*; import Box2D.Collision.*; import Box2D.Collision.Shapes.*; import Box2D.Common.Math.*; public class ball02 extends Sprite { // world creation public var world:b2World=new b2World(new b2Vec2(0,10.0),true); public var world_scale:int=30; // the player public var player:b2Body; // force to apply to the player public var force:b2Vec2; // variables to store whether the keys are pressed or not // true = pressed; // false = unpressed public var left,right,up,down:Boolean=false; // declaring my custom contact listener class public var contact_listener=new custom_contact_listener(); public function ball02():void { // assigning the contact listener to the world world.SetContactListener(contact_listener); // calling debug draw function debug_draw(); // drawing the boundaries draw_box(250,400,500,10,false,"ground"); draw_box(0,200,10,400,false,"left"); draw_box(500,200,10,400,false,"right"); draw_box(250,0,500,10,false,"roof"); draw_box(250,200,300,10,false,"middle"); // adding the player at 250,200 add_player(250,350); // adding some coins for (var i:int = 1; i<=5; i++) { draw_coin(Math.random()*400+50,Math.random()*150+25,Math.random()*3+2); } // listeners needed for the game to work addEventListener(Event.ENTER_FRAME, update); stage.addEventListener(KeyboardEvent.KEY_DOWN,on_key_down); stage.addEventListener(KeyboardEvent.KEY_UP,on_key_up); } // according to the key pressed, set the proper variable to "true" public function on_key_down(e:KeyboardEvent):void { switch (e.keyCode) { case 37 : left=true; break; case 38 : up=true; break; case 39 : right=true; break; case 40 : down=true; break; } } // according to the key released, set the proper variable to "false" public function on_key_up(e:KeyboardEvent):void { switch (e.keyCode) { case 37 : left=false; break; case 38 : up=false; break; case 39 : right=false; break; case 40 : down=false; break; } } // function to draw a coin public function draw_coin(px,py,r):void { var my_body:b2BodyDef= new b2BodyDef(); my_body.position.Set(px/world_scale, py/world_scale); var my_circle:b2CircleShape=new b2CircleShape(r/world_scale); var my_fixture:b2FixtureDef = new b2FixtureDef(); my_fixture.shape=my_circle; // look! it's a sensor!! my_fixture.isSensor=true; var world_body:b2Body=world.CreateBody(my_body); world_body.CreateFixture(my_fixture); } // simple function to draw a box public function draw_box(px,py,w,h,d,ud):void { var my_body:b2BodyDef= new b2BodyDef(); my_body.position.Set(px/world_scale, py/world_scale); if (d) { my_body.type=b2Body.b2_dynamicBody; } var my_box:b2PolygonShape = new b2PolygonShape(); my_box.SetAsBox(w/2/world_scale, h/2/world_scale); var my_fixture:b2FixtureDef = new b2FixtureDef(); my_fixture.shape=my_box; var world_body:b2Body=world.CreateBody(my_body); world_body.SetUserData(ud); world_body.CreateFixture(my_fixture); } // function to add the player public function add_player(px,py):void { var my_body:b2BodyDef= new b2BodyDef(); my_body.position.Set(px/world_scale, py/world_scale); my_body.type=b2Body.b2_dynamicBody; var my_circle:b2CircleShape=new b2CircleShape(10/world_scale); var my_fixture:b2FixtureDef = new b2FixtureDef(); my_fixture.shape=my_circle; player=world.CreateBody(my_body); player.SetUserData("player"); player.CreateFixture(my_fixture); } // debug draw public function debug_draw():void { var debug_draw:b2DebugDraw = new b2DebugDraw(); var debug_sprite:Sprite = new Sprite(); addChild(debug_sprite); debug_draw.SetSprite(debug_sprite); debug_draw.SetDrawScale(world_scale); debug_draw.SetFlags(b2DebugDraw.e_shapeBit); world.SetDebugDraw(debug_draw); } // function to be executed at every frame public function update(e:Event):void { // setting the force to null force=new b2Vec2(0,0); // according to the key(s) pressed, add the proper vector force if (left) { force.Add(new b2Vec2(-10,0)); } if (right) { force.Add(new b2Vec2(10,0)); } if (up) { force.Add(new b2Vec2(0,-20)); } if (down) { force.Add(new b2Vec2(0,5)); } // if there is any force, then apply it if (force.x||force.y) { player.ApplyForce(force,player.GetWorldCenter()); } world.Step(1/30,10,10); world.ClearForces(); // scanning through all bodies for (var worldbody:b2Body = world.GetBodyList(); worldbody; worldbody = worldbody.GetNext()) { // if a body is marked as "remove"... if (worldbody.GetUserData()=="remove") { // ... just remove it!! world.DestroyBody(worldbody); } } world.DrawDebugData(); } } } |
Line 34: with the old function introduced at Understanding Box2D applicable forces I create a box marked as “middle”.
No other changes on the main file, now let’s see the custom_contact_listener.as file
|
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 |
package { import Box2D.Dynamics.*; import Box2D.Collision.*; import Box2D.Collision.Shapes.*; import Box2D.Dynamics.Joints.*; import Box2D.Dynamics.Contacts.*; import Box2D.Common.*; import Box2D.Common.Math.*; class custom_contact_listener extends b2ContactListener { override public function BeginContact(contact:b2Contact):void { // getting the fixtures that collided var fixtureA:b2Fixture=contact.GetFixtureA(); var fixtureB:b2Fixture=contact.GetFixtureB(); // if the fixture is a sensor, mark the parent body to be removed if (fixtureB.IsSensor()) { fixtureB.GetBody().SetUserData("remove"); } if (fixtureA.IsSensor()) { fixtureA.GetBody().SetUserData("remove"); } } override public function PreSolve(contact:b2Contact, oldManifold:b2Manifold):void { // getting the fixtures that collided var fixtureA:b2Fixture=contact.GetFixtureA(); var fixtureB:b2Fixture=contact.GetFixtureB(); // variable to handle bodies y position var player_y_position:Number; var platform_y_position:Number; // checking if the collision bodies are the ones marked as "middle" and "player" if ((fixtureA.GetBody().GetUserData()=="middle" && fixtureB.GetBody().GetUserData()=="player")||(fixtureA.GetBody().GetUserData()=="player" && fixtureB.GetBody().GetUserData()=="middle")) { // determining if the fixtureA represents the platform ("middle") or the player switch (fixtureA.GetBody().GetUserData()) { case "middle" : // determining y positions player_y_position=fixtureB.GetBody().GetPosition().y*30; platform_y_position=fixtureA.GetBody().GetPosition().y*30; break; case "player" : // determining y positions player_y_position=fixtureA.GetBody().GetPosition().y*30; platform_y_position=fixtureB.GetBody().GetPosition().y*30; <strong> break; } // checking distance between bodies var distance = player_y_position-platform_y_position; // if the distance is greater than player radius + half of the platform height... if (distance>-14.5) { // don't manage the contact contact.SetEnabled(false); } } } } } |
Line 22: beginning of the PreSolve function, the core of this example
Lines 24-25: getting the fixtures that generated the contact
Lines 27-28: declaring two variables to store y position of both bodies
Line 30: here I am checking if the fixtures are the one associated to the player and the one associated to the cloud
Lines 32-43: according to the fixture associated to the player and the one associated to the cloud, I am saving in the variables declared at lines 27-28 the y position of both bodies. I am multiplying directly by 30 without passing the right world_scale value declared at line 13 of the main class because it’s not the purpose of this tutorial.
Line 45: Determining the vertical distance from the player and the cloud
Lines 47-50: If the player is not at least 14.5 pixels higher than the cloud, then disable the contact. Why 14.5? It’s the sum of the ball radius (10) and half the cloud height (5)… and I am not using 15 because I found sometimes the distance when the ball falls on the cloud is 14.93, so the cloud won’t “hold” the ball, letting it fall down. With 14.5, I am sure this won’t happen.
And this is the result…
Move the ball by tapping arrow keys and watch the static object in the center of the stage act as a cloud… you can fly through it from bottom to top, but you can’t do it from top to bottom.
They can be easily customized to meet the unique requirements of your project.





(12 votes, average: 4.92 out of 5)






This post has 10 comments
Monkios
This is really nice.
How tough would it be to add some bouncing on the ball ?
rishabh
@monkios
just add some restitution (0.3 for ex)
Quintus
Niceeeee :)
Quintus
Greetings!
I am trying to make a box2d game with this tutorial. It’s a sort of biljart game.
I don’t know when 2 biljart balls hittest each other. So my question is: how can i declare if a ball hits another ball?
-Quintus
encoder
lol
@1. Monkios
it is something like writing 6 characters.
Emanuele Feronato
if you read yr comments pls read this post:
http://szeredai.wordpress.com/2010/03/22/from-design-to-physix-enabled-object-under-5-minutes/
wanted to ask you something a long time ago:
why are you throwing all yr code in a single class? i know it’s only 200 lines but still..
kinda started my blog. it is for putting things out there and i tend not to advertise with it ;)
artyangst
Great tutorial, but my question is a little more advanced. This only works on horizontal platforms. For something like a slanted platform, the player could still have a center of mass above the platform, yet actually be “below” the platform. So I could figure this out if I knew the point of contact in worldspace. Any tips on how to figure this out? :)
Heorhiy
2artyangst
Hi! have figured it out?
shakespeare
instead of using the distance calculation, you could check the velocity of the character you are moving, and if it is moving “upward”, you could set the contact enabled to false.
Brad Henderson
YOU ROCK!!! It took me only like 30 mins to get his implemented and I just started using BoX2d yesterday. Many many many THANKS!!!
Bananni
your “SUPPORTED BY” section loaded a side that sends viruses, better take a look on it ;)
opbvhdjehh(dot)biz/index.php?tp=89c9473171a1c848
was it