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:

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
163
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.

Download the source code.

Rate this post: 1 Star2 Stars3 Stars4 Stars5 Stars (6 votes, average: 5.00 out of 5)
Loading ... Loading ...
If you found this post useful, please consider a small donation.
» Flash Templates provided by Template Monster are pre-made web design products developed using Flash technology.
They can be easily customized to meet the unique requirements of your project.

6 Responses

  1. Monkios says:

    This is really nice.

    How tough would it be to add some bouncing on the ball ?

  2. rishabh says:

    @monkios
    just add some restitution (0.3 for ex)

  3. Quintus says:

    Niceeeee :)

  4. Quintus says:

    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

  5. encoder says:

    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 ;)

  6. artyangst says:

    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? :)

Leave a Reply