1 /**
  2 	@class Example code in the form of a simple "falling game" where the player must try to stay on the screen as platforms move past them upward.
  3 */
  4 function ExampleGame(gs) {
  5 	// seedable random number generator for building the world
  6 	var r = new SeedableRandom();
  7 	var d = new Date;
  8 	r.seed(d.getTime());
  9 	// vertical separation of platform layers
 10 	X_SEPARATION = 300;
 11 	Y_SEPARATION = 150;
 12 	
 13 	/* The player class */
 14 	function Player(world) {
 15 		this.type = 'player';
 16 		// constants
 17 		var MAX_VY = 20;
 18 		var WALK_VX = 3;
 19 		var WALK_FRAMES = 3;
 20 		var FALL_FRAMES = 2;
 21 		// position
 22 		var pos = this.pos = [gs.width / 2, gs.height / 2];
 23 		// velocity
 24 		var vx = 0;
 25 		var vy = 0;
 26 		// sprite which represents the player
 27 		var p = new Sprite(["center", "bottom"], {
 28 			stand: [["img/character-stand.png", 0],],
 29 			walk right: [["img/character-walk-right-1.png", WALK_FRAMES], ["img/character-walk-right-2.png", WALK_FRAMES], ["img/character-walk-right-3.png", WALK_FRAMES], ["img/character-walk-right-2.png", WALK_FRAMES],],
 30 			walk left: [["img/character-walk-left-1.png", WALK_FRAMES], ["img/character-walk-left-2.png", WALK_FRAMES], ["img/character-walk-left-3.png", WALK_FRAMES], ["img/character-walk-left-2.png", WALK_FRAMES],],
 31 			fall: [["img/character-fall-1.png", FALL_FRAMES], ["img/character-fall-2.png", FALL_FRAMES],],
 32 		},
 33 		// callback gets called when everything is loaded
 34 		function() {
 35 			p.action("stand");
 36 		});
 37 		
 38 		/* Concurrency stuff */
 39 		
 40 		// draw the player's sprite every frame
 41 		this.draw = function(c) {
 42 			p.draw(c, world.camera(pos));
 43 		}
 44 		
 45 		this.updateanimation = function() {
 46 			if (vy > world.gravity * 2) {
 47 				p.action("fall");
 48 			} else {
 49 				if (vx >= WALK_VX) {
 50 					p.action("walk right");
 51 				} else if (vx <= -WALK_VX) {
 52 					p.action("walk left");
 53 				} else {
 54 					p.action("stand");
 55 				}
 56 			}
 57 		}
 58 		
 59 		// update the player's position every frame
 60 		this.update = function() {
 61 			vy = Math.min(vy + world.gravity, MAX_VY);
 62 			this.updateanimation();
 63 			pos[0] += vx;
 64 			pos[1] += vy;
 65 			p.update();
 66 			if (pos[1] > p.height + gs.height || pos[1] < 0) {
 67 				document.getElementById("gameover").style.paddingTop = gs.height / 2 - 100;
 68 				document.getElementById("gameover").style.display = "block";
 69 			}
 70 		}
 71 		
 72 		/* collision stuff */
 73 		
 74 		// return the bounding box of our sprite for the collision test
 75 		this.get_collision_aabb = function() {
 76 			return p.aabb(pos);
 77 		}
 78 		
 79 		/* input events stuff */
 80 		this.keyDown_37 = function () {
 81 			this.updateanimation();
 82 			vx = -WALK_VX;
 83 		}
 84 		
 85 		this.keyUp_37 = this.keyUp_39 = function() {
 86 			this.updateanimation();
 87 			vx = 0;
 88 		}
 89 		
 90 		this.keyDown_39 = function () {
 91 			this.updateanimation();
 92 			vx = WALK_VX;
 93 		}
 94 		
 95 		// basic comparison function
 96 		var cmp = function(x, y){ return x[0] < y[0] ? 1 : x[0] > y[0] ? -1 : 0; };
 97 		
 98 		// if the axis aligned bouding box of this entity collides with another
 99 		this.collide_aabb = function(who) {
100 			if (who.type == 'platform') {
101 				var ab = this.get_collision_aabb();
102 				var bb = who.get_collision_aabb();
103 				
104 				var sides = [
105 						[bb[1] - (ab[1] + ab[3]), 1, 1],
106 						[bb[0] - (ab[0] + ab[2]), 0, 1],
107 						[ab[0] - (bb[0] + bb[2]), 0, -1],
108 						[ab[1] - (bb[1] + bb[3]), 1, -1]
109 				];
110 				sides.sort(cmp);
111 				var d = sides[0];
112 				// hit a vertical side
113 				if (d[1]) {
114 					if (pos[0] > bb[0] + bb[2]) {
115 						pos[0] += WALK_VX;
116 					} else if (pos[0] < bb[0]) {
117 						pos[0] -= WALK_VX;
118 					} else {
119 						pos[1] = bb[1];
120 						vy = 0;
121 						this.updateanimation();
122 					}
123 				} else {
124 					// horizontal side
125 					if (pos[0] > bb[0] + bb[2]) {
126 						pos[0] += WALK_VX;
127 					} else if (pos[0] < bb[0]) {
128 						pos[0] -= WALK_VX;
129 					}
130 				}
131 			}
132 		}
133 		/*this.keyDown = function (keyCode) {
134 			console.log(keyCode);
135 		}*/
136 	}
137 	
138 	/* A prop in the world. */
139 	function Prop(world, platform) {
140 		this.type = 'prop';
141 		var propfile = null;
142 		var offset = 0;
143 		
144 		// choose a random sprite to load for this prop
145 		if (r.nextInt(0, 5) == 0) {
146 			propfile = "img/prop-" + ["tyre", "lamp", "bench"][r.nextInt(0, 3)] + ".png";
147 		} else {
148 			propfile = "img/tree";
149 			if (r.nextInt(0, 2))
150 				propfile += "-pine";
151 			propfile += "-" + r.nextInt(1, 4) + ".png";
152 		}
153 		
154 		// instantiate the sprite
155 		var p = new Sprite(["center", "bottom"], {default: [[propfile, 0]]}, function() {
156 			p.action("default");
157 			offset = (r.next() - 0.5) * (platform.get_collision_aabb()[2] - p.width / 2);
158 		});
159 		
160 		// draw this prop's sprite every frame
161 		this.draw = function(c) {
162 			p.draw(c, world.camera([platform.pos[0] + offset, platform.pos[1] + 1]));
163 		}
164 	}
165 	
166 	/* Platform */
167 	function Platform(world, pos) {
168 		this.type = 'platform';
169 		// the list of props sitting on this platform
170 		var props = [];
171 		// current position
172 		this.pos = pos;
173 		// closureify
174 		var platform = this;
175 		
176 		// get the sprite for this platform
177 		var p = this.sprite = new Sprite(["center", "top"], {default: [["img/platform-" + r.nextInt(1, 5) + ".png", 0]]}, function() {
178 			// once the sprite is loaded do some init
179 			p.action("default");
180 		});
181 		
182 		// called when this entity is added
183 		this.init = function() {
184 			// make me some props
185 			for (var i = 0; i < r.nextInt(1, 4); i++) {
186 				props.push(new Prop(world, this));
187 				gs.addEntity(props[props.length - 1]);
188 			}
189 		}
190 		
191 		// update this platform's position every frame
192 		this.update = function() {
193 			pos[1] -= world.upspeed;
194 			// if the platform has moved off the screen the get rid of it
195 			if (pos[1] < -200) {
196 				for (var i = 0; i < props.length; i++) {
197 					gs.delEntity(props[i]);
198 					delete props[i];
199 				}
200 				gs.delEntity(this);
201 				world.remove(this);
202 			}
203 			p.update();
204 		}
205 		
206 		// draw this platform's sprite every frame
207 		this.draw = function(c) {
208 			p.draw(c, world.camera(pos));
209 		}
210 		
211 		// return the bounding box of our sprite for the aabb collision test
212 		this.get_collision_aabb = function() {
213 			return p.aabb(pos);
214 		}
215 	}
216 	
217 	/* World */
218 	function World() {
219 		// how much gravity to apply to objects each frame
220 		this.gravity = 0.4;
221 		// how fast the props etc. should move upwards
222 		this.upspeed = 1; //0.09;
223 		// where the camera is centered
224 		this.cpos = gs.width / 2;
225 		// how far has the user progressed downwards?
226 		this.distance = 0;
227 		// last time we created new platforms
228 		var lastupdate = 0;
229 		
230 		// background colour
231 		var bg = 'rgba(240, 255, 255, 1.0)';
232 		var player = new Player(this);
233 		var platforms = [];
234 		
235 		// defines a simple screen-relative camera method
236 		this.camera = function(pos) {
237 			return [pos[0] - this.cpos + gs.width / 2, pos[1]];
238 		}
239 		
240 		// called when we are first added
241 		this.init = function() {
242 			gs.addEntity(player);
243 			platforms.push(gs.addEntity(new Platform(this, [gs.width / 2, gs.height / 2])));
244 			for (var y = gs.height / 2 + Y_SEPARATION; y < gs.height + Y_SEPARATION; y += Y_SEPARATION) {
245 				this.addrow(y);
246 			}
247 		}
248 		
249 		// called every frame to draw the background
250 		this.draw = function() {
251 			gs.background(bg);
252 		}
253 		
254 		// called every frame to run the game, collisions, etc.
255 		this.update = function() {
256 			// detect collisions between the player and the props
257 			collide.aabb([player], platforms);
258 			// update the camera position
259 			this.cpos += (player.pos[0] - this.cpos) * 0.5;
260 			// increment the total distance travelled
261 			this.distance += this.upspeed;
262 			// each time we overflow create new platforms
263 			if (Math.floor(this.distance / Y_SEPARATION) > lastupdate) {
264 				lastupdate = Math.floor(this.distance / Y_SEPARATION);
265 				this.addrow(gs.height + Y_SEPARATION);
266 			}
267 			// constantly increase the speed of platforms moving up
268 			this.upspeed += 0.001;
269 		}
270 		
271 		/* mouse/finger detection */
272 
273 		this.pointerDown = function() {
274 			if (gs.pointerPosition[0] < gs.width / 2) {
275 				player.keyDown_37();
276 			} else {
277 				player.keyDown_39();
278 			}
279 		}
280 		
281 		this.pointerUp = function() {
282 			player.keyUp_37();
283 		}
284 		
285 		this.pointerBox = function() {
286 			return [0, 0, gs.width, gs.height];
287 		}
288 		
289 		// remove a platform from the world
290 		this.remove = function(which) {
291 			platforms.remove(which);
292 			//console.log(platforms.length);
293 			gs.delEntity(which);
294 		}
295 		
296 		// add a new random row of platforms to the world
297 		this.addrow = function(y) {
298 			for (var x = player.pos[0] - gs.width / 2; x < player.pos[0] + gs.width / 2; x += X_SEPARATION) {
299 				platforms.push(gs.addEntity(new Platform(this, [x, y])));
300 			}
301 		}
302 	}
303 	
304 	// preload all of the sprites we will use in this game
305 	Sprite.preload([
306 			"img/character-fall-1.png",
307 			"img/character-fall-2.png",
308 			"img/character-stand.png",
309 			"img/character-walk-left-1.png",
310 			"img/character-walk-left-2.png",
311 			"img/character-walk-left-3.png",
312 			"img/character-walk-right-1.png",
313 			"img/character-walk-right-2.png",
314 			"img/character-walk-right-3.png",
315 			"img/makeleft.sh",
316 			"img/platform-1.png",
317 			"img/platform-2.png",
318 			"img/platform-3.png",
319 			"img/platform-4.png",
320 			"img/prop-bench.png",
321 			"img/prop-lamp.png",
322 			"img/prop-tyre.png",
323 			"img/tree-1.png",
324 			"img/tree-2.png",
325 			"img/tree-3.png",
326 			"img/tree-pine-1.png",
327 			"img/tree-pine-2.png",
328 			"img/tree-pine-3.png",
329 		],
330 		// create the world
331 		function() { gs.addEntity(new World()); }
332 	);
333 }
334 
335 // this launch function is called by the HTML document to start the game
336 // the HTML document has a div tag called 'surface'
337 function launch() {
338 	// grab the surface div and insert a canvas of the same size inside it
339 	var surface = document.getElementById("surface");
340 	var newcanvas = document.createElement("canvas");
341 	// set the width and height of our canvas to be the same as the container div
342 	newcanvas.style.width = newcanvas.width = (surface.offsetWidth + 1);
343 	newcanvas.style.height = newcanvas.height = (surface.offsetHeight + 1);
344 	surface.appendChild(newcanvas);
345 	// launch the gamesoup loop on the new canvas we just created
346 	var gs = new JSGameSoup(newcanvas, 30);
347 	ExampleGame(gs);
348 	gs.launch();
349 }
350