Box2D platform engine alternative

Everybody should know Box2D is the best engine you can use to make a platform game.

Rick Triqui, my latest game, was made with PlayCrafter that uses Box2D, and I’m starting to code my own Box2D platform engine.

Luis Fernando Silva, author of the upcoming Darkness game (Sponsors! Contact him to see the work in progress! Awesome idea!) wanted to share with us his Box2D platform engine, that made from scratch.

The source code is fully commented, for our pleasure

This is the main 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
package {
	import flash.display.MovieClip;
 
	// Import the levels from the 'Levels' folder:
	import Levels.*;
 
	public class Main extends MovieClip {
		// The game level, re-instanciate this object to
		// switch the levels
		public var level:GameLevel;
 
		// Keyboard listener, got the script at Emanuele's websote
		public var keyboard:keys = new keys(stage);
 
		// Constructor, here these 3 simple lines of code create the level
		// and prepare it to be player
		public function Main() {
			level = new Level1(keyboard); // Create a new level, Level1, in this case
			addChild(level); // Add it to the display object
 
			addEventListener("enterFrame", Update, false, 0, true); // Add a loop
		}
 
		// Update the game here using GameLevel.Update so you can do
		// some general stuff on a main loop instead of using the GameLevel.
		public function Update(e:*) : void {
			level.Update();
		}
	}
}

At line 5 the script imports levels (just one in this example) coded in 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
package Levels {
 
	public class Level1 extends GameLevel {
 
		public function Level1(k:keys) {
			//// Always remember to call the superior constructor first!
			super(k);
 
			//// The player:
			createPlayer(250, 50);
 
			//// The moveable blocks:
			addBox(110, 110, 50, 50);
			addBox(190, 200, 30, 50);
			addBox(100, 50, 120, 20);
			// Small see-saw:
			addBox(400, 250, 30, 30);
			addBox(400, 220, 200, 10);
			addBox(480, 200, 20, 20);
 
			//// The ground block:
			addStaticBox(550/2, 300, 550, 20);
		}
	}
}

At line 10 the script calls the most important class, GameLevel, that’s made 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
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
package {
	import flash.display.Sprite;
 
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
 
	// Main Game Level class definition
	public class GameLevel extends Sprite {
		// Player:
		public var playerBody:b2Body;		
		public var player:Player;
		// Jump trigger:
		public var upDown:Boolean = false;
 
		// Misc:
		public var lastBody:b2Body;
 
		// Vars used to create bodies
		public var body:b2Body;
		public var bodyDef:b2BodyDef;
		public var boxDef:b2PolygonDef;
		public var circleDef:b2CircleDef;
 
		// Keyboard listener:
		public var keyboard:keys;
 
		public function GameLevel(k:keys) {
			keyboard = k;
 
			// World Setup:
			var worldAABB:b2AABB = new b2AABB();
			worldAABB.lowerBound.Set(-100.0, -100.0);
			worldAABB.upperBound.Set(100.0, 100.0);
 
			// Define the gravity vector
			var gravity:b2Vec2 = new b2Vec2(0.0, 10.0);
 
 
			// Allow bodies to sleep
			var doSleep:Boolean = true;
 
			// Construct a world object
			m_world = new b2World(worldAABB, gravity, doSleep);
 
			player = new Player();
		}
 
 
 
 
		public function Update() {
			// Wake up the player body, if it falls asleep, the player cannot
			// move anymore!
			playerBody.WakeUp();
 
			// Jump:
			if(keyboard.is_up()){
				// Cheesy way to check if the button was hit, instead of being held down:
				if(upDown == false){
					var b1:b2Body = GetBodyAtPoint(player.x, player.y + player.height/2, true);
					var b2:b2Body = GetBodyAtPoint(player.x-7, player.y + player.height - 4, true);
					var b3:b2Body = GetBodyAtPoint(player.x+7, player.y + player.height - 4, true);
					if((b1 != playerBody && b2 != playerBody && b3 != playerBody) && b1 != null || b2 != null || b3 != null && playerBody.m_linearVelocity.y >= 0){
						var DO = true;
 
						if(DO){
							playerBody.ApplyImpulse(new b2Vec2(0, -10), playerBody.GetPosition());//playerBody.m_linearVelocity.y = -10;
							// If you want the underliying body to react according to Newton's
							// second law of motion (it's pushed down), uncoment the following laws:
							/*var bo = (b1 != null ? b1 : (b2 != null ? b2 : b3));
							bo.ApplyImpulse(new b2Vec2(0, 10), playerBody.GetPosition());*/
						}
					}
					upDown = true;
				}
			}else{
				upDown = false;
			}
 
			// Side movements:
			if(keyboard.is_right()){
				playerBody.m_linearVelocity.x = 3;
			}
			if(keyboard.is_left()){
				playerBody.m_linearVelocity.x = -3;
			}
 
			// Sted the world:
			m_world.Step(m_timeStep, m_iterations);
 
			// Go through body list and update sprite positions/rotations
			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 * 30;
					bb.m_userData.y = bb.GetPosition().y * 30;
					bb.m_userData.rotation = bb.GetAngle() * (180/Math.PI);
				}
			}
		}
 
 
 
		// Create the player using this function, _x and _y are the coordinates to place
		// the object:
		public function createPlayer(_x, _y) : b2Body {
			bodyDef = new b2BodyDef();
			bodyDef.position.x = _x / 30;
			bodyDef.position.y = _y / 30;
			circleDef = new b2CircleDef();
			circleDef.radius = 30 / 2 / 30;
			circleDef.density = 1.0;
			circleDef.friction = 1;
			circleDef.restitution = 0.1;
			var mass:b2MassData = new b2MassData;
			mass.mass = 1;
			bodyDef.userData = player;
			player.x = _x;
			player.y = _y;
			addChild(player);
			body = m_world.CreateDynamicBody(bodyDef);
			body.SetMass(mass);
			body.CreateShape(circleDef);
			body.m_linearDamping = 1;
			playerBody = body;
 
			return playerBody;
		}
 
		// Using this function, you can create dynamic (moveable) blocks.
		// The parameters are self-explanatory.
		// The userData parameter is optional and it replaces the boring
		// sprite used to represent the box.
		public function addBox(x, y, wid, heig, userData:* = null) : b2Body {
			bodyDef = new b2BodyDef();
			bodyDef.position.x = x/30;
			bodyDef.position.y = y/30;
			boxDef = new b2PolygonDef();
			boxDef.SetAsBox(wid/30/2, heig/30/2);
			boxDef.density = 1.0;
			boxDef.friction = 0.7;
			boxDef.restitution = 0.2;
			if(userData != null){
				bodyDef.userData = userData;
			}else{
				bodyDef.userData = new PhysBox(wid, heig);
				//bodyDef.userData.width = wid * 2;
				//bodyDef.userData.height = heig * 2;
				addChild(bodyDef.userData);
			}
			body = m_world.CreateDynamicBody(bodyDef);
			body.CreateShape(boxDef);
			body.SetMassFromShapes();
 
			lastBody = body;
 
			return body;
		}
 
		// Using this function, you can create static (non-moveable) blocks.
		// The parameters are self-explanatory.
		// The userData parameter is same as in addBox function.
		public function addStaticBox(x, y, wid, heig, userData:* = null) : b2Body {
			var bodyDef = new b2BodyDef();
			bodyDef.position.x = x/30;
			bodyDef.position.y = y/30;
			boxDef = new b2PolygonDef();
			boxDef.SetAsBox(wid/30/2, heig/30/2);
			boxDef.density = 0.0;
			boxDef.friction = 0.5;
			boxDef.restitution = 0.2;
			if(userData != null){
				bodyDef.userData = userData;
			}else{
				bodyDef.userData = new PhysBox(wid, heig);
				//bodyDef.userData.width = wid * 2;
				//bodyDef.userData.height = heig * 2;
				addChild(bodyDef.userData);
			}
			body = m_world.CreateStaticBody(bodyDef);
			body.CreateShape(boxDef);
			body.SetMassFromShapes();
 
			lastBody = body;
 
			return body;
		}
 
		// Really, dunno how this works, but it does. And that's what matters.
		public function GetBodyAtPoint(px:Number, py:Number, includeStatic:Boolean = false) : b2Body {
			// Make a small box.
			var px2 = px/30;
			var py2 = py/30;
			var PointVec:b2Vec2 = new b2Vec2();
			PointVec.Set(px2, py2);
			var aabb:b2AABB = new b2AABB();
			aabb.lowerBound.Set(px2 - 0.001, py2 - 0.001);
			aabb.upperBound.Set(px2 + 0.001, py2 + 0.001);
 
			// Query the world for overlapping shapes.
			var k_maxCount:int = 10;
			var shapes:Array = new Array();
			var count:int = m_world.Query(aabb, shapes, k_maxCount);
			var body:b2Body = null;
 
			// The loop that seeks for the body:
			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(), PointVec);
					if (inside) {
						body = tShape.m_body;
						break;
					}
				}
 			}
			return body;
		}
 
 
		// Physics world and definitions
		public var m_world:b2World;
		public var m_iterations:int = 20;
		public var m_timeStep:Number = 1.0/25.0;
	}
}

And this is the class handling the keyboard input

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
package {
	import flash.events.KeyboardEvent;
	import flash.events.FocusEvent;
 
	public class keys {
		private var press_left = false;
		private var press_right = false;
		private var press_up = false;
		private var press_down = false;
		private var press_space = false;
		public var Keys:Array = new Array(200);
		public function keys(movieclip) {
			movieclip.stage.addEventListener(FocusEvent.FOCUS_OUT, focus, false, 0);
			movieclip.stage.addEventListener(KeyboardEvent.KEY_DOWN, key_down, false, 0);
			movieclip.stage.addEventListener(KeyboardEvent.KEY_UP, key_up, false, 0);
		}
		public function focus(e:FocusEvent){
			for(var i:int = 0;i<Keys.length;i++){
				Keys[i] = false;
			}
 
			press_left = false;
			press_right = false;
			press_up = false;
			press_down = false;
			press_space = false;
		}
 
		public function is_left() {
			return press_left;
		}
		public function is_right() {
			return press_right;
		}
		public function is_up() {
			return press_up;
		}
		public function is_down() {
			return press_down;
		}
		public function is_space() {
			return press_space;
		}
		public function isDown(key:int){
			return Keys[key];
		}
		private function key_down(event:KeyboardEvent) {
			Keys[event.keyCode] = true;
			if (event.keyCode == 32) {
				press_space = true;
			}
			if (event.keyCode == 37 || event.keyCode == 65) {
				press_left = true;
			}
			if (event.keyCode == 38 || event.keyCode == 87) {
				press_up = true;
			}
			if (event.keyCode == 39 || event.keyCode == 68) {
				press_right = true;
			}
			if (event.keyCode == 40 || event.keyCode == 83) {
				press_down = true;
			}
		}
		private function key_up(event:KeyboardEvent) {
			Keys[event.keyCode] = false;
			if (event.keyCode == 32) {
				press_space = false;
			}
			if (event.keyCode == 37 || event.keyCode == 65) {
				press_left = false;
			}
			if (event.keyCode == 38 || event.keyCode == 87) {
				press_up = false;
			}
			if (event.keyCode == 39 || event.keyCode == 68) {
				press_right = false;
			}
			if (event.keyCode == 40 || event.keyCode == 83) {
				press_down = false;
			}
		}
	}
}

And this is the result:

And this is the full source code to download

Rate this post: 1 Star2 Stars3 Stars4 Stars5 Stars (20 votes, average: 4.75 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.

16 Responses

  1. jb says:

    Wow, very simple and efficient !

  2. Monkios says:

    Very nice. The “jet-pack” feel while jumping isn’t there anymore.

    Might be a bug :

    Sometimes, when the seesaw is too close to the side of the frame and you are on it, you can’t jump.

  3. Yeah, this is caused by the jumping code, which tests collisions under the player. This can be fized with some few extra lines to the code. I’ll add this later and send you guys the source code + multilevel example.

  4. This is wonderful. Loving these BOX2D tutorials. :)

    Thanks Luiz!

  5. nice! :D
    Trying to make a tile game of it :)

  6. arxanas says:

    If it’s Box2D, then shouldn’t the player experience some resistance when trying to change direction (whether on the ground or in midair)?

  7. RedRail says:

    Arxanas, I think there is no resistance because the velocity is changed directly by setting the linear x velocity to 3, rather than by using impulses(or forces). (just my speculation, though)

    Anyway. Moving around has a really nice feeling about it, good job. However, player is …”sticky” while moving to the sides. You can stay long in the air by jumping and then pressing left or right near a box. It’s fun to use, but not what you would expect from, say, Mario.

    Have you tried to use Flash IDE as a level editor? Should be useful for a platform game.

  8. Jerry says:

    Very nice. Lot’s can be done with this.

    @RedRail – I don’t think Mario had any physics other then gravity going on in the begining. Later they add some friction and stuff.

  9. shuping chen says:

    Perfect!Thanks.

  10. EvilKris says:

    Found the tute really handy, cheers dude.

  11. molron says:

    Okay, so how do I skin those object inside Flash develop, it doesn’t look really nice with only black outline.

    Can I load my image and use it instead of those black outlined…

  12. [...] Fernando Silva, author of a Box2D platform engine alternative published on the blog some time ago, created a prototype of this [...]

  13. Andrew says:

    I keep getting this error
    “1061: Call to a possibly undefined method CreateDynamicBody through a reference with static type Box2D.Dynamics:b2World.”

    any idea why or how to fix it?

    I got that it is trying to find the b2World.as file in the Box2D>Dynamics folder, but why it is not finding it while it is clearly there is beyond me.

  14. Andrew says:

    I figured out my problem…
    If you are doing these great tuts in order don’t keep using the same Box2D source files. Emanuele wrote these for the version when they were made. Since then there have been updates which could as in my case change some names of methods. So be sure you are using the same version of Box2D as the tutorial.

  15. Davide says:

    Hi Emanuele,

    great stuff in your blog.
    Very usefull code, snippets and son on…

    A question for you: could this box2D engine be used in conjuction with the Collision Detection Kit? Maybe only the physics part. Moreover. Does this box2D Engine works with non uniform shapes?

    Thank you very much.

    Saluti
    Davide
    Roma

  16. [...] released a Box2D platform engine some months ago, and now I am publishing his last work, a Verlet physics [...]

Leave a Reply

flash games company