Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(199)

Side by Side Diff: chrome/renderer/resources/offline.js

Issue 545973003: Update network error template to new design (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Remove debug code from JS file. Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 (function() {
5 /**
6 * Canvas port of the T-rex runner.
7 * @author Edward Jung (edwardjung@google.com)
8 *
9 * Note, the file referenced on the offline interstitial is the
10 * compiled version.
11 *
12 * Build command:
13 * java -jar path/to/closure/compiler.jar \
14 * --compilation_level ADVANCED_OPTIMIZATIONS \
15 * --js runrunrun-canvas.js \
16 * --js_output_file runrunrun-canvas.min.js
17 */
18
19
20 /**
21 * T-Rex runner.
22 * @param {string} outerContainerId Outer containing element id.
23 * @param {object} opt_config Optional configuration object.
24 * @constructor
25 * @export
26 */
27 var Runner = function(outerContainerId, opt_config) {
28 this.outerContainerEl = document.querySelector(outerContainerId);
29
30 this.containerEl = null;
31
32 this.config = opt_config || Runner.config;
33
34 this.dimensions = Runner.defaultDimensions;
35
36 this.canvas = null;
37 this.canvasCtx = null;
38
39 this.tRex = null;
40
41 this.distanceMeter = null;
42 this.distanceRan = 0;
43
44 this.highestScore = 0;
45
46 this.time = 0;
47 this.runningTime = 0;
48 this.msPerFrame = 1000 / Runner.FPS;
49 this.currentSpeed = this.config.SPEED;
50
51 this.obstacles = [];
52
53 this.started = false;
54 this.activated = false;
55 this.crashed = false;
56 this.paused = false;
57
58 this.resizeTimerId_ = null;
59
60 this.playCount = 0;
61
62 // Images.
63 this.images = {};
64 this.imagesLoaded = 0;
65 this.loadImages();
66
67 // Sound FX.
68 this.audioBuffer = null;
69 this.audioContext = window.AudioContext ?
70 new AudioContext() : new webkitAudioContext();
71 this.soundFx = {};
72
73 // Singleton
74 if (Runner.instance_) {
75 return Runner.instance_;
76 }
77 Runner.instance_ = this;
78 };
79 // Export for Closure compiler
80 window['Runner'] = Runner;
81 window['Runner']['Trex'] = Runner.Trex;
82 window['Runner']['Clouds'] = Runner.Clouds;
83 window['Runner']['Horizon'] = Runner.Horizon;
84 Runner.prototype['handleEvent'] = Runner.prototype.handleEvent;
85
86
87 /**
88 * Singleton getter.
89 * @return {Runner}
90 */
91 Runner.getInstance = function(container, opt_config) {
92 return Runner.instance_ || new Runner(container, opt_config);
93 };
94
95
96 /**
97 * Setting individual settings for debugging.
98 * @param {string} setting Setting name.
99 * @param {*} value Setting value to assign.
100 */
101 Runner.prototype.updateConfigSetting = function(setting, value) {
102 if (setting in this.config && value != undefined) {
103 this.config[setting] = value;
104
105 switch (setting) {
106 case 'GRAVITY':
107 case 'MIN_JUMP_HEIGHT':
108 case 'SPEED_DROP_COEFFICIENT':
109 this.tRex.config[setting] = value;
110 break;
111 case 'INITIAL_JUMP_VELOCITY':
112 this.tRex.setJumpVelocity(value);
113 break;
114 case 'SPEED':
115 this.setSpeed(value);
116 break;
117 }
118 }
119 };
120
121
122 /**
123 * Default game configuration.
124 * @enum {string}
125 */
126 Runner.config = {
127 ACCELERATION: 0.001,
128 BG_CLOUD_SPEED: 0.2,
129 BOTTOM_PAD: 10,
130 CLEAR_TIME: 3000,
131 CLOUD_FREQUENCY: 0.5,
132 GAMEOVER_CLEAR_TIME: 750,
133 GAP_COEFFICIENT: 0.6,
134 GRAVITY: 0.6,
135 INITIAL_JUMP_VELOCITY: 12,
136 MAX_CLOUDS: 6,
137 MAX_OBSTACLE_LENGTH: 3,
138 MAX_SPEED: 12,
139 MIN_JUMP_HEIGHT: 35,
140 MOBILE_SPEED_COEFFICIENT: 1.2,
141 SPEED: 6,
142 SPEED_DROP_COEFFICIENT: 3
143 };
144
145
146 // Default game width.
147 Runner.DEFAULT_WIDTH = 600;
148
149 // Frames per second.
150 Runner.FPS = 60;
151
152
153 /**
154 * Default dimensions.
155 * @enum {string}
156 */
157 Runner.defaultDimensions = {
158 WIDTH: Runner.DEFAULT_WIDTH,
159 HEIGHT: 150
160 };
161
162
163 /**
164 * CSS class names.
165 * @enum {string}
166 */
167 Runner.classes = {
168 CANVAS: 'runner-canvas',
169 CONTAINER: 'runner-container',
170 CRASHED: 'crashed',
171 ICON: 'icon-offline',
172 TOUCH_CONTROLLER: 'controller'
173 };
174
175
176 /**
177 * Image source urls.
178 * @enum {string}
179 */
180 Runner.imageSources = {
181 LDPI: [
182 { name: 'CACTUS_LARGE', id: '1x-obstacle-large' },
arv (Not doing code reviews) 2014/09/15 17:35:44 wrong indentation no ws after {
edwardjung 2014/09/16 13:14:31 Done.
183 { name: 'CACTUS_SMALL', id: '1x-obstacle-small' },
184 { name: 'CLOUD', id: '1x-cloud' },
185 { name: 'HORIZON', id: '1x-horizon' },
186 { name: 'RESTART', id: '1x-restart' },
187 { name: 'TEXT_SPRITE', id: '1x-text' },
188 { name: 'TREX', id: '1x-trex' }
189 ],
190 HDPI: [
191 { name: 'CACTUS_LARGE', id: '2x-obstacle-large' },
192 { name: 'CACTUS_SMALL', id: '2x-obstacle-small' },
193 { name: 'CLOUD', id: '2x-cloud' },
194 { name: 'HORIZON', id: '2x-horizon' },
195 { name: 'RESTART', id: '2x-restart' },
196 { name: 'TEXT_SPRITE', id: '2x-text' },
197 { name: 'TREX', id: '2x-trex' }
198 ]
199 };
200
201
202 /**
203 * Sound FX. Reference to the ID of the audio tag on interstitial page.
204 * @enum {string}
205 */
206 Runner.sounds = {
207 BUTTON_PRESS: 'offline-sound-press',
208 HIT: 'offline-sound-hit',
209 SCORE: 'offline-sound-reached'
210 };
211
212
213 /**
214 * Key code mapping.
215 * @enum {string}
216 */
217 Runner.keycodes = {
218 JUMP: { '38': 1, '32': 1 }, // Up, spacebar
219 DUCK: { '40': 1 }, // Down
220 RESTART: { '13': 1 } // Enter
221 };
222
223
224 /**
225 * Runner event names.
226 * @enum {string}
227 */
228 Runner.events = {
229 ANIM_END: 'webkitAnimationEnd',
arv (Not doing code reviews) 2014/09/15 17:35:43 skip webkit prefix
edwardjung 2014/09/16 13:14:31 CSS animations are still prefixed in Chrome. The u
230 CLICK: 'click',
231 KEYDOWN: 'keydown',
232 KEYUP: 'keyup',
233 MOUSEDOWN: 'mousedown',
234 MOUSEUP: 'mouseup',
235 RESIZE: 'resize',
236 TOUCHEND: 'touchend',
237 TOUCHSTART: 'touchstart',
238 VISIBILITY: 'visibilitychange',
239 BLUR: 'blur',
240 FOCUS: 'focus',
241 LOAD: 'load'
242 };
243
244
245 /**
246 * Global web audio context for playing sounds.
247 */
248 Runner.audioContext = window.AudioContext ?
249 new AudioContext() : new webkitAudioContext();
arv (Not doing code reviews) 2014/09/15 17:35:43 Remove webkit prefix
arv (Not doing code reviews) 2014/09/15 17:35:43 wrong indentation
edwardjung 2014/09/16 13:14:31 Done.
edwardjung 2014/09/16 13:14:31 Done. Just assigned a AudioContext instead.
250
251 /**
252 * Load and cache the image assets from the page.
253 */
254 Runner.prototype.loadImages = function() {
255 var imageSources = Runner.isHDPI ? Runner.imageSources.HDPI :
256 Runner.imageSources.LDPI;
257
258 var numImages = imageSources.length;
259
260 for (var i = numImages - 1; i >= 0; i--) {
261 var imgSource = imageSources[i];
262 this.images[imgSource.name] = document.getElementById(imgSource.id);
263
arv (Not doing code reviews) 2014/09/15 17:35:44 remove empty line
edwardjung 2014/09/16 13:14:32 Done.
264 }
265 this.init();
266 };
267
268
269 /**
270 * Load and decode base 64 encoded sounds.
271 */
272 Runner.prototype.loadSounds = function() {
273 for (var sound in Runner.sounds) {
274 var soundSrc = String(document.getElementById(Runner.sounds[sound]).src);
arv (Not doing code reviews) 2014/09/15 17:35:44 useless String cast?
edwardjung 2014/09/16 13:14:31 Done.
275 soundSrc = soundSrc.substr(soundSrc.indexOf(',') + 1);
276 var buffer = Runner.decodeBase64ToArrayBuffer(soundSrc);
277
278 // Async, so no guarantee of order in array.
279 Runner.audioContext['decodeAudioData'](buffer, function(index, audioData) {
arv (Not doing code reviews) 2014/09/15 17:35:43 Why not Runner.audioContext.decodeAudioData? Are y
edwardjung 2014/09/16 13:14:32 Removed. Actually I'm not compiling the file anymo
280 this.soundFx[index] = audioData;
arv (Not doing code reviews) 2014/09/15 17:35:43 wrong indentation
edwardjung 2014/09/16 13:14:32 Done.
281 }.bind(this, sound));
282 }
283
284 };
285
286
287 /**
288 * Sets the game speed. Adjust the speed accordingly if on a smaller screen.
289 * @param {number} opt_speed Optional speed.
290 */
291 Runner.prototype.setSpeed = function(opt_speed) {
292 var speed = opt_speed || this.currentSpeed;
293
294 // Reduce the speed on smaller mobile screens.
295 if (Runner.test.mobile() && this.dimensions.WIDTH < Runner.DEFAULT_WIDTH) {
arv (Not doing code reviews) 2014/09/15 17:35:43 Why bother testing if mobile. The screen width sho
edwardjung 2014/09/16 13:14:32 Done. We were trying to restrict this to mobile o
296 var mobileSpeed = speed * ((this.dimensions.WIDTH / Runner.DEFAULT_WIDTH) *
297 this.config.MOBILE_SPEED_COEFFICIENT);
298 this.currentSpeed = mobileSpeed > speed ? speed : mobileSpeed;
299 } else if (opt_speed) {
300 this.currentSpeed = opt_speed;
301 }
302 };
303
304
305 /**
306 * Game initialiser.
307 */
308 Runner.prototype.init = function() {
309 // Hide the static icon.
310 document.querySelector('.' + Runner.classes.ICON).style.visibility = 'hidden';
311
312 this.adjustDimensions();
313 this.setSpeed();
314
315 this.containerEl = document.createElement('div');
316 this.containerEl.className = Runner.classes.CONTAINER;
317
318 // Player canvas container.
319 this.canvas = Runner.createCanvas(this.containerEl, this.dimensions.WIDTH,
320 this.dimensions.HEIGHT, Runner.classes.PLAYER);
321
322 this.canvasCtx = this.canvas.getContext('2d');
323 this.canvasCtx.fillStyle = '#f7f7f7';
324 this.canvasCtx.fill();
325 Runner.updateCanvasScaling(this.canvas);
326
327 // Horizon contains clouds, obstacles and the ground.
328 this.horizon = new Runner.Horizon(this.canvas, this.images, this.dimensions,
329 this.config.GAP_COEFFICIENT);
330
331 // Distance meter
332 this.distanceMeter = new Runner.DistanceMeter(this.canvas,
333 this.images['TEXT_SPRITE'], this.dimensions.WIDTH);
334
335 // Draw t-rex
336 this.tRex = new Runner.Trex(this.canvas, this.images['TREX']);
337
338 this.outerContainerEl.appendChild(this.containerEl);
339
340 if (Runner.isMobile) {
341 this.createTouchController();
342 }
343
344 this.startListening();
345 this.update();
346 this.loadSounds();
347
348 window.addEventListener(Runner.events.RESIZE,
349 this.debounceResize.bind(this), false);
350 };
351
352
353 /**
354 * Create the touch controller. A div that covers whole screen.
355 */
356 Runner.prototype.createTouchController = function() {
357 this.touchController = document.createElement('div');
358 this.touchController.className = Runner.classes.TOUCH_CONTROLLER;
359 };
360
361
362 /**
363 * Debounce the resize event.
364 */
365 Runner.prototype.debounceResize = function() {
366 if (!this.resizeTimerId_) {
367 this.resizeTimerId_ =
368 setInterval(this.adjustDimensions.bind(this), 250);
369 }
370 };
371
372
373 /**
374 * Adjust game space dimensions on resize.
375 */
376 Runner.prototype.adjustDimensions = function() {
377 clearInterval(this.resizeTimerId_);
378 this.resizeTimerId_ = null;
379
380 var boxStyles = window.getComputedStyle(this.outerContainerEl);
381 var padding = parseInt(boxStyles.paddingLeft.substr(0,
382 boxStyles.paddingLeft.length - 2));
383
384 this.dimensions.WIDTH = this.outerContainerEl.offsetWidth - (padding * 2);
385
386 // Redraw the elements back onto the canvas.
387 if (this.canvas) {
388 this.canvas.width = this.dimensions.WIDTH;
389 this.canvas.height = this.dimensions.HEIGHT;
390
391 Runner.updateCanvasScaling(this.canvas);
392
393 this.distanceMeter.calcXPos(this.dimensions.WIDTH);
394 this.clearCanvas();
395 this.horizon.update(0, 0, true);
396 this.tRex.update(0);
397
398 // Outer container and distance meter.
399 if (this.activated || this.crashed) {
400 this.containerEl.style.width = this.dimensions.WIDTH + 'px';
401 this.containerEl.style.height = this.dimensions.HEIGHT + 'px';
402 this.distanceMeter.update(0, Math.ceil(this.distanceRan));
403 this.stop();
404 } else {
405 this.tRex.draw(0, 0);
406 }
407
408 // Game over panel.
409 if (this.crashed && this.gameOverPanel) {
410 this.gameOverPanel.updateDimensions(this.dimensions.WIDTH);
411 this.gameOverPanel.draw();
412 }
413 }
414 };
415
416
417 /**
418 * Play the game intro.
419 * Canvas container width expands out to the full width.
420 */
421 Runner.prototype.playIntro = function() {
422 if (!this.started && !this.crashed) {
423 this.playingIntro = true;
424 this.tRex.playingIntro = true;
425
426 // CSS animation definition.
427 var keyframes = '@-webkit-keyframes intro { ' +
428 'from { width:' + Runner.Trex.config.WIDTH + 'px }' +
429 'to { width: ' + this.dimensions.WIDTH + 'px }' +
430 '}';
431 document.styleSheets[0].insertRule(keyframes, 0);
432
433 this.containerEl.addEventListener(Runner.events.ANIM_END,
434 this.startGame.bind(this));
435
436 this.containerEl.style.webkitAnimation = 'intro .4s ease-out 1 both';
437 this.containerEl.style.width = this.dimensions.WIDTH + 'px';
438
439 if (this.touchController) {
440 this.outerContainerEl.appendChild(this.touchController);
441 }
442 this.activated = true;
443 this.started = true;
444 } else if (this.crashed) {
445 this.restart();
446 }
447 };
448
449
450 /**
451 * Update the game status to started.
452 */
453 Runner.prototype.startGame = function() {
454 this.runningTime = 0;
455 this.playingIntro = false;
456 this.tRex.playingIntro = false;
457 this.containerEl.style.webkitAnimation = '';
458 this.playCount++;
459
460 // Handle tabbing off the page. Pause the current game.
461 window.addEventListener(Runner.events.VISIBILITY,
462 this.onVisibilityChange.bind(this), false);
463
464 window.addEventListener(Runner.events.BLUR,
465 this.onVisibilityChange.bind(this), false);
466
467 window.addEventListener(Runner.events.FOCUS,
468 this.onVisibilityChange.bind(this), false);
469 };
470
471
472 /**
473 * Clear the whole canvas.
474 */
475 Runner.prototype.clearCanvas = function() {
476 this.canvasCtx.clearRect(0, 0, this.dimensions.WIDTH, this.dimensions.HEIGHT);
477 };
478
479
480 /**
481 * Update the game frame.
482 */
483 Runner.prototype.update = function() {
484 this.drawPending = false;
485
486 var now = new Date().getTime();
arv (Not doing code reviews) 2014/09/15 17:35:44 Date.now but you are better of using performance.n
edwardjung 2014/09/16 13:14:32 Thanks for the tip. Switched to performance.now.
487 var deltaTime = now - (this.time || now);
488 this.time = now;
489
490 // Chrome Tracer
491 // if (deltaTime > 17) {
492 // console.time('update');
493 // }
494
495 if (this.activated) {
496 this.clearCanvas();
497
498 if (this.tRex.jumping) {
499 this.tRex.updateJump(deltaTime, this.config);
arv (Not doing code reviews) 2014/09/15 17:35:44 wrong indent
edwardjung 2014/09/16 13:14:31 Done.
500 }
501
502 this.runningTime += deltaTime;
503 var hasObstacles = this.runningTime > this.config.CLEAR_TIME;
504
505 // First jump triggers the intro.
506 if (this.tRex.jumpCount == 1 && !this.playingIntro) {
507 this.playIntro();
508 }
509
510 // The horizon doesn't move until the intro is over.
511 if (this.playingIntro) {
512 this.horizon.update(0, this.currentSpeed, hasObstacles);
513 } else {
514 deltaTime = !this.started ? 0 : deltaTime;
515 this.horizon.update(deltaTime, this.currentSpeed, hasObstacles);
516 }
517
518 // Check for collisions.
519 var collision = hasObstacles ?
520 Runner.Collision.check(this.horizon.obstacles[0], this.tRex) : false;
521
522 if (!collision) {
523 this.distanceRan += this.currentSpeed * (deltaTime / this.msPerFrame);
524
525 if (this.currentSpeed < this.config.MAX_SPEED) {
526 this.currentSpeed += this.config.ACCELERATION;
527 }
528 } else {
529 this.gameOver();
530 }
531
532 if (this.distanceMeter.getActualDistance(this.distanceRan) >
533 this.distanceMeter.maxScore) {
534 this.distanceRan = 0;
535 }
536
537 this.distanceMeter.update(deltaTime, Math.ceil(this.distanceRan),
538 this.soundFx.SCORE);
539 }
540
541 if (!this.crashed) {
542 this.tRex.update(deltaTime);
543 this.raq();
544 }
545 // if (deltaTime > 17) {
546 // console.timeEnd('update');
547 // }
548 };
549
550
551 /**
552 * Updates the canvas size taking into
553 * account the backing store pixel ratio and
554 * the device pixel ratio.
555 *
556 * See article by Paul Lewis:
557 * http://www.html5rocks.com/en/tutorials/canvas/hidpi/
558 *
559 * @param {HTMLCanvasElement} canvas Canvas to scale.
560 * @param {number} opt_width Canvas to scale.
561 * @param {number} opt_width Canvas to scale.
562 * @return {boolean} Whether the canvas was scaled.
563 */
564 Runner.updateCanvasScaling = function(canvas, opt_width, opt_height) {
565
566 var context = canvas.getContext('2d');
567
568 // Query the various pixel ratios
569 var devicePixelRatio = Math.floor(window.devicePixelRatio) || 1;
570 var backingStoreRatio = Math.floor(context.webkitBackingStorePixelRatio) || 1;
571 var ratio = devicePixelRatio / backingStoreRatio;
572
573 // Upscale the canvas if the two ratios don't match
574 if (devicePixelRatio !== backingStoreRatio) {
575
576 var oldWidth = opt_width || canvas.width;
577 var oldHeight = opt_height || canvas.height;
578
579 canvas.width = oldWidth * ratio;
580 canvas.height = oldHeight * ratio;
581
582 canvas.style.width = oldWidth + 'px';
583 canvas.style.height = oldHeight + 'px';
584
585 // Scale the context to counter the fact that we've manually scaled
586 // our canvas element.
587 context.scale(ratio, ratio);
588 return true;
589 }
590 return false;
591 };
592
593
594 /**
595 * Draw the collision boxes on canvas, for debugging.
596 * @param {Runner.CollisionBox} collision T-Rex collison box.
597 * @param {Runner.CollisionBox} obstacle Obstacle collison box.
598 */
599 Runner.prototype.drawCollisionBoxes = function(collision, obstacle) {
600 this.canvasCtx.save();
601 this.canvasCtx.strokeStyle = '#f00';
602 this.canvasCtx.strokeRect(collision[0].x, collision[0].y,
603 collision[0].width, collision[0].height);
604
605 this.canvasCtx.strokeStyle = '#00f';
606 this.canvasCtx.strokeRect(collision[1].x, collision[1].y,
607 collision[1].width, collision[1].height);
608 this.canvasCtx.restore();
609 };
610
611
612 /**
613 * Event handler.
614 */
615 Runner.prototype.handleEvent = function(e) {
616 switch (e.type) {
617 case Runner.events.KEYDOWN:
arv (Not doing code reviews) 2014/09/15 17:35:44 I would just put all of this code in an IIFE and h
edwardjung 2014/09/16 13:14:30 Something like this?
618 case Runner.events.TOUCHSTART:
619 case Runner.events.MOUSEDOWN:
620 this.onKeyDown(e);
621 break;
622 case Runner.events.KEYUP:
623 case Runner.events.TOUCHEND:
624 case Runner.events.MOUSEUP:
625 this.onKeyUp(e);
626 break;
627 }
628 };
629
630
631 /**
632 * Bind relevant key / mouse / touch listeners.
633 */
634 Runner.prototype.startListening = function() {
635 // Keys.
636 document.addEventListener(Runner.events.KEYDOWN, this);
637 document.addEventListener(Runner.events.KEYUP, this);
638
639 if (Runner.isMobile) {
640 // Mobile only touch devices.
641 this.touchController.addEventListener(Runner.events.TOUCHSTART, this);
642 this.touchController.addEventListener(Runner.events.TOUCHEND, this);
643 this.containerEl.addEventListener(Runner.events.TOUCHSTART, this);
644 } else {
645 // Mouse.
646 document.addEventListener(Runner.events.MOUSEDOWN, this, false);
647 document.addEventListener(Runner.events.MOUSEUP, this, false);
648 }
649 };
650
651
652 /**
653 * Remove all listeners.
654 */
655 Runner.prototype.stopListening = function() {
656 document.removeEventListener(Runner.events.KEYDOWN, this);
657 document.removeEventListener(Runner.events.KEYUP, this);
658
659 if (Runner.isMobile) {
660 this.touchController.removeEventListener(Runner.events.TOUCHSTART, this);
661 this.touchController.removeEventListener(Runner.events.TOUCHEND, this);
662 this.containerEl.removeEventListener(Runner.events.TOUCHSTART, this);
663 } else {
664 document.removeEventListener(Runner.events.MOUSEDOWN, this, false);
665 document.removeEventListener(Runner.events.MOUSEUP, this, false);
666 }
667 };
668
669
670 /**
671 * Process keydown.
672 * @param {Event} e The event object.
673 */
674 Runner.prototype.onKeyDown = function(e) {
675
676 if (!this.crashed && (Runner.keycodes.JUMP[String(e.keyCode)] ||
677 e.type == Runner.events.TOUCHSTART)) {
678 if (!this.activated) {
679 this.activated = true;
680 }
681
682 if (!this.tRex.jumping) {
683 Runner.playSound(this.soundFx.BUTTON_PRESS);
684 this.tRex.startJump();
685 }
686 }
687
688 if (this.crashed && e.type == Runner.events.TOUCHSTART &&
689 e.currentTarget == this.containerEl) {
690 this.restart();
691 }
692
693 // Speed drop, activated only when jump key is not pressed.
694 if (Runner.keycodes.DUCK[String(e.keyCode)] && this.tRex.jumping) {
arv (Not doing code reviews) 2014/09/15 17:35:43 useless stringify
edwardjung 2014/09/16 13:14:31 Done.
695 e.preventDefault();
696 this.tRex.setSpeedDrop();
697 }
698 };
699
700
701 /**
702 * Process key up.
703 * @param {Event} e The event object.
704 */
705 Runner.prototype.onKeyUp = function(e) {
706 var keyCode = String(e.keyCode);
707 var isjumpKey = Runner.keycodes.JUMP[keyCode] ||
708 e.type == Runner.events.TOUCHEND ||
709 e.type == Runner.events.MOUSEDOWN;
710
711 if (this.isRunning() && isjumpKey) {
712 this.tRex.endJump();
713 } else if (Runner.keycodes.DUCK[keyCode]) {
714 this.tRex.speedDrop = false;
715 } else if (this.crashed) {
716 // Check that enough time has elapsed before allowing jump key to restart.
717 var deltaTime = new Date().getTime() - this.time;
718
719 if (Runner.keycodes.RESTART[keyCode] ||
720 (e.type == Runner.events.MOUSEUP && e.target == this.canvas) ||
721 (deltaTime >= this.config.GAMEOVER_CLEAR_TIME && Runner.keycodes.JUMP[key Code])) {
722 this.restart();
723 }
724 } else if (this.paused && isjumpKey) {
725 this.play();
726 }
727 };
728
729
730 /**
731 * RequestAnimationFrame wrapper.
732 */
733 Runner.prototype.raq = function() {
734 if (!this.drawPending) {
735 this.drawPending = true;
736 this.raqId = requestAnimationFrame(this.update.bind(this));
737 }
738 };
739
740
741 /**
742 * Whether the game is running.
743 * @return {boolean}
744 */
745 Runner.prototype.isRunning = function() {
746 return this.raqId ? true : false;
747 };
748
749
750 /**
751 * Get random number.
752 * @param {number} min Minimum number.
753 * @param {number} max Maximum number.
754 * @param {number} A random number.
755 */
756 Runner.getRandomNum = function(min, max) {
757 return Math.floor(Math.random() * (max - min + 1)) + min;
758 };
759
760
761 Runner.vibrate = function(duration) {
762 if (Runner.isMobile) {
763 window.navigator['vibrate'](duration);
764 }
765 };
766
767 /**
768 * Game over state.
769 */
770 Runner.prototype.gameOver = function() {
771 Runner.playSound(this.soundFx.HIT);
772 Runner.vibrate(200);
773
774 this.stop();
775 this.crashed = true;
776 this.distanceMeter.acheivement = false;
777
778 this.tRex.update(100, Runner.Trex.status.CRASHED);
779
780 // Game over panel.
781 if (!this.gameOverPanel) {
782 this.gameOverPanel = new Runner.GameOverPanel(this.canvas,
783 this.images['TEXT_SPRITE'], this.images['RESTART'],
784 this.dimensions);
785 } else {
786 this.gameOverPanel.draw();
787 }
788
789 // Update the high score.
790 if (this.distanceRan > this.highestScore) {
791 this.highestScore = Math.ceil(this.distanceRan);
792 this.distanceMeter.setHighScore(this.highestScore);
793 }
794
795 // Reset the time clock.
796 this.time = new Date().getTime();
797 };
798
799
800 /**
801 * Stop the game.
802 */
803 Runner.prototype.stop = function() {
804 this.activated = false;
805 this.paused = true;
806 cancelAnimationFrame(this.raqId);
807 this.raqId = 0;
808 };
809
810
811 /**
812 * Play the game.
813 */
814 Runner.prototype.play = function() {
815 if (!this.crashed) {
816 this.activated = true;
817 this.paused = false;
818 this.tRex.update(0, Runner.Trex.status.RUNNING);
819 this.time = new Date().getTime();
820 this.update();
821 }
822 };
823
824
825 /**
826 * Restart game.
827 */
828 Runner.prototype.restart = function() {
829 if (!this.raqId) {
830 this.playCount++;
831 this.runningTime = 0;
832 this.activated = true;
833 this.crashed = false;
834 this.distanceRan = 0;
835 this.setSpeed(this.config.SPEED);
836
837 this.time = new Date().getTime();
838 this.containerEl.classList.remove(Runner.classes.CRASHED);
839 this.clearCanvas();
840 this.distanceMeter.reset(this.highestScore);
841 this.horizon.reset();
842 this.tRex.reset();
843 Runner.playSound(this.soundFx.BUTTON_PRESS);
844
845 this.update();
846 }
847 };
848
849
850 /**
851 * Pause the game if the tab is not in focus.
852 */
853 Runner.prototype.onVisibilityChange = function(e) {
854 if (document.hidden || document.webkitHidden || e.type == 'blur') {
855 this.stop();
856 } else {
857 this.play();
858 }
859 };
860
861
862 /**
863 * Whether the game is running.
864 * @return {boolean}
865 */
866 Runner.prototype.isRunning = function() {
867 return this.raqId ? true : false;
arv (Not doing code reviews) 2014/09/15 17:35:43 return !!this.raqId;
edwardjung 2014/09/16 13:14:30 Done.
868 };
869
870
871 /**
872 * Create canvas element.
873 * @param {HTMLElement} container Container element to append canvas to.
874 * @param {number} width Width of canvas.
875 * @param {number} height Height of canvas
876 * @param {string} opt_classname Optional class name.
877 * @return {HTMLCanvasElement} The canvas element.
878 */
879 Runner.createCanvas = function(container, width, height, opt_classname) {
880 var canvas = document.createElement('canvas');
881 canvas.className = opt_classname ? Runner.classes.CANVAS + ' ' +
882 opt_classname : Runner.classes.CANVAS;
883 canvas.width = width;
884 canvas.height = height;
885 container.appendChild(canvas);
886
887 return canvas;
888 };
889
890
891 /**
892 * Play a sound.
893 * @param {SoundBuffer} soundBuffer
894 */
895 Runner.playSound = function(soundBuffer) {
896 if (soundBuffer) {
897 var sourceNode = Runner.audioContext['createBufferSource']();
898 sourceNode.buffer = soundBuffer;
899 sourceNode['connect'](Runner.audioContext['destination']);
900 sourceNode['start'](0);
901 }
902 };
903
904
905 /**
906 * Decodes the base 64 audio to ArrayBuffer used by Web Audio.
907 */
908 Runner.decodeBase64ToArrayBuffer = function(base64String) {
909
910 var len = (base64String.length / 4) * 3;
911 var str = atob(base64String);
912 var arrayBuffer = new ArrayBuffer(len);
913 var bytes = new Uint8Array(arrayBuffer);
914
915 for (var i = 0; i < len; i++) {
916 bytes[i] = str.charCodeAt(i);
917 }
918 return bytes.buffer;
919 };
920
921
922 Runner.test = {};
923
924 /**
925 * Test for mobile platform.
926 * @return {boolean} Whether the platform is mobile.
927 */
928 Runner.test.mobile = function() {
929 return window.navigator.userAgent.indexOf('Mobi') > -1;
930 };
931
932 /**
933 * Test for hi DPI.
934 * @return {boolean} Whether the device has a high device pixel ratio.
935 */
936 Runner.test.hiDPI = function() {
937 return window.devicePixelRatio > 1;
938 };
939
940
941 /**
942 * Test for touch.
943 * @return {boolean} Whether touch support is available.
944 */
945 Runner.test.touchEnabled = function() {
946 return 'ontouchstart' in window;
947 };
948
949
950 Runner.isHDPI = Runner.test.hiDPI();
951
952 Runner.isMobile = Runner.test.mobile();
953
954 Runner.isTouchEnabled = Runner.test.touchEnabled();
955
956
957 //******************************************************************************
958
959
960 /**
961 * Game over panel.
962 * @param {!HTMLCanvasElement} canvas Canvas element to draw on.
963 * @param {!HTMLImage} textSprite Text sprite Image
964 * @param {!HTMLImage} restartImg Restart image.
965 * @param {!Object} dimensions Dimensions of the canvas.
966 * @constructor
967 */
968 Runner.GameOverPanel = function(canvas, textSprite, restartImg, dimensions) {
969 this.canvas = canvas;
970
971 this.canvasCtx = canvas.getContext('2d');
972
973 this.canvasDimensions = dimensions;
974
975 this.textSprite = textSprite;
976
977 this.restartImg = restartImg;
978
979 this.draw();
980 };
981
982
983 /**
984 * Dimensions used in the panel.
985 * @enum {string}
986 */
987 Runner.GameOverPanel.dimensions = {
988 TEXT_X: 0,
989 TEXT_Y: 13,
990 TEXT_WIDTH: 191,
991 TEXT_HEIGHT: 11,
992 RESTART_WIDTH: 36,
993 RESTART_HEIGHT: 32
994 };
995
996
997 /**
998 * Update the panel dimensions.
999 * @param {number} width New canvas width.
1000 * @param {number} opt_height Optional new canvas height.
1001 */
1002 Runner.GameOverPanel.prototype.updateDimensions = function(width, opt_height) {
1003 this.canvasDimensions.WIDTH = width;
1004 if (opt_height) {
1005 this.canvasDimensions.HEIGHT = opt_height;
1006 }
1007 };
1008
1009
1010 /**
1011 * Draw the panel.
1012 */
1013 Runner.GameOverPanel.prototype.draw = function() {
1014 var dimensions = Runner.GameOverPanel.dimensions;
1015
1016 var centerX = this.canvasDimensions.WIDTH / 2;
1017
1018 // Game over text.
1019 var textSourceX = dimensions.TEXT_X;
1020 var textSourceY = dimensions.TEXT_Y;
1021 var textSourceWidth = dimensions.TEXT_WIDTH;
1022 var textSourceHeight = dimensions.TEXT_HEIGHT;
1023
1024 var textTargetX = Math.round(centerX - (dimensions.TEXT_WIDTH / 2));
1025 var textTargetY = Math.round((this.canvasDimensions.HEIGHT - 25) / 3);
1026 var textTargetWidth = dimensions.TEXT_WIDTH;
1027 var textTargetHeight = dimensions.TEXT_HEIGHT;
1028
1029 var restartSourceWidth = dimensions.RESTART_WIDTH;
1030 var restartSourceHeight = dimensions.RESTART_HEIGHT;
1031 var restartTargetX = centerX - (dimensions.RESTART_WIDTH / 2);
1032 var restartTargetY = this.canvasDimensions.HEIGHT / 2;
1033
1034
1035 if (Runner.isHDPI) {
1036 textSourceY *= 2;
1037 textSourceX *= 2;
1038 textSourceWidth = textSourceWidth * 2;
1039 textSourceHeight = textSourceHeight * 2;
1040 restartSourceWidth = restartSourceWidth * 2;
1041 restartSourceHeight = restartSourceHeight * 2;
1042 }
1043
1044 // Game over text from sprite.
1045 this.canvasCtx.drawImage(this.textSprite,
1046 textSourceX, textSourceY, textSourceWidth, textSourceHeight,
1047 textTargetX, textTargetY, textTargetWidth, textTargetHeight);
1048
1049 // Restart button.
1050 this.canvasCtx.drawImage(this.restartImg, 0, 0,
1051 restartSourceWidth, restartSourceHeight,
1052 restartTargetX, restartTargetY, dimensions.RESTART_WIDTH,
1053 dimensions.RESTART_HEIGHT);
1054 };
1055
1056
1057 //******************************************************************************
1058
1059 /**
1060 * Collision checker utilities.
1061 */
1062 Runner.Collision = function() {};
1063
1064
1065 /**
1066 * Check for a collision.
1067 * @param {!Runner.Obstacle} obstacle Obstacle object.
1068 * @param {!Runner.Trex} tRex T-rex object.
1069 * @param {HTMLCanvasContext} opt_canvasCtx Optional canvas context for drawing
1070 * collision boxes.
1071 * @return {Array.<Runner.CollisionBox>}
1072 */
1073 Runner.Collision.check = function(obstacle, tRex, opt_canvasCtx) {
1074 var obstacleBoxXPos = Runner.defaultDimensions.WIDTH + obstacle.xPos;
1075
1076 // Adjustments are made to the bounding box as there is a 1 pixel white
1077 // border around the t-rex and obstacles.
1078 var tRexBox = new Runner.CollisionBox(
1079 tRex.xPos + 1,
1080 tRex.yPos + 1,
1081 tRex.config.WIDTH - 2,
1082 tRex.config.HEIGHT - 2
1083 );
1084
1085 var obstacleBox = new Runner.CollisionBox(
1086 obstacle.xPos + 1,
1087 obstacle.yPos + 1,
1088 obstacle.typeConfig.width * obstacle.size - 2,
1089 obstacle.typeConfig.height - 2
1090 );
1091
1092 // Debug outer box
1093 if (opt_canvasCtx) {
1094 Runner.Collision.drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox);
1095 }
1096
1097 // Simple outer bounds check.
1098 if (Runner.Collision.boxCompare(tRexBox, obstacleBox)) {
1099 var collisionBoxes = obstacle.collisionBoxes;
1100 var tRexCollisionBoxes = Runner.Trex.collisionBoxes;
1101
1102 // Detailed axis aligned box check.
1103 for (var t = 0, l = tRexCollisionBoxes.length; t < l; t++) {
1104 for (var i = 0, j = collisionBoxes.length; i < j; i++) {
1105 // Adjust the box to actual positions.
1106 var adjTrexBox =
1107 Runner.Collision.createAdjustedCollisionBox(tRexCollisionBoxes[t],
1108 tRexBox);
1109 var adjObstacleBox =
1110 Runner.Collision.createAdjustedCollisionBox(collisionBoxes[i],
1111 obstacleBox);
1112
1113 var crashed = Runner.Collision.boxCompare(adjTrexBox, adjObstacleBox);
1114
1115 // Draw boxes for debug.
1116 if (opt_canvasCtx) {
1117 Runner.Collision.drawCollisionBoxes(opt_canvasCtx,
1118 adjTrexBox, adjObstacleBox);
1119 }
1120
1121 if (crashed) {
1122 return [adjTrexBox, adjObstacleBox];
1123 }
1124 }
1125 }
1126 }
1127 return false;
1128 };
1129
1130
1131 /**
1132 * Adjust the collision box.
1133 * @param {!Runner.CollisionBox} box The original box.
1134 * @param {!Runner.CollisionBox} adjustment Adjustment box.
1135 * @return {Runner.CollisionBox} The adjusted collision box object.
1136 */
1137 Runner.Collision.createAdjustedCollisionBox = function(box, adjustment) {
1138 return new Runner.CollisionBox(
1139 box.x + adjustment.x,
1140 box.y + adjustment.y,
1141 box.width,
1142 box.height);
1143 };
1144
1145
1146 /**
1147 * Draw the collision box for debug.
1148 */
1149 Runner.Collision.drawCollisionBoxes =
1150 function(canvasCtx, tRexBox, obstacleBox) {
1151 canvasCtx.save();
1152 canvasCtx.strokeStyle = '#f00';
1153 canvasCtx.strokeRect(tRexBox.x, tRexBox.y,
1154 tRexBox.width, tRexBox.height);
1155
1156 canvasCtx.strokeStyle = '#0f0';
1157 canvasCtx.strokeRect(obstacleBox.x, obstacleBox.y,
1158 obstacleBox.width, obstacleBox.height);
1159 canvasCtx.restore();
1160 };
1161
1162
1163 /**
1164 * Compare two collision boxes for a collision.
1165 * @param {Runner.CollisionBox} tRexBox
1166 * @param {Runner.CollisionBox} obstacleBox
1167 * @return {boolean} Whether the boxes intersected.
1168 */
1169 Runner.Collision.boxCompare = function(tRexBox, obstacleBox) {
1170 var crashed = false;
1171 var tRexBoxX = tRexBox.x;
1172 var tRexBoxY = tRexBox.y;
1173
1174 var obstacleBoxX = obstacleBox.x;
1175 var obstacleBoxY = obstacleBox.y;
1176
1177 // Axis-Aligned Bounding Box method.
1178 if (tRexBox.x < obstacleBoxX + obstacleBox.width &&
1179 tRexBox.x + tRexBox.width > obstacleBoxX &&
1180 tRexBox.y < obstacleBox.y + obstacleBox.height &&
1181 tRexBox.height + tRexBox.y > obstacleBox.y) {
1182 crashed = true;
1183 }
1184
1185 return crashed;
1186 };
1187
1188
1189 //******************************************************************************
1190
1191 /**
1192 * Collision box object.
1193 * @param {number} x X position.
1194 * @param {number} y Y Position.
1195 * @param {number} w Width.
1196 * @param {number} h Height.
1197 */
1198 Runner.CollisionBox = function(x, y, w, h) {
1199 this.x = x;
1200 this.y = y;
1201 this.width = w;
1202 this.height = h;
1203 };
1204
1205
1206 //******************************************************************************
1207
1208 /**
1209 * Obstacle.
1210 * @param {HTMLCanvasCtx} canvasCtx Canvas context.
1211 * @param {Runner.Obstacle.type} type Type of object.
1212 * @param {image} obstacleImg Image sprite to use obstacle.
1213 * @param {Object} dimensions Obstacle dimensions.
1214 * @param {number} gapCoefficient Mutipler in determining the gap.
1215 * @param {number} speed Current game speed.
1216 */
1217 Runner.Obstacle = function(canvasCtx, type, obstacleImg, dimensions,
1218 gapCoefficient, speed) {
1219
1220 this.canvasCtx = canvasCtx;
1221
1222 this.image = obstacleImg;
1223
1224 this.typeConfig = type;
1225
1226 this.gapCoefficient = gapCoefficient;
1227
1228 this.size = Runner.getRandomNum(1, Runner.Obstacle.MAX_OBSTACLE_LENGTH);
1229
1230 this.dimensions = dimensions;
1231
1232 this.remove = false;
1233
1234 this.xPos = 0;
1235
1236 this.yPos = this.typeConfig.yPos;
1237
1238 this.width = 0;
1239
1240 this.collisionBoxes = [];
1241
1242 this.gap = 0;
1243
1244 this.init(speed);
1245 };
1246
1247 /**
1248 *
1249 */
1250 Runner.Obstacle.MAX_GAP_COEFFICIENT = 1.5;
1251
1252 /**
1253 * Maximum obstacle grouping count.
1254 */
1255 Runner.Obstacle.MAX_OBSTACLE_LENGTH = 3,
1256
1257
1258 /**
1259 * Initialise the DOM for the obstacle.
1260 * @param {number} speed
1261 */
1262 Runner.Obstacle.prototype.init = function(speed) {
1263 this.cloneCollisionBoxes();
1264
1265 // Only allow sizing if we're at the right speed.
1266 if (this.size > 1 && this.typeConfig.multipleSpeed > speed) {
1267 this.size = 1;
1268 }
1269
1270 this.width = this.typeConfig.width * this.size;
1271
1272 this.xPos = this.dimensions.WIDTH - this.width;
1273
1274 this.draw();
1275
1276 // Make collision box adjustments,
1277 // Central box is adjusted to the size as one box.
1278 // ____ ______ ________
1279 // _| |-| _| |-| _| |-|
1280 // | |<->| | | |<--->| | | |<----->| |
1281 // | | 1 | | | | 2 | | | | 3 | |
1282 // |_|___|_| |_|_____|_| |_|_______|_|
1283 //
1284 if (this.size > 1) {
1285 this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width -
1286 this.collisionBoxes[2].width;
1287 this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width;
1288 }
1289
1290 this.gap = this.getGap(this.gapCoefficient, speed);
1291 };
1292
1293
1294 /**
1295 * Draw and crop based on size.
1296 */
1297 Runner.Obstacle.prototype.draw = function() {
1298 // this.canvasCtx.save();
1299
1300 var sourceWidth = this.typeConfig.width;
1301 var sourceHeight = this.typeConfig.height;
1302
1303 if (Runner.isHDPI) {
1304 sourceWidth = sourceWidth * 2;
1305 sourceHeight = sourceHeight * 2;
1306 }
1307
1308 // Sprite
1309 var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1));
1310 this.canvasCtx.drawImage(this.image,
1311 sourceX, 0,
1312 sourceWidth * this.size, sourceHeight,
1313 this.xPos, this.yPos,
1314 this.typeConfig.width * this.size, this.typeConfig.height);
1315 // this.canvasCtx.restore();
1316 };
1317
1318
1319 /**
1320 * Obstacle frame update.
1321 * @param {number} deltaTime Time in ms.
1322 * @param {number} speed Current speed.
1323 */
1324 Runner.Obstacle.prototype.update = function(deltaTime, speed) {
1325 if (!this.remove) {
1326 this.xPos -= Math.floor((speed * Runner.FPS / 1000) * deltaTime);
1327 this.draw();
1328
1329 if (!this.isVisible()) {
1330 this.remove = true;
1331 }
1332 }
1333 };
1334
1335
1336 /**
1337 * Calculate a random gap size.
1338 * - Minimum gap gets wider as speed increses
1339 * @param {number} gapCoefficient
1340 * @param {number} speed Current speed.
1341 * @return {number} The gap size.
1342 */
1343 Runner.Obstacle.prototype.getGap = function(gapCoefficient, speed) {
1344 var minGap = Math.round((this.width * speed) +
1345 (this.typeConfig.minGap * gapCoefficient));
1346 var maxGap = Math.round(minGap * Runner.Obstacle.MAX_GAP_COEFFICIENT);
1347 return Runner.getRandomNum(minGap, maxGap);
1348 };
1349
1350
1351
1352 /**
1353 * Check if obstacle is visible.
1354 * @return {boolean} Whether the obstacle is in the game area.
1355 */
1356 Runner.Obstacle.prototype.isVisible = function() {
1357 return (this.xPos + this.width) > 0;
1358 };
1359
1360
1361 /**
1362 * Make a copy of the collision boxes, since these will change based on
1363 * obstacle type and size.
1364 */
1365 Runner.Obstacle.prototype.cloneCollisionBoxes = function() {
1366 var collisionBoxes = this.typeConfig.collisionBoxes;
1367
1368 for (var i = collisionBoxes.length - 1; i >= 0; i--) {
1369 this.collisionBoxes[i] = new Runner.CollisionBox(collisionBoxes[i].x,
1370 collisionBoxes[i].y, collisionBoxes[i].width, collisionBoxes[i].height);
1371 }
1372 };
1373
1374
1375 /**
1376 * Obstacle definitions.
1377 * minGap: minimum pixel space betweeen obstacles.
1378 * multipleSpeed: Speed at which multiples are allowed.
1379 */
1380 Runner.Obstacle.types = [
1381 {
1382 type: 'CACTUS_SMALL',
1383 className: ' cactus cactus-small ',
1384 width: 17,
1385 height: 35,
1386 yPos: 105,
1387 multipleSpeed: 3,
1388 minGap: 120,
1389 collisionBoxes: [
1390 new Runner.CollisionBox(0, 7, 5, 27),
1391 new Runner.CollisionBox(4, 0, 6, 34),
1392 new Runner.CollisionBox(10, 4, 7, 14)
1393 ]
1394 },
1395 {
1396 type: 'CACTUS_LARGE',
1397 className: ' cactus cactus-large ',
1398 width: 25,
1399 height: 50,
1400 yPos: 90,
1401 multipleSpeed: 6,
1402 minGap: 120,
1403 collisionBoxes: [
1404 new Runner.CollisionBox(0, 12, 7, 38),
1405 new Runner.CollisionBox(8, 0, 7, 49),
1406 new Runner.CollisionBox(13, 10, 10, 38)
1407 ]
1408 }
1409 ];
1410
1411
1412 //******************************************************************************
1413 /**
1414 * T-rex game character.
1415 * @param {HTMLCanvas} canvas HTML Canvas.
1416 * @param {HTMLImage} image Character image.
1417 * @constructor
1418 */
1419 Runner.Trex = function(canvas, image) {
1420 this.canvas = canvas;
1421 this.canvasCtx = canvas.getContext('2d');
1422
1423 this.image = image;
1424
1425 this.xPos = 0;
1426
1427 this.yPos = 0;
1428
1429 // Position when on the ground.
1430 this.groundYPos = 0;
1431
1432 this.currentFrame = 0;
1433
1434 this.currentAnimFrames = [];
1435
1436 this.blinkDelay = 0;
1437
1438 this.animStartTime = 0;
1439
1440 this.timer = 0;
1441
1442 this.msPerFrame = 1000 / Runner.FPS;
1443
1444 this.config = Runner.Trex.config;
1445
1446 // Current status.
1447 this.status = Runner.Trex.status.WAITING;
1448
1449 this.jumping = false;
1450
1451 this.jumpVelocity = 0;
1452
1453 this.reachedMinHeight = false;
1454
1455 this.speedDrop = false;
1456
1457 this.jumpCount = 0;
1458
1459 this.jumpspotX = 0;
1460
1461 this.init();
1462 };
1463
1464
1465 /**
1466 * T-rex player config.
1467 */
1468 Runner.Trex.config = {
1469 DROP_VELOCITY: -5,
1470 GRAVITY: 0.6,
1471 HEIGHT: 47,
1472 INIITAL_JUMP_VELOCITY: -10,
1473 INTRO_DURATION: 1500,
1474 MAX_JUMP_HEIGHT: 30,
1475 MIN_JUMP_HEIGHT: 30,
1476 SPEED_DROP_COEFFICIENT: 3,
1477 SPRITE_WIDTH: 262,
1478 START_X_POS: 50,
1479 WIDTH: 44
1480 };
1481
1482
1483 /**
1484 * Used in collision detection.
1485 * @type {Array.<Runner.CollisionBox>}
1486 */
1487 Runner.Trex.collisionBoxes = [
1488 new Runner.CollisionBox(1, -1, 30, 26),
1489 new Runner.CollisionBox(32, 0, 8, 16),
1490 new Runner.CollisionBox(10, 35, 14, 8),
1491 new Runner.CollisionBox(1, 24, 29, 5),
1492 new Runner.CollisionBox(5, 30, 21, 4),
1493 new Runner.CollisionBox(9, 34, 15, 4)
1494 ];
1495
1496
1497 /**
1498 * Animation states.
1499 * @enum {string}
1500 */
1501 Runner.Trex.status = {
1502 CRASHED: 'crashed',
1503 JUMPING: 'jumping',
1504 RUNNING: 'running',
1505 WAITING: 'waiting'
1506 };
1507
1508 // Blinking coefficient.
1509 Runner.Trex.BLINK_TIMING = 7000;
1510
1511
1512 /**
1513 * Animation config for different states.
1514 */
1515 Runner.Trex.animFrames = {
1516 'waiting': {
1517 frames: [44, 0],
1518 msPerFrame: 1000 / 3
1519 },
1520 'running': {
1521 frames: [88, 132],
1522 msPerFrame: 1000 / 12
1523 },
1524 'crashed': {
1525 frames: [220],
1526 msPerFrame: 1000 / 60
1527 },
1528 'jumping': {
1529 frames: [0],
1530 msPerFrame: 1000 / 60
1531 }
1532 };
1533
1534 /**
1535 * T-rex player initaliser.
1536 * Sets the t-rex to blink at random intervals.
1537 */
1538 Runner.Trex.prototype.init = function() {
1539 this.blinkDelay = this.setBlinkDelay();
1540 this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT -
1541 Runner.config.BOTTOM_PAD;
1542 this.yPos = this.groundYPos;
1543
1544 this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT;
1545
1546 this.draw(0, 0);
1547 this.update(0, Runner.Trex.status.WAITING);
1548 };
1549
1550
1551 /**
1552 * Setter for the jump velocity.
1553 * The approriate drop velocity is also set.
1554 */
1555 Runner.Trex.prototype.setJumpVelocity = function(setting) {
1556 this.config.INIITAL_JUMP_VELOCITY = -setting;
1557 this.config.DROP_VELOCITY = -setting / 2;
1558 };
1559
1560
1561 /**
1562 * Set the animation status.
1563 * @param {!number} deltaTime Time since last update.
1564 * @param {Runner.Trex.status} status Optional status to switch to.
1565 */
1566 Runner.Trex.prototype.update = function(deltaTime, opt_status) {
1567 this.timer += deltaTime;
1568
1569 // Update the status.
1570 if (opt_status) {
1571 this.status = opt_status;
1572 this.currentFrame = 0;
1573 this.msPerFrame = Runner.Trex.animFrames[opt_status].msPerFrame;
1574 this.currentAnimFrames = Runner.Trex.animFrames[opt_status].frames;
1575
1576 if (opt_status == Runner.Trex.status.WAITING) {
1577 this.animStartTime = new Date().getTime();
1578 this.setBlinkDelay();
1579 }
1580 }
1581
1582 // Game intro animation, t-rex moves in from the left.
1583 if (this.playingIntro && this.xPos < this.config.START_X_POS) {
1584 this.xPos += Math.round((this.config.START_X_POS /
1585 this.config.INTRO_DURATION) * deltaTime);
1586 }
1587
1588 if (this.status == Runner.Trex.status.WAITING) {
1589 this.blink(new Date().getTime());
1590 } else {
1591 this.draw(this.currentAnimFrames[this.currentFrame], 0);
1592 }
1593
1594 // Update the frame position.
1595 if (this.timer >= this.msPerFrame) {
1596 this.currentFrame = this.currentFrame == this.currentAnimFrames.length - 1 ?
1597 0 : this.currentFrame + 1;
1598 this.timer = 0;
1599 }
1600 };
1601
1602
1603 /**
1604 * Draw the t-rex to a particular position.
1605 * @param {number} x X position.
1606 * @param {number} y Y position.
1607 */
1608 Runner.Trex.prototype.draw = function(x, y) {
1609 var sourceX = x;
1610 var sourceY = y;
1611 var sourceWidth = this.config.WIDTH;
1612 var sourceHeight = this.config.HEIGHT;
1613
1614 if (Runner.isHDPI) {
1615 sourceX *= 2;
1616 sourceY *= 2;
1617 sourceWidth *= 2;
1618 sourceHeight *= 2;
1619 }
1620
1621 this.canvasCtx.drawImage(this.image, sourceX, sourceY,
1622 sourceWidth, sourceHeight,
1623 this.xPos, this.yPos,
1624 this.config.WIDTH, this.config.HEIGHT);
1625 };
1626
1627
1628 /**
1629 * Sets a random time for the blink to happen.
1630 */
1631 Runner.Trex.prototype.setBlinkDelay = function() {
1632 this.blinkDelay = Math.ceil(Math.random() * Runner.Trex.BLINK_TIMING);
1633 };
1634
1635 /**
1636 * Make t-rex blink at random intervals.
1637 * @param {number} time Current time in milliseconds.
1638 */
1639 Runner.Trex.prototype.blink = function(time) {
1640 var deltaTime = time - this.animStartTime;
1641
1642 if (deltaTime >= this.blinkDelay) {
1643 this.draw(this.currentAnimFrames[this.currentFrame], 0);
1644
1645 if (this.currentFrame == 1) {
1646 // Set new random delay to blink.
1647 this.setBlinkDelay();
1648 this.animStartTime = time;
1649 }
1650 }
1651 };
1652
1653
1654 /**
1655 * Initialise a jump.
1656 */
1657 Runner.Trex.prototype.startJump = function() {
1658 if (!this.jumping) {
1659 this.update(0, Runner.Trex.status.JUMPING);
1660 this.jumpVelocity = this.config.INIITAL_JUMP_VELOCITY;
1661 this.jumping = true;
1662 this.reachedMinHeight = false;
1663 this.speedDrop = false;
1664 }
1665 };
1666
1667
1668 /**
1669 * Jump is complete, falling down.
1670 */
1671 Runner.Trex.prototype.endJump = function() {
1672 if (this.reachedMinHeight &&
1673 this.jumpVelocity < this.config.DROP_VELOCITY) {
1674 this.jumpVelocity = this.config.DROP_VELOCITY;
1675 }
1676 };
1677
1678
1679 /**
1680 * Update frame for a jump.
1681 * @param {number} deltaTime Time since last update.
1682 */
1683 Runner.Trex.prototype.updateJump = function(deltaTime) {
1684 var msPerFrame = Runner.Trex.animFrames[this.status].msPerFrame;
1685 var framesElapsed = deltaTime / msPerFrame;
1686
1687 // Speed drop makes Trex fall faster.
1688 if (this.speedDrop) {
1689 this.yPos += Math.round(this.jumpVelocity *
1690 this.config.SPEED_DROP_COEFFICIENT * framesElapsed);
1691 } else {
1692 this.yPos += Math.round(this.jumpVelocity * framesElapsed);
1693 }
1694
1695 this.jumpVelocity += this.config.GRAVITY * framesElapsed;
1696
1697 // Minimum height has been reached.
1698 if (this.yPos < this.minJumpHeight || this.speedDrop) {
1699 this.reachedMinHeight = true;
1700 }
1701
1702 // Reached max height
1703 if (this.yPos < this.config.MAX_JUMP_HEIGHT || this.speedDrop) {
1704 this.endJump();
1705 }
1706
1707 // Back down at ground level. Jump completed.
1708 if (this.yPos > this.groundYPos) {
1709 this.reset();
1710 this.jumpCount++;
1711 }
1712
1713 this.update(deltaTime);
1714 };
1715
1716
1717 /**
1718 * Set the speed drop. Immediately cancels the current jump.
1719 */
1720 Runner.Trex.prototype.setSpeedDrop = function() {
1721 this.tRex.speedDrop = true;
1722 this.tRex.jumpVelocity = 1;
1723 };
1724
1725
1726 /**
1727 * Reset the t-rex to running at start of game.
1728 */
1729 Runner.Trex.prototype.reset = function() {
1730 this.yPos = this.groundYPos;
1731 this.jumpVelocity = 0;
1732 this.jumping = false;
1733 this.update(0, Runner.Trex.status.RUNNING);
1734 this.midair = false;
1735 this.speedDrop = false;
1736 this.jumpCount = 0;
1737 };
1738
1739
1740 //******************************************************************************
1741
1742 /**
1743 * Handles displaying the distance meter.
1744 * @param {!HTMLCanvasElement} canvas Canvas element to draw to.
1745 * @param {!HTMLImage} spriteSheet Image sprite.
1746 * @param {number} width Width of the container
1747 * @constructor
1748 */
1749 Runner.DistanceMeter = function(canvas, spriteSheet, canvasWidth) {
1750 this.canvas = canvas;
1751
1752 this.canvasCtx = canvas.getContext('2d');
1753
1754 this.image = spriteSheet;
1755
1756 this.x = 0;
1757
1758 this.y = 5;
1759
1760 this.currentDistance = 0;
1761
1762 this.maxScore = 0;
1763
1764 this.highScore = 0;
1765
1766 this.container = null;
1767
1768 this.digits = [];
1769
1770 this.acheivement = false;
1771
1772 this.defaultString = '';
1773
1774 this.flashTimer = 0;
1775
1776 this.flashIterations = 0;
1777
1778 this.init(canvasWidth);
1779 };
1780
1781
1782 Runner.DistanceMeter.dimensions = {
1783 WIDTH: 10,
1784 HEIGHT: 13,
1785 DEST_WIDTH: 11
1786 };
1787
1788
1789 /**
1790 * Y positioning of the digits in the sprite sheet.
1791 * X position is always 0.
1792 */
1793 Runner.DistanceMeter.yPos = [0, 13, 27, 40, 53, 67, 80, 93, 107, 120];
1794
1795 /**
1796 * Number of digits.
1797 */
1798 Runner.DistanceMeter.MAX_DISTANCE_UNITS = 5;
1799
1800 /**
1801 * Distance that causes achievement animation.
1802 */
1803 Runner.DistanceMeter.ACHIEVEMENT_DISTANCE = 100;
1804
1805 /**
1806 * Used for conversion from pixel distance to a scaled unit.
1807 */
1808 Runner.DistanceMeter.COEFFICIENT = 0.025;
1809
1810 /**
1811 * Flash duration in milliseconds.
1812 */
1813 Runner.DistanceMeter.FLASH_DURATION = 1000 / 4;
1814
1815 /**
1816 * Flash iterations for achievement animation.
1817 */
1818 Runner.DistanceMeter.FLASH_ITERATIONS = 3;
1819
1820
1821 /**
1822 * Initialise the distance meter to '00000'.
1823 */
1824 Runner.DistanceMeter.prototype.init = function(width) {
1825 var maxDistanceStr = '';
1826
1827 this.calcXPos(width);
1828 this.maxScore = Runner.DistanceMeter.MAX_DISTANCE_UNITS;
1829 for (var i = 0; i < Runner.DistanceMeter.MAX_DISTANCE_UNITS; i++) {
1830 this.draw(i, 0);
1831 this.defaultString += '0';
1832 maxDistanceStr += '9';
1833 }
1834
1835 this.maxScore = parseInt(maxDistanceStr);
1836
1837 };
1838
1839
1840 /**
1841 * Calculate the xPos in the canvas.
1842 */
1843 Runner.DistanceMeter.prototype.calcXPos = function(canvasWidth) {
1844 this.x = canvasWidth - (Runner.DistanceMeter.dimensions.DEST_WIDTH *
1845 (Runner.DistanceMeter.MAX_DISTANCE_UNITS + 1));
1846 };
1847
1848
1849 /**
1850 * Draw a digit to canvas.
1851 * @param {number} digitPos Position of the digit.
1852 * @param {number} value Digit value 0-9.
1853 * @param {boolean} opt_highScore Whether we a drawing the high score.
1854 */
1855 Runner.DistanceMeter.prototype.draw = function(digitPos, value, opt_highScore) {
1856 var sourceWidth = Runner.DistanceMeter.dimensions.WIDTH;
1857 var sourceHeight = Runner.DistanceMeter.dimensions.HEIGHT;
1858 var sourceX = (Runner.DistanceMeter.dimensions.WIDTH) * value;
1859
1860 var targetX = (digitPos * (Runner.DistanceMeter.dimensions.DEST_WIDTH));
1861 var targetY = this.y;
1862 var targetWidth = Runner.DistanceMeter.dimensions.WIDTH;
1863 var targetHeight = Runner.DistanceMeter.dimensions.HEIGHT;
1864
1865 // For high DPI we 2x source values.
1866 if (Runner.isHDPI) {
1867 sourceWidth = sourceWidth * 2;
1868 sourceHeight = sourceHeight * 2;
1869 sourceX = sourceX * 2;
1870 }
1871
1872 this.canvasCtx.save();
1873
1874 if (opt_highScore) {
1875 // Left of the current score.
1876 var highScoreX = this.x - ((Runner.DistanceMeter.MAX_DISTANCE_UNITS * 2)) *
1877 Runner.DistanceMeter.dimensions.WIDTH;
1878
1879 this.canvasCtx.translate(highScoreX, this.y);
1880 } else {
1881 this.canvasCtx.translate(this.x, this.y);
1882 }
1883
1884 this.canvasCtx.drawImage(this.image, sourceX, 0,
1885 sourceWidth, sourceHeight,
1886 targetX, targetY,
1887 targetWidth, targetHeight
1888 );
1889
1890 this.canvasCtx.restore();
1891 };
1892
1893
1894 /**
1895 * Covert pixel distance to a 'real' distance.
1896 * @param {number} distance Pixel distance ran.
1897 * @return {number} The 'real' distance ran.
1898 */
1899 Runner.DistanceMeter.prototype.getActualDistance = function(distance) {
1900 return distance ?
1901 Math.round(distance * Runner.DistanceMeter.COEFFICIENT) : 0;
1902 };
1903
1904
1905 /**
1906 * Update the distance meter.
1907 * @param {number} deltaTime Time since last update.
1908 * @param {number} distance Distance to display.
1909 * @param {sound} sound Sound to play.
1910 */
1911 Runner.DistanceMeter.prototype.update = function(deltaTime, distance, sound) {
1912 var paint = true;
1913
1914 if (!this.acheivement) {
1915 distance = this.getActualDistance(distance);
1916
1917 if (distance > 0) {
1918 // Acheivement unlocked
1919 if (distance % Runner.DistanceMeter.ACHIEVEMENT_DISTANCE == 0) {
1920 // Flash score and play sound.
1921 this.acheivement = true;
1922 this.flashTimer = 0;
1923 Runner.playSound(sound);
1924 }
1925
1926 // Create a string representation of the distance with leading 0.
1927 var distanceStr = (this.defaultString +
1928 distance).substr(-Runner.DistanceMeter.MAX_DISTANCE_UNITS);
1929 this.digits = String(distanceStr).split('');
1930 } else {
1931 this.digits = this.defaultString.split('');
1932 }
1933 } else {
1934 // Control flashing of the score on reaching acheivement.
1935 if (this.flashIterations <= Runner.DistanceMeter.FLASH_ITERATIONS) {
1936 this.flashTimer += deltaTime;
1937
1938 if (this.flashTimer < Runner.DistanceMeter.FLASH_DURATION) {
1939 paint = false;
1940 } else if (this.flashTimer > (Runner.DistanceMeter.FLASH_DURATION * 2)) {
1941 this.flashTimer = 0;
1942 this.flashIterations++;
1943 }
1944 } else {
1945 this.acheivement = false;
1946 this.flashIterations = 0;
1947 this.flashTimer = 0;
1948 }
1949 }
1950
1951 // Draw the digits if not flashing.
1952 if (paint) {
1953 for (var i = this.digits.length - 1; i > -1; i--) {
1954 this.draw(i, parseInt(this.digits[i]));
1955 }
1956 }
1957
1958 this.drawHighScore();
1959
1960 return this.acheivement;
1961 };
1962
1963
1964 /**
1965 * Draw the high score.
1966 */
1967 Runner.DistanceMeter.prototype.drawHighScore = function() {
1968 this.canvasCtx.save();
1969 this.canvasCtx.globalAlpha = .8;
1970 for (var i = this.highScore.length - 1; i > -1; i--) {
1971 this.draw(i, parseInt(this.highScore[i]), true);
1972 }
1973 this.canvasCtx.restore();
1974 };
1975
1976
1977 /**
1978 * Set the highscore as a array string.
1979 * Position of char in the sprite: H - 10 in and I - 11.
1980 * @param {number} distance Distance in pixels.
1981 */
1982 Runner.DistanceMeter.prototype.setHighScore = function(distance) {
1983 distance = this.getActualDistance(distance);
1984 var highScoreStr = (this.defaultString +
1985 distance).substr(-Runner.DistanceMeter.MAX_DISTANCE_UNITS);
1986
1987 this.highScore = ['10', '11', ''].concat(highScoreStr.split(''));
1988 };
1989
1990
1991 /**
1992 * Reset the distance meter back to '00000'.
1993 */
1994 Runner.DistanceMeter.prototype.reset = function() {
1995 this.update(0);
1996 this.acheivement = false;
1997 };
1998
1999
2000
2001 //******************************************************************************
2002
2003 /**
2004 * Cloud background item.
2005 * Similar to an obstacle object but without collision boxes.
2006 * @param {HTMLCanvasElement} canvas Canvas element.
2007 * @param {Image} cloudImg Cloud image.
2008 * @param {number} containerWidth Width of container.
2009 */
2010 Runner.Cloud = function(canvas, cloudImg, containerWidth) {
2011 this.canvas = canvas;
2012
2013 this.canvasCtx = this.canvas.getContext('2d');
2014
2015 this.image = cloudImg;
2016
2017 this.containerWidth = containerWidth;
2018
2019 this.xPos = containerWidth;
2020
2021 this.yPos = 0;
2022
2023 this.remove = false;
2024
2025 this.cloudGap = Runner.getRandomNum(Runner.Cloud.config.MIN_CLOUD_GAP,
2026 Runner.Cloud.config.MAX_CLOUD_GAP);
2027
2028 this.init();
2029 };
2030
2031
2032 /**
2033 * Cloud object config.
2034 * @enum {string}
2035 */
2036 Runner.Cloud.config = {
2037 HEIGHT: 13,
2038 MAX_CLOUD_GAP: 400,
2039 MAX_SKY_LEVEL: 30,
2040 MIN_CLOUD_GAP: 100,
2041 MIN_SKY_LEVEL: 71,
2042 WIDTH: 46
2043 };
2044
2045
2046 /**
2047 * Initialise the cloud. Sets the Cloud height.
2048 */
2049 Runner.Cloud.prototype.init = function() {
2050 this.yPos = Runner.getRandomNum(Runner.Cloud.config.MAX_SKY_LEVEL,
2051 Runner.Cloud.config.MIN_SKY_LEVEL);
2052 this.draw();
2053 };
2054
2055
2056 /**
2057 * Draw the cloud.
2058 */
2059 Runner.Cloud.prototype.draw = function() {
2060 this.canvasCtx.save();
2061 var sourceWidth = Runner.Cloud.config.WIDTH;
2062 var sourceHeight = Runner.Cloud.config.HEIGHT;
2063
2064 if (Runner.isHDPI) {
2065 sourceWidth = sourceWidth * 2;
2066 sourceHeight = sourceHeight * 2;
2067 }
2068
2069 this.canvasCtx.drawImage(this.image, 0, 0,
2070 sourceWidth, sourceHeight,
2071 this.xPos, this.yPos,
2072 Runner.Cloud.config.WIDTH, Runner.Cloud.config.HEIGHT);
2073
2074 this.canvasCtx.restore();
2075 };
2076
2077
2078 /**
2079 * Update the cloud position.
2080 * @param {number} speed Speed.
2081 */
2082 Runner.Cloud.prototype.update = function(speed) {
2083 if (!this.remove) {
2084 this.xPos -= Math.ceil(speed);
2085 this.draw();
2086
2087 // Mark as removeable if no longer in the canvas.
2088 if (!this.isVisible()) {
2089 this.remove = true;
2090 }
2091 }
2092 };
2093
2094
2095 /**
2096 * Check if the cloud is in the stage.
2097 */
2098 Runner.Cloud.prototype.isVisible = function() {
2099 return (this.xPos + Runner.Cloud.config.WIDTH) > 0;
2100 };
2101
2102
2103 //******************************************************************************
2104
2105 /**
2106 * Horizon Line.
2107 * Consists of two connecting lines. Randomly assigns a flat / bumpy horizon.
2108 * @param {HTMLCanvasElement} canvas Canvas element.
2109 * @param {HTMLImage} bgImg Horizon line sprite.
2110 * @constructor
2111 */
2112 Runner.HorizonLine = function(canvas, bgImg) {
2113 this.image = bgImg;
2114 this.canvas = canvas;
2115 this.canvasCtx = canvas.getContext('2d');
2116 this.sourceDimensions = {};
2117 this.dimensions = Runner.HorizonLine.dimensions;
2118 this.sourceXPos = [0, this.dimensions.WIDTH];
2119 this.xPos = [];
2120 this.yPos = 0;
2121 this.bumpThreshold = 0.5;
2122
2123 this.setSourceDimensions();
2124 this.draw();
2125 };
2126
2127
2128 /**
2129 * Horizon line dimensions.
2130 * @enum {string}
2131 */
2132 Runner.HorizonLine.dimensions = {
2133 WIDTH: 600,
2134 HEIGHT: 12,
2135 YPOS: 127
2136 };
2137
2138
2139 /**
2140 * Set the source dimensions of the horizon line.
2141 */
2142 Runner.HorizonLine.prototype.setSourceDimensions = function() {
2143
2144 for (var dimension in Runner.HorizonLine.dimensions) {
2145 if (Runner.isHDPI) {
2146 if (dimension != 'YPOS') {
2147 this.sourceDimensions[dimension] =
2148 Runner.HorizonLine.dimensions[dimension] * 2;
2149 }
2150 } else {
2151 this.sourceDimensions[dimension] =
2152 Runner.HorizonLine.dimensions[dimension];
2153 }
2154 this.dimensions[dimension] = Runner.HorizonLine.dimensions[dimension];
2155 }
2156
2157 this.xPos = [0, Runner.HorizonLine.dimensions.WIDTH];
2158 this.yPos = Runner.HorizonLine.dimensions.YPOS;
2159 };
2160
2161
2162 /**
2163 * Return the crop x position of a type.
2164 */
2165 Runner.HorizonLine.prototype.getRandomType = function() {
2166 return (Math.random() > this.bumpThreshold) ? this.dimensions.WIDTH : 0;
2167 };
2168
2169
2170 /**
2171 * Draw the horizon line.
2172 */
2173 Runner.HorizonLine.prototype.draw = function() {
2174
2175 this.canvasCtx.drawImage(this.image, this.sourceXPos[0], 0,
2176 this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
2177 this.xPos[0], this.yPos,
2178 this.dimensions.WIDTH, this.dimensions.HEIGHT);
2179
2180 this.canvasCtx.drawImage(this.image, this.sourceXPos[1], 0,
2181 this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
2182 this.xPos[1], this.yPos,
2183 this.dimensions.WIDTH, this.dimensions.HEIGHT);
2184 };
2185
2186
2187 /**
2188 * Update the x position of an indivdual piece of the line.
2189 * @param {number} pos Line position.
2190 * @param {number} increment Amount to increment by.
2191 */
2192 Runner.HorizonLine.prototype.updateXPos = function(pos, increment) {
2193 var line1 = pos;
2194 var line2 = pos == 0 ? 1 : 0;
2195
2196 this.xPos[line1] -= increment;
2197 this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH;
2198
2199 if (this.xPos[line1] <= -this.dimensions.WIDTH) {
2200 this.xPos[line1] += this.dimensions.WIDTH * 2;
2201 this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH;
2202 this.sourceXPos[line1] = this.getRandomType();
2203 }
2204 };
2205
2206
2207 /**
2208 * Update the horizon line.
2209 * @param {number} deltaTime Time since last update.
2210 * @param {number} speed Speed.
2211 */
2212 Runner.HorizonLine.prototype.update = function(deltaTime, speed) {
2213 var increment = Math.floor((speed * (Runner.FPS / 1000)) * deltaTime);
2214
2215 if (this.xPos[0] <= 0) {
2216 this.updateXPos(0, increment);
2217 } else {
2218 this.updateXPos(1, increment);
2219 }
2220 this.draw();
2221 };
2222
2223
2224 /**
2225 * Reset horizon to the starting position.
2226 */
2227 Runner.HorizonLine.prototype.reset = function() {
2228 this.xPos[0] = 0;
2229 this.xPos[1] = Runner.HorizonLine.dimensions.WIDTH;
2230 };
2231
2232 //******************************************************************************
2233 /**
2234 * Horizon background class.
2235 * @param {HTMLCanvasElement} canvas
2236 * @param {Array.<HTMLImageElement>} images Array of images.
2237 * @param {object} dimensions Canvas dimensions.
2238 * @param {number} gapCoefficient Gap coefficient.
2239 * @constructor
2240 */
2241 Runner.Horizon = function(canvas, images, dimensions, gapCoefficient) {
2242
2243 this.canvas = canvas;
2244
2245 this.canvasCtx = this.canvas.getContext('2d');
2246
2247 this.config = Runner.Horizon.config;
2248
2249 this.dimensions = dimensions;
2250
2251 this.gapCoefficient = gapCoefficient;
2252
2253 // Cloud
2254 this.clouds = [];
2255 this.cloudImg = images['CLOUD'];
2256 this.cloudSpeed = this.config.BG_CLOUD_SPEED;
2257
2258 // Horizon
2259 this.horizonImg = images['HORIZON'];
2260 this.horizonLine = null;
2261
2262 // Obstacles
2263 this.obstacleImgs = {
2264 'CACTUS_SMALL': images['CACTUS_SMALL'],
2265 'CACTUS_LARGE': images['CACTUS_LARGE']
2266 };
2267
2268 this.obstacles = [];
2269
2270 this.horizonOffsets = [0, 0];
2271
2272 this.cloudFrequency = this.config.CLOUD_FREQUENCY;
2273
2274 this.init();
2275 };
2276
2277
2278 /**
2279 * Horizon config.
2280 * @enum {string}
2281 */
2282 Runner.Horizon.config = {
2283 BG_CLOUD_SPEED: 0.2,
2284 BUMPY_THRESHOLD: .3,
2285 CLOUD_FREQUENCY: .5,
2286 HORIZON_HEIGHT: 16,
2287 MAX_CLOUDS: 6
2288 };
2289
2290
2291 /**
2292 * Initialise the horizon. Just add the line and a cloud. No obstacles.
2293 */
2294 Runner.Horizon.prototype.init = function() {
2295 this.addCloud();
2296
2297 this.horizonLine = new Runner.HorizonLine(this.canvas, this.horizonImg);
2298 };
2299
2300
2301 /**
2302 *
2303 * @param {number} deltaTime Time elapsed.
2304 * @param {number} currentSpeed Speed.
2305 * @param {boolean} updateObstacles Used as an override to prevent the obstacles
2306 * from being updated / added. This happens in the ease in section.
2307 */
2308 Runner.Horizon.prototype.update =
2309 function(deltaTime, currentSpeed, updateObstacles) {
2310
2311 this.runningTime += deltaTime;
2312
2313 this.horizonLine.update(deltaTime, currentSpeed);
2314 this.updateClouds(deltaTime, currentSpeed);
2315
2316 if (updateObstacles) {
2317 this.updateObstacles(deltaTime, currentSpeed);
2318 }
2319 };
2320
2321 /**
2322 * Update the cloud positions.
2323 * @param {number} deltaTime Time elapsed.
2324 * @param {number} currentSpeed Speed.
2325 */
2326 Runner.Horizon.prototype.updateClouds = function(deltaTime, speed) {
2327 var cloudSpeed = ((this.cloudSpeed / 1000) * deltaTime) * speed;
2328 var numClouds = this.clouds.length;
2329
2330 if (numClouds) {
2331 for (var i = numClouds - 1; i >= 0; i--) {
2332 this.clouds[i].update(cloudSpeed);
2333 }
2334
2335 var lastCloud = this.clouds[numClouds - 1];
2336
2337 // Check for adding a new cloud.
2338 if (numClouds < this.config.MAX_CLOUDS &&
2339 (this.dimensions.WIDTH - lastCloud.xPos) > lastCloud.cloudGap &&
2340 this.cloudFrequency > Math.random()) {
2341 this.addCloud();
2342 }
2343
2344 // Remove expired clouds.
2345 this.clouds = this.clouds.filter(function(obj) {
2346 return !obj.remove;
2347 });
2348 }
2349 };
2350
2351
2352 /**
2353 * Update the obstacle positions.
2354 * @param {number} deltaTime Time elapsed.
2355 * @param {number} currentSpeed Speed.
2356 */
2357 Runner.Horizon.prototype.updateObstacles = function(deltaTime, currentSpeed) {
2358 // Obstacles, move to Horizon layer.
2359 var updatedObstacles = this.obstacles.slice(0);
2360
2361 for (var i = 0, j = this.obstacles.length; i < j; i++) {
2362 var obstacle = this.obstacles[i];
2363 obstacle.update(deltaTime, currentSpeed);
2364
2365 // Clean up existing obstacles.
2366 if (obstacle.remove) {
2367 updatedObstacles.shift();
2368 }
2369 }
2370 this.obstacles = updatedObstacles;
2371
2372 if (this.obstacles.length > 0) {
2373 var lastObstacle = this.obstacles[this.obstacles.length - 1];
2374
2375 if (lastObstacle && !lastObstacle.followingObstacleCreated &&
2376 lastObstacle.isVisible() &&
2377 (lastObstacle.xPos + (lastObstacle.width + lastObstacle.gap)) <
2378 this.dimensions.WIDTH) {
2379 this.addNewObstacle(currentSpeed);
2380 lastObstacle.followingObstacleCreated = true;
2381 }
2382 } else {
2383 // Create new obstacles.
2384 this.addNewObstacle(currentSpeed);
2385 }
2386 };
2387
2388
2389 /**
2390 * Add a new obstacle.
2391 * @param {number} currentSpeed Speed.
2392 */
2393 Runner.Horizon.prototype.addNewObstacle = function(currentSpeed) {
2394 var obstacleTypeIndex =
2395 Runner.getRandomNum(0, Runner.Obstacle.types.length - 1);
2396 var obstacleType = Runner.Obstacle.types[obstacleTypeIndex];
2397 var obstacleImg = this.obstacleImgs[obstacleType.type];
2398
2399 this.obstacles.push(new Runner.Obstacle(this.canvasCtx, obstacleType,
2400 obstacleImg, this.dimensions, this.gapCoefficient, currentSpeed));
2401 };
2402
2403
2404 /**
2405 * Reset the horizon layer.
2406 * Remove existing obstacles and reposition the horizon line.
2407 */
2408 Runner.Horizon.prototype.reset = function() {
2409 this.obstacles = [];
2410 this.horizonLine.reset();
2411 };
2412
2413
2414 /**
2415 * Update the canvas width and scaling.
2416 * @param {number} width Canvas width.
2417 * @param {number} height Canvas height.
2418 */
2419 Runner.Horizon.prototype.resize = function(width, height) {
2420 this.canvas.width = width;
2421 this.canvas.height = height;
2422 };
2423
2424
2425 /**
2426 * Add a new cloud to the horizon.
2427 */
2428 Runner.Horizon.prototype.addCloud = function() {
2429 this.clouds.push(new Runner.Cloud(this.canvas, this.cloudImg,
2430 this.dimensions.WIDTH));
2431
2432 };
2433
2434 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698