Compare v09 to v10
Produced: 13/1/17 9:07:51 am
   
Mode:  All  
Left file: /Users/peetj/github/cs-js-beg-platformer/versions/game-09.js  
Right file: /Users/peetj/github/cs-js-beg-platformer/versions/game-10.js  
1 1 /*
2     Title: game-09.js
3     Description: In this version of the code, we are going to ensure that a
4                  level complete message is displayed if the player collects
5                  all the coins.
  2   Title: game-10.js
  3   Description: In this version of the code,
  4                we are going to create a second level
6 5 */
7 6  
8 7 /* GLOBAL STATE VARIABLES START WITH A __ */
9 8 var _player = null;
10 9 var _viewportX = 0;
11 10 var _scoreDisplay = "";
12 11 var _gameStartTime = (new Date()).getTime();
13 12 var _timeLeftDisplay = "";
14 13 var _secondsTotal = 120;
15 14  
  15 var _tileMap = [
  16   {
  17     "level": 1,
  18     "map": [
  19       [[0,1],[0, 8],[0,13],[0,4],[0,8],[0, 8],[0,5],[0,4],[0,5],[0, 8]],
  20       [[0,8],[0,4],[0,3],[0,4],[0,6],[0, 8],[0,23]],
  21       [[0,8],[0,19],[0,4],[0,3],[0,4],[0,6],[0, 8],[0,4]],
  22       [[0,11],[0,4],[0,12],[0,4],[0,14],[0,6]],
  23       [[0,16],[0, 8],[0,13],[0, 8],[0,11]],
  24       [[4,1],[0,9],[0,37]],
  25       [[0,48]],
  26       [[1,48]]
  27     ]
  28   },
  29   {
  30     "level": 2,
  31     "map": [
  32       [[4,1],[0,1],[0,12],[0,1],[0,8],[0,1],[0,5],[0,1],[0,5],[0,1]],
  33       [[0,8],[0,1],[0,3],[0,1],[0,6],[0,1],[0,23]],
  34       [[0,8],[0,19],[0,1],[0,3],[0,1],[0,6],[0,1],[0,4]],
  35       [[0,11],[0,1],[0,12],[0,1],[0,14],[0,6]],
  36       [[0,16],[8,1],[0,13],[8,1],[0,11]],
  37       [[0,9],[4,1],[0,37]],
  38       [[0,48]],
  39       [[1,48]]
  40     ]
  41   }
  42 ]
  43  
16 44 /* State */
17 45 var __STATE = {};
18 46 __STATE.level = 1;
19 47 __STATE.gameStarted = false;
20 48 __STATE.gameOver = false;
21 49 __STATE.timeLeft = _secondsTotal;
22 50 __STATE.currentScore = 0;
23 51 __STATE.gameOverIsDisplaying = false;
24 52 __STATE.numCoinsToCollect = 0;
25 53 __STATE.levelComplete = false;
26 54  
27 55 const PLAYER_SPEED = 4;
28 56 const WALKER_VELOCITY = -80;
29 57 const EVENT_PLAYER_DIE = "EVENT_PLAYER_DIE";
30 58 const EVENT_PLAYER_HIT_WALKER = "EVENT_PLAYER_HIT_WALKER";
31 59 const EVENT_PLAYER_HIT_WALL = "EVENT_PLAYER_HIT_WALL";
32 60 const EVENT_GAME_OVER = "EVENT_GAME_OVER";
33 61 const EVENT_LEVEL_COMPLETE = "EVENT_LEVEL_COMPLETE";
34    
35 62 const SCREENWIDTH = 800;
36 63 const SCREENHEIGHT = 600;
37 64 const TILE_WIDTH = 80;
38 65 const TILE_HEIGHT = 60;
39 66 const GRAVITY_STRENGTH = 1000;
40 67 const RIGHT = 0;
41 68 const LEFT = 1;
42 69 const NONE = 2;
43 70  
44   var _player = null;
45    
46   var tileMap = [
47     [[0,1],[8,1],[0,13],[4,1],[0,8],[8,1],[0,5],[4,1],[0,5],[8,1]],
48     [[0,8],[4,1],[0,3],[4,1],[0,6],[8,1],[0,23]],
49     [[0,8],[0,19],[4,1],[0,3],[4,1],[0,6],[8,1],[0,4]],
50     [[0,11],[4,1],[0,12],[4,1],[0,14],[0,6]],
51     [[0,16],[8,1],[0,13],[8,1],[0,11]],
52     [[0,9],[4,1],[0,37]],
53     [[0,48]],
54     [[1,48]]
55   ];
56    
57 71 Crafty.init(800, 600, document.getElementById('gamecanvas'));
  72  
58 73 setupGlobalBindings();
59 74  
60   var assets = {'tiles': ['img/tile-1.png', 'img/platform.png', 'img/platformx2.png']};
  75 var assets = {'tiles': ['img/tile-1.png', 'img/platform.png', 'img/platformx2.png', 'img/sun.png']};
61 76 var playerSprite = { 'sprites': { 'img/playerSprite.png': { tile: 50, tileh: 77, map: { man_left: [0, 1], man_right: [0, 2], jump_right: [6, 4] } } } };
62 77  
63 78 initialiseGame();
64 79  
65 80 function initialiseGame () {
66 81   Crafty.load(assets, function(){
67 82     reset();
68 83     loadBackground();
69 84     loadSprites();
70 85     generateMap();
71 86     spawnEntities();
72 87     displayText();
73 88     /* Load sounds */
74 89     Crafty.audio.add("coin", "sounds/coin.wav");
75 90     __STATE.gameStarted = true;
76 91   });
77 92 }
78 93  
79 94 function loadBackground () {
80     Crafty.background('#3BB9FF');
  95   var bgColor = shade('#3BB9FF', __STATE.level * 4);
  96   //Crafty.background(bgColor);
81 97   //Crafty.background('#FFFFFF url(img/bg.png) repeat-x center center');
  98   Crafty.e('BG, 2D, DOM, Image')
  99     .attr({ x: 0, y: 0, z: 0, w:3840, h:600 })
  100     .image('img/bg.png', 'repeat-x');
  101 }
  102  
  103 function shade(color, percent){
  104     if (color.length > 7 ) return shadeRGBColor(color,percent);
  105     else return shadeColor2(color,percent);
  106 }
  107  
  108 function shadeColor2(color, percent) {
  109     var f=parseInt(color.slice(1),16),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=f>>16,G=f>>8&0x00FF,B=f&0x0000FF;
  110     return "#"+(0x1000000+(Math.round((t-R)*p)+R)*0x10000+(Math.round((t-G)*p)+G)*0x100+(Math.round((t-B)*p)+B)).toString(16).slice(1);
82 111 }
83 112  
84 113 function loadSprites () {
85 114   Crafty.load(playerSprite);
86 115 }
87 116  
88 117 function spawnEntities () {
89 118   spawnPlayer();
90 119   spawnWalkers();
91 120 }
92 121  
93 122 function spawnPlayer (){
94 123   _player = Crafty.e('Player, 2D, DOM, man_right, SpriteAnimation, Twoway, Collision, Gravity, Tween, Keyboard')
95 124     .attr({
96 125       x: 50,
97 126       y: 263
98 127     })
99 128     .reel('moveRight', 500, [[0, 2], [1, 2], [2, 2], [3, 2], [4, 2], [5, 2], [6, 2], [7, 2]])
100 129     .reel('moveLeft', 500, [[0, 1], [1, 1], [2, 1], [3, 1], [4, 1], [5, 1], [6, 1], [7, 1]])
101 130     .twoway(200, 510)
102 131     .gravity('FloorTile')
103 132     .gravityConst(GRAVITY_STRENGTH)
104 133     .bind('KeyDown',
105 134       function (e) {
106 135         if(__STATE.levelComplete)
107 136           return;
  137  
108 138         if (Crafty.keydown['37'] && Crafty.keydown['39']) {
109 139           this.pauseAnimation();
110 140           this.resetAnimation();
111 141           this.isMoving = false;
112 142           return;
113 143         }
114 144         if (e.key === Crafty.keys.RIGHT_ARROW && !this.isJumping) {
115 145           this.animate('moveRight', -1);
116 146         }
117 147         else if (e.key === Crafty.keys.LEFT_ARROW && !this.isJumping) {
118 148           this.animate('moveLeft', -1);
119 149         }
120 150         this.isMoving = true;
121 151       }
122 152     )
123 153     .bind('KeyUp',
124 154       function (e) {
125 155         if(__STATE.levelComplete)
126 156           return;
  157  
127 158         if ((this.isPlaying('moveRight') && e.key === Crafty.keys.RIGHT_ARROW) ||
128 159           (this.isPlaying('moveLeft') && e.key === Crafty.keys.LEFT_ARROW)) {
129 160           this.pauseAnimation();
130 161           this.resetAnimation();
131 162           /* Check/Set the player's moving state */
132 163           if (e.key === Crafty.keys.RIGHT_ARROW || e.key === Crafty.keys.LEFT_ARROW) {
133 164             this.isMoving = false;
134 165             return;
135 166           }
136 167         }
137 168         /* Kickstart animation if we need to */
138 169         if (this.isDown('RIGHT_ARROW') && !this.isJumping) {
139 170           this.animate('moveRight', -1);
140 171           this.isMoving = true;
141 172         }
142 173         else if (this.isDown('LEFT_ARROW') && !this.isJumping) {
143 174           this.animate('moveLeft', -1);
144 175           this.isMoving = true;
145 176         }
146 177       }
147 178     )
148 179     .bind('Moved', function (obj) {
149 180       if (this.x >= (SCREENWIDTH / 2) && this.x <= 3440) {
150 181         Crafty.viewport.scroll('_x', (this.x - (SCREENWIDTH / 2)) * -1);
151 182         displayScore();
152 183         displayTimeLeft();
153 184       }
154 185     })
155 186     .bind('CheckJumping', function (ground) {
156 187       this.isJumping = true;
157 188       var  canJump, doubleJump;
158 189       if(this.canJump === false && !this.inDoubleJumpMode){
159 190         /* Lets check if there is a platform above us */
160 191         var platforms = Crafty('Tile');
161 192         for(var i=0; i < platforms.length; i++){
162 193           var platform = platforms[i];
163 194           platform = Crafty(platform);
164 195           if(platform.x < this.x && platform.y < this.y && platform.x+160 > this.x){
165 196             /* We probably have a platform above us so don't allow double jump */
166 197             canJump = false;
167 198             doubleJump = false;
168 199             break;
169 200           }
170 201         }
171 202         this.canJump = canJump !== undefined ? canJump : true;
172 203         this.inDoubleJumpMode = doubleJump !== undefined ? doubleJump : true;
173 204       }
174 205       else if(this.inDoubleJumpMode){
175 206         this.canJump = false;
176 207       }
177 208       this.pauseAnimation();
178 209       this.resetAnimation();
179 210       //this.animate('jump', 1)
180 211       this.sprite(this.currentDirection === RIGHT ? 6 : 2, 4);
181 212     })
182 213     .bind('LandedOnGround', function (ground) {
183 214       if (this.isJumping) {
184 215         this.isJumping = false;
185 216         this.gravityConst(GRAVITY_STRENGTH);
186 217         /* We cannot get the direction from the velocity. We'll use the current loaded sprite animation  */
187 218         this.sprite(0, this.getReel().id === 'moveRight' ? 2 : 1);
188 219         /* We may need to enable controls here as we may have disabled them */
189 220         if(this.bounced){
190 221           this.velocity().x = 0;
191 222           this.bounced = false;
192 223         }
193 224         this.inDoubleJumpMode = false;
194 225       }
195 226       /* Will need to enable controls in some circumstances */
196 227       if(!__STATE.levelComplete){
197 228         this.enableControl();
198 229       }
199 230     })
200 231     .bind('NewDirection', function (obj) {
201 232       /* 0 is neither right nor left so we don't care about it */
202 233       if (obj.x === 0) return;
203 234       this.currentDirection = obj.x === 1 ? RIGHT : LEFT;
204 235       if (this.currentDirection === RIGHT && !this.isJumping) {
205 236         if (this.isMoving) {
206 237           /* Start running again */
207 238           this.animate('moveRight', -1);
208 239         }else {
209 240           this.sprite(0, 2);
210 241         }
211 242       }
212 243       else if (this.currentDirection === LEFT && !this.isJumping) {
213 244         if (this.isMoving) {
214 245           /* Start running again */
215 246           this.animate('moveLeft', -1);
216 247         }else {
217 248           this.sprite(0, 1);
218 249         }
219 250       }
220 251     })
221 252     .checkHits('Platform')
222 253     .bind('HitOn', function(hitData){
223 254       /* If underneath player, bounce back down */
224 255       this.disableControl();
225 256       this.gravityConst(1000);
226 257       this.velocity().x = 0;
227 258       this.velocity().y = 0;
228 259       Crafty.e("Delay").delay(reinstateMovement, 500, 1);
229 260     })
230 261     .bind('TweenEnd', function(prop){
231 262       if(this.alpha === 0.0){
232 263         this.destroy();
233 264       }
234 265     })
235 266     .bind(EVENT_PLAYER_DIE, function () {
236 267       this.tween({ alpha: 0.0 }, 1000);
237 268       Crafty.trigger(EVENT_GAME_OVER);
238 269     })
239 270     .bind(EVENT_PLAYER_HIT_WALKER, function () {
240 271       this.vy = -400;
241 272       this.tween({ y: this.y - 100 }, 300);
242 273     })
243 274     .bind(EVENT_LEVEL_COMPLETE, function () {
244 275       this.disableControl();
245 276     })
246 277     .bind('EnterFrame', function(){
247 278       if(this.x <= -6) this.x = -5;
248 279       if(this.x >= 3785) this.x = 3784;
249 280     })
250 281  
251 282     /* Set player defaults */
252 283     _player.isJumping = false;
253 284     _player.currentDirection = RIGHT;
254 285     _player.isMoving = false;
255 286     _player.reel('moveRight');
256 287     _player.bounced = false;
257 288     _player.inDoubleJumpMode = false;
258 289 }
259 290  
260 291 function spawnWalkers () {
261 292   Crafty.e('Delay').delay(spawnWalker, Crafty.math.randomInt(2000, 8000), -1);
262 293 }
263 294  
264 295 function spawnWalker () {
265 296   Crafty.e('Walker');
266 297 }
267 298  
268 299 /*
269     The tilemap generation. An example of the first row is shown below
270    
271     [[0,1],[8,1],[0,13],[4,1],[0,8],[8,1],[0,5],[4,1],[0,7],[1,1],[8,1]],
  300   The tilemap generation. Tile level maps are 'pulled' from the tilemap.json
  301   file which is in the 'runner' directory. You can add as many levels as you
  302   want.
272 303 */
273 304 function generateMap () {
274     const Y_OFFSET = 600 - (tileMap.length * TILE_HEIGHT);
275     tileMap.map(function (tileRow, rowIdx) {
  305   _tileMap.map(function(lv, idx){
  306     if(lv.level === __STATE.level){
  307       var tempMap = lv.map;
  308       tempMap = lv.map;
  309       processMap(tempMap);
  310       return;
  311     }
  312   });
  313 }
  314  
  315 function processMap(tempMap){
  316   const Y_OFFSET = 600 - (tempMap.length * TILE_HEIGHT);
  317   tempMap.map(function (tileRow, rowIdx) {
276 318     var xPos = 0;
277 319     var yPos = 0;
278 320     tileRow.map(function (tile, tileIdx) {
279 321       yPos = Y_OFFSET + (rowIdx * 60);
280 322       var tileType = tile[0];
281 323       var tileNum = tile[1];
282 324       if (tileType === 0){
283 325         xPos += (tileNum * 80);
284 326       }
285 327       if (tileType === 1) {
286 328         for(var i=0; i < tileNum; i++){
287 329           Crafty.e('FloorTile, 2D, DOM, Image, Collision')
288 330             .attr({ x: xPos, y: yPos, w: TILE_WIDTH, h: TILE_HEIGHT })
289 331             .image(Crafty.assets['img/tile-' + tileType + '.png'].src);
290 332           xPos += 80;
291 333         }
292 334       }
293 335       else{
294 336         if (tileType === 4) {
295 337           Crafty.e('Platform')
296 338             .setImage('img/platform.png')
297 339             .setPlatform(xPos, yPos, 1)
298 340             .addCoins(Crafty.math.randomInt(1,2));
299             xPos += 160;
  341             xPos += 160;
300 342         }
301 343         else if (tileType === 8) {
302 344           Crafty.e('Platform')
303 345             .setImage('img/platformx2.png')
304 346             .setPlatform(xPos, yPos, 2)
305 347             .addCoins(Crafty.math.randomInt(1,2));
306 348           xPos += 320;
307 349         }
308           xPos += 80;
309 350       }
310 351       if(rowIdx === 6) console.log('x:',xPos,'y:',yPos);
311 352     });
312 353   });
313 354 }
314 355  
315 356 function displayText () {
316 357   displayScore();
317 358   displayTimeLeft();
318 359 }
319 360  
320 361 function displayTimeLeft () {
321 362   if(_timeLeftDisplay){
322 363     _timeLeftDisplay.destroy();
323 364   }
324 365   _timeLeftDisplay = Crafty.e("2D, DOM, Text")
325 366     .attr({ x: 720 - Crafty.viewport._x, y: 20 })
326 367     .text(__STATE.timeLeft)
327 368     .textColor('#FF0000')
328 369     .textFont({ size: '14px', weight: 'bold', family: 'Courier New' });
329 370 }
330 371  
331 372 function displayScore () {
332 373   if(_scoreDisplay){
333 374     _scoreDisplay.destroy();
334 375   }
335 376   _scoreDisplay = Crafty.e("2D, DOM, Text")
336 377     .attr({ x: 750 - Crafty.viewport._x, y: 20 })
337 378     .text(pad(__STATE.currentScore, 3))
338 379     .textColor('#FF0000')
339 380     .textFont({ size: '14px', weight: 'bold', family: 'Courier New' });
340 381 }
341 382  
342 383 function displayGameOver(){
343 384   if(Crafty("GameOver"))
344 385     Crafty("GameOver").destroy();
345 386  
346 387   Crafty.e("GameOver, 2D, DOM, Text")
347 388     .attr({ x: 280 - Crafty.viewport._x, y: 280, z:100, w: 250 })
348 389     .text("GAME OVER").textColor('#FF0000').textFont({ size: '36px', weight: 'bold', family: 'Courier New' });
349 390 }
350 391  
351 392 function displayLevelComplete(){
352 393   if(Crafty("LevelComplete"))
353 394     Crafty("LevelComplete").destroy();
354 395  
355 396   Crafty.e("LevelComplete, 2D, DOM, Text")
356 397     .attr({ x: 270 - Crafty.viewport._x, y: 280, z:100, w: 350 })
357       .text("LEVEL COMPLETE").textColor('#33FF33').textFont({ size: '36px', weight: 'bold', family: 'Courier New' });
  398     .text("LEVEL "+__STATE.level+" COMPLETE").textColor('#33FF33').textFont({ size: '36px', weight: 'bold', family: 'Courier New' });
358 399 }
359 400  
360 401 function updateScore(amt){
361 402   __STATE.currentScore += amt;
362 403   displayScore();
363 404 }
364 405  
365 406 function pad(num, size) {
366 407     var s = num+"";
367 408     while (s.length < size) s = "0" + s;
368 409     return s;
369 410 }
370 411  
371 412 function setupGlobalBindings () {
372 413   Crafty.bind(EVENT_GAME_OVER, function(){
373 414     _player.disableControl();
374 415     pauseAndResetAnimation(_player);
  416     _player.destroy();
375 417     displayGameOver();
376 418   })
377 419 }
378 420  
379 421 function pauseAndResetAnimation (ent){
380 422   ent.pauseAnimation();
381 423   ent.resetAnimation();
382 424 }
383 425  
384 426 function reinstateMovement () {
  427   console.log('Enabling controls...');
385 428   if(!__STATE.levelComplete){
386 429     _player.enableControl();
387 430   }
388 431 }
389 432  
390 433 function reset () {
391 434   _player = null;
392 435   _viewportX = 0;
393     _scoreDisplay = "";
  436   _scoreDisplay = __STATE.level === 1 ? "" : _scoreDisplay;
394 437   _gameStartTime = (new Date()).getTime();
395 438   _timeLeftDisplay = "";
396     _secondsTotal = 120;
  439   _secondsTotal = 125 - __STATE.level * 5;
397 440  
398 441   __STATE.gameStarted = false;
399 442   __STATE.gameOver = false;
400 443   __STATE.timeLeft = _secondsTotal;
401     __STATE.currentScore = 0;
  444   __STATE.currentScore = __STATE.level === 1 ? 0 : __STATE.currentScore;
402 445   __STATE.gameOverIsDisplaying = false;
403 446   __STATE.numCoinsToCollect = 0;
404 447   __STATE.levelComplete = false;
405 448  
406 449   /* Position viewport */
407 450   Crafty.viewport.scroll('_x', 0)
408 451 }
409 452  
410 453 Crafty.bind('EnterFrame', function(){
411 454   if(!__STATE.gameStarted || __STATE.levelComplete)
412 455     return;
413 456  
414 457   if(Crafty.frame() % 50 === 1){
415 458     __STATE.timeLeft = _secondsTotal - Math.round(((new Date()).getTime() - _gameStartTime) / 1000);
416 459     if(__STATE.timeLeft <= -1){
417 460       __STATE.gameOver = true;
418 461     }
419 462   }
420 463   if(__STATE.gameOver && !__STATE.gameOverIsDisplaying){
421 464     /* Display Game Over */
422 465     Crafty.trigger(EVENT_GAME_OVER);
423 466     __STATE.gameOverIsDisplaying = true;
424 467   }
425 468   if(!__STATE.gameOver){
426 469     displayTimeLeft();
427 470   }
428 471 })
429 472  
430 473 Crafty.bind(EVENT_LEVEL_COMPLETE, function(){
431 474   console.log('Level Complete...');
432 475   __STATE.levelComplete = true;
433 476   displayLevelComplete();
  477   Crafty.e("Delay").delay(function(){
  478     /* Destroy everything */
  479     Crafty("2D").each(function(i) {
  480       this.destroy();
  481     });
  482     ++__STATE.level;
  483     initialiseGame();
  484   }, 2000, 0);
434 485 })