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

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

Powered by Google App Engine
This is Rietveld 408576698