Chrome内置的断网Javascript 小游戏脚本示范
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Chrome内置的断网Javascript 小游戏脚本示范,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含66210字,纯文字阅读大概需要95分钟。
内容图文
//示范面向对象 this 作用域 闭包 单例模式很好的示范
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 * @param {string} outerContainerId Outer containing element id. 9 * @param {Object} opt_config 10 * @constructor 11 * @export 12*/ 13function Runner(outerContainerId, opt_config) { 14// Singleton 15if (Runner.instance_) { 16return Runner.instance_; 17 } 18 Runner.instance_ = this; 19 20this.outerContainerEl = document.querySelector(outerContainerId); 21this.containerEl = null; 22this.snackbarEl = null; 23this.detailsButton = this.outerContainerEl.querySelector(‘#details-button‘); 24 25this.config = opt_config || Runner.config; 26 27this.dimensions = Runner.defaultDimensions; 28 29this.canvas = null; 30this.canvasCtx = null; 31 32this.tRex = null; 33 34this.distanceMeter = null; 35this.distanceRan = 0; 36 37this.highestScore = 0; 38 39this.time = 0; 40this.runningTime = 0; 41this.msPerFrame = 1000 / FPS; 42this.currentSpeed = this.config.SPEED; 43 44this.obstacles = []; 45 46this.started = false; 47this.activated = false; 48this.crashed = false; 49this.paused = false; 50 51this.resizeTimerId_ = null; 52 53this.playCount = 0; 54 55// Sound FX. 56this.audioBuffer = null; 57this.soundFx = {}; 58 59// Global web audio context for playing sounds. 60this.audioContext = null; 61 62// Images. 63this.images = {}; 64this.imagesLoaded = 0; 65 66if (this.isDisabled()) { 67this.setupDisabledRunner(); 68 } else { 69this.loadImages(); 70 } 71 } 72 window[‘Runner‘] = Runner; 73 74 75/** 76 * Default game width. 77 * @const 78*/ 79var DEFAULT_WIDTH = 600; 80 81/** 82 * Frames per second. 83 * @const 84*/ 85var FPS = 60; 86 87/** @const */ 88var IS_HIDPI = window.devicePixelRatio > 1; 89 90/** @const */ 91var IS_IOS = window.navigator.userAgent.indexOf(‘CriOS‘) > -1 || 92 window.navigator.userAgent == ‘UIWebViewForStaticFileContent‘; 93 94/** @const */ 95var IS_MOBILE = window.navigator.userAgent.indexOf(‘Mobi‘) > -1 || IS_IOS; 96 97/** @const */ 98var IS_TOUCH_ENABLED = ‘ontouchstart‘ in window; 99 100/** 101 * Default game configuration. 102 * @enum {number} 103*/ 104 Runner.config = { 105 ACCELERATION: 0.001, 106 BG_CLOUD_SPEED: 0.2, 107 BOTTOM_PAD: 10, 108 CLEAR_TIME: 3000, 109 CLOUD_FREQUENCY: 0.5, 110 GAMEOVER_CLEAR_TIME: 750, 111 GAP_COEFFICIENT: 0.6, 112 GRAVITY: 0.6, 113 INITIAL_JUMP_VELOCITY: 12, 114 MAX_CLOUDS: 6, 115 MAX_OBSTACLE_LENGTH: 3, 116 MAX_OBSTACLE_DUPLICATION: 2, 117 MAX_SPEED: 13, 118 MIN_JUMP_HEIGHT: 35, 119 MOBILE_SPEED_COEFFICIENT: 1.2, 120 RESOURCE_TEMPLATE_ID: ‘audio-resources‘, 121 SPEED: 6, 122 SPEED_DROP_COEFFICIENT: 3 123 }; 124 125 126/** 127 * Default dimensions. 128 * @enum {string} 129*/ 130 Runner.defaultDimensions = { 131 WIDTH: DEFAULT_WIDTH, 132 HEIGHT: 150 133 }; 134 135 136/** 137 * CSS class names. 138 * @enum {string} 139*/ 140 Runner.classes = { 141 CANVAS: ‘runner-canvas‘, 142 CONTAINER: ‘runner-container‘, 143 CRASHED: ‘crashed‘, 144 ICON: ‘icon-offline‘, 145 SNACKBAR: ‘snackbar‘, 146 SNACKBAR_SHOW: ‘snackbar-show‘, 147 TOUCH_CONTROLLER: ‘controller‘ 148 }; 149 150 151/** 152 * Sprite definition layout of the spritesheet. 153 * @enum {Object} 154*/ 155 Runner.spriteDefinition = { 156 LDPI: { 157 CACTUS_LARGE: { x: 332, y: 2 }, 158 CACTUS_SMALL: { x: 228, y: 2 }, 159 CLOUD: { x: 86, y: 2 }, 160 HORIZON: { x: 2, y: 54 }, 161 PTERODACTYL: { x: 134, y: 2 }, 162 RESTART: { x: 2, y: 2 }, 163 TEXT_SPRITE: { x: 484, y: 2 }, 164 TREX: { x: 677, y: 2 } 165 }, 166 HDPI: { 167 CACTUS_LARGE: { x: 652, y: 2 }, 168 CACTUS_SMALL: { x: 446, y: 2 }, 169 CLOUD: { x: 166, y: 2 }, 170 HORIZON: { x: 2, y: 104 }, 171 PTERODACTYL: { x: 260, y: 2 }, 172 RESTART: { x: 2, y: 2 }, 173 TEXT_SPRITE: { x: 954, y: 2 }, 174 TREX: { x: 1338, y: 2 } 175 } 176 }; 177 178 179/** 180 * Sound FX. Reference to the ID of the audio tag on interstitial page. 181 * @enum {string} 182*/ 183 Runner.sounds = { 184 BUTTON_PRESS: ‘offline-sound-press‘, 185 HIT: ‘offline-sound-hit‘, 186 SCORE: ‘offline-sound-reached‘ 187 }; 188 189 190/** 191 * Key code mapping. 192 * @enum {Object} 193*/ 194 Runner.keycodes = { 195 JUMP: { ‘38‘: 1, ‘32‘: 1 }, // Up, spacebar 196 DUCK: { ‘40‘: 1 }, // Down 197 RESTART: { ‘13‘: 1 } // Enter 198 }; 199 200 201/** 202 * Runner event names. 203 * @enum {string} 204*/ 205 Runner.events = { 206 ANIM_END: ‘webkitAnimationEnd‘, 207 CLICK: ‘click‘, 208 KEYDOWN: ‘keydown‘, 209 KEYUP: ‘keyup‘, 210 MOUSEDOWN: ‘mousedown‘, 211 MOUSEUP: ‘mouseup‘, 212 RESIZE: ‘resize‘, 213 TOUCHEND: ‘touchend‘, 214 TOUCHSTART: ‘touchstart‘, 215 VISIBILITY: ‘visibilitychange‘, 216 BLUR: ‘blur‘, 217 FOCUS: ‘focus‘, 218 LOAD: ‘load‘ 219 }; 220 221 222 Runner.prototype = { 223/** 224 * Whether the easter egg has been disabled. CrOS enterprise enrolled devices. 225 * @return {boolean} 226*/ 227 isDisabled: function () { 228return loadTimeData && loadTimeData.valueExists(‘disabledEasterEgg‘); 229 }, 230 231/** 232 * For disabled instances, set up a snackbar with the disabled message. 233*/ 234 setupDisabledRunner: function () { 235this.containerEl = document.createElement(‘div‘); 236this.containerEl.className = Runner.classes.SNACKBAR; 237this.containerEl.textContent = loadTimeData.getValue(‘disabledEasterEgg‘); 238this.outerContainerEl.appendChild(this.containerEl); 239 240// Show notification when the activation key is pressed. 241 document.addEventListener(Runner.events.KEYDOWN, function (e) { 242if (Runner.keycodes.JUMP[e.keyCode]) { 243this.containerEl.classList.add(Runner.classes.SNACKBAR_SHOW); 244 document.querySelector(‘.icon‘).classList.add(‘icon-disabled‘); 245 } 246 }.bind(this)); 247 }, 248 249/** 250 * Setting individual settings for debugging. 251 * @param {string} setting 252 * @param {*} value 253*/ 254 updateConfigSetting: function (setting, value) { 255if (setting inthis.config && value != undefined) { 256this.config[setting] = value; 257 258switch (setting) { 259case ‘GRAVITY‘: 260case ‘MIN_JUMP_HEIGHT‘: 261case ‘SPEED_DROP_COEFFICIENT‘: 262this.tRex.config[setting] = value; 263break; 264case ‘INITIAL_JUMP_VELOCITY‘: 265this.tRex.setJumpVelocity(value); 266break; 267case ‘SPEED‘: 268this.setSpeed(value); 269break; 270 } 271 } 272 }, 273 274/** 275 * Cache the appropriate image sprite from the page and get the sprite sheet 276 * definition. 277*/ 278 loadImages: function () { 279if (IS_HIDPI) { 280 Runner.imageSprite = document.getElementById(‘offline-resources-2x‘); 281this.spriteDef = Runner.spriteDefinition.HDPI; 282 } else { 283 Runner.imageSprite = document.getElementById(‘offline-resources-1x‘); 284this.spriteDef = Runner.spriteDefinition.LDPI; 285 } 286 287this.init(); 288 }, 289 290/** 291 * Load and decode base 64 encoded sounds. 292*/ 293 loadSounds: function () { 294if (!IS_IOS) { 295this.audioContext = new AudioContext(); 296 297var resourceTemplate = 298 document.getElementById(this.config.RESOURCE_TEMPLATE_ID).content; 299 300for (var sound in Runner.sounds) { 301var soundSrc = 302 resourceTemplate.getElementById(Runner.sounds[sound]).src; 303 soundSrc = soundSrc.substr(soundSrc.indexOf(‘,‘) + 1); 304var buffer = decodeBase64ToArrayBuffer(soundSrc); 305 306// Async, so no guarantee of order in array. 307this.audioContext.decodeAudioData(buffer, function (index, audioData) { 308this.soundFx[index] = audioData; 309 }.bind(this, sound)); 310 } 311 } 312 }, 313 314/** 315 * Sets the game speed. Adjust the speed accordingly if on a smaller screen. 316 * @param {number} opt_speed 317*/ 318 setSpeed: function (opt_speed) { 319var speed = opt_speed || this.currentSpeed; 320 321// Reduce the speed on smaller mobile screens. 322if (this.dimensions.WIDTH < DEFAULT_WIDTH) { 323var mobileSpeed = speed * this.dimensions.WIDTH / DEFAULT_WIDTH * 324this.config.MOBILE_SPEED_COEFFICIENT; 325this.currentSpeed = mobileSpeed > speed ? speed : mobileSpeed; 326 } elseif (opt_speed) { 327this.currentSpeed = opt_speed; 328 } 329 }, 330 331/** 332 * Game initialiser. 333*/ 334 init: function () { 335// Hide the static icon. 336 document.querySelector(‘.‘ + Runner.classes.ICON).style.visibility = 337 ‘hidden‘; 338 339this.adjustDimensions(); 340this.setSpeed(); 341 342this.containerEl = document.createElement(‘div‘); 343this.containerEl.className = Runner.classes.CONTAINER; 344 345// Player canvas container. 346this.canvas = createCanvas(this.containerEl, this.dimensions.WIDTH, 347this.dimensions.HEIGHT, Runner.classes.PLAYER); 348 349this.canvasCtx = this.canvas.getContext(‘2d‘); 350this.canvasCtx.fillStyle = ‘#f7f7f7‘; 351this.canvasCtx.fill(); 352 Runner.updateCanvasScaling(this.canvas); 353 354// Horizon contains clouds, obstacles and the ground. 355this.horizon = new Horizon(this.canvas, this.spriteDef, this.dimensions, 356this.config.GAP_COEFFICIENT); 357 358// Distance meter 359this.distanceMeter = new DistanceMeter(this.canvas, 360this.spriteDef.TEXT_SPRITE, this.dimensions.WIDTH); 361 362// Draw t-rex 363this.tRex = new Trex(this.canvas, this.spriteDef.TREX); 364 365this.outerContainerEl.appendChild(this.containerEl); 366 367if (IS_MOBILE) { 368this.createTouchController(); 369 } 370 371this.startListening(); 372this.update(); 373 374 window.addEventListener(Runner.events.RESIZE, 375this.debounceResize.bind(this)); 376 }, 377 378/** 379 * Create the touch controller. A div that covers whole screen. 380*/ 381 createTouchController: function () { 382this.touchController = document.createElement(‘div‘); 383this.touchController.className = Runner.classes.TOUCH_CONTROLLER; 384 }, 385 386/** 387 * Debounce the resize event. 388*/ 389 debounceResize: function () { 390if (!this.resizeTimerId_) { 391this.resizeTimerId_ = 392 setInterval(this.adjustDimensions.bind(this), 250); 393 } 394 }, 395 396/** 397 * Adjust game space dimensions on resize. 398*/ 399 adjustDimensions: function () { 400 clearInterval(this.resizeTimerId_); 401this.resizeTimerId_ = null; 402 403var boxStyles = window.getComputedStyle(this.outerContainerEl); 404var padding = Number(boxStyles.paddingLeft.substr(0, 405 boxStyles.paddingLeft.length - 2)); 406 407this.dimensions.WIDTH = this.outerContainerEl.offsetWidth - padding * 2; 408 409// Redraw the elements back onto the canvas. 410if (this.canvas) { 411this.canvas.width = this.dimensions.WIDTH; 412this.canvas.height = this.dimensions.HEIGHT; 413 414 Runner.updateCanvasScaling(this.canvas); 415 416this.distanceMeter.calcXPos(this.dimensions.WIDTH); 417this.clearCanvas(); 418this.horizon.update(0, 0, true); 419this.tRex.update(0); 420 421// Outer container and distance meter. 422if (this.activated || this.crashed) { 423this.containerEl.style.width = this.dimensions.WIDTH + ‘px‘; 424this.containerEl.style.height = this.dimensions.HEIGHT + ‘px‘; 425this.distanceMeter.update(0, Math.ceil(this.distanceRan)); 426this.stop(); 427 } else { 428this.tRex.draw(0, 0); 429 } 430 431// Game over panel. 432if (this.crashed && this.gameOverPanel) { 433this.gameOverPanel.updateDimensions(this.dimensions.WIDTH); 434this.gameOverPanel.draw(); 435 } 436 } 437 }, 438 439/** 440 * Play the game intro. 441 * Canvas container width expands out to the full width. 442*/ 443 playIntro: function () { 444if (!this.started && !this.crashed) { 445this.playingIntro = true; 446this.tRex.playingIntro = true; 447 448// CSS animation definition. 449var keyframes = ‘@-webkit-keyframes intro { ‘ + 450 ‘from { width:‘ + Trex.config.WIDTH + ‘px }‘ + 451 ‘to { width: ‘ + this.dimensions.WIDTH + ‘px }‘ + 452 ‘}‘; 453 document.styleSheets[0].insertRule(keyframes, 0); 454 455this.containerEl.addEventListener(Runner.events.ANIM_END, 456this.startGame.bind(this)); 457 458this.containerEl.style.webkitAnimation = ‘intro .4s ease-out 1 both‘; 459this.containerEl.style.width = this.dimensions.WIDTH + ‘px‘; 460 461if (this.touchController) { 462this.outerContainerEl.appendChild(this.touchController); 463 } 464this.activated = true; 465this.started = true; 466 } elseif (this.crashed) { 467this.restart(); 468 } 469 }, 470 471 472/** 473 * Update the game status to started. 474*/ 475 startGame: function () { 476this.runningTime = 0; 477this.playingIntro = false; 478this.tRex.playingIntro = false; 479this.containerEl.style.webkitAnimation = ‘‘; 480this.playCount++; 481 482// Handle tabbing off the page. Pause the current game. 483 window.addEventListener(Runner.events.VISIBILITY, 484this.onVisibilityChange.bind(this)); 485 486 window.addEventListener(Runner.events.BLUR, 487this.onVisibilityChange.bind(this)); 488 489 window.addEventListener(Runner.events.FOCUS, 490this.onVisibilityChange.bind(this)); 491 }, 492 493 clearCanvas: function () { 494this.canvasCtx.clearRect(0, 0, this.dimensions.WIDTH, 495this.dimensions.HEIGHT); 496 }, 497 498/** 499 * Update the game frame. 500*/ 501 update: function () { 502this.drawPending = false; 503 504var now = getTimeStamp(); 505var deltaTime = now - (this.time || now); 506this.time = now; 507 508if (this.activated) { 509this.clearCanvas(); 510 511if (this.tRex.jumping) { 512this.tRex.updateJump(deltaTime); 513 } 514 515this.runningTime += deltaTime; 516var hasObstacles = this.runningTime > this.config.CLEAR_TIME; 517 518// First jump triggers the intro. 519if (this.tRex.jumpCount == 1 && !this.playingIntro) { 520this.playIntro(); 521 } 522 523// The horizon doesn‘t move until the intro is over. 524if (this.playingIntro) { 525this.horizon.update(0, this.currentSpeed, hasObstacles); 526 } else { 527 deltaTime = !this.started ? 0 : deltaTime; 528this.horizon.update(deltaTime, this.currentSpeed, hasObstacles); 529 } 530 531// Check for collisions. 532var collision = hasObstacles && 533 checkForCollision(this.horizon.obstacles[0], this.tRex); 534 535if (!collision) { 536this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame; 537 538if (this.currentSpeed < this.config.MAX_SPEED) { 539this.currentSpeed += this.config.ACCELERATION; 540 } 541 } else { 542this.gameOver(); 543 } 544 545var playAcheivementSound = this.distanceMeter.update(deltaTime, 546 Math.ceil(this.distanceRan)); 547 548if (playAcheivementSound) { 549this.playSound(this.soundFx.SCORE); 550 } 551 } 552 553if (!this.crashed) { 554this.tRex.update(deltaTime); 555this.raq(); 556 } 557 }, 558 559/** 560 * Event handler. 561*/ 562 handleEvent: function (e) { 563return (function (evtType, events) { 564switch (evtType) { 565case events.KEYDOWN: 566case events.TOUCHSTART: 567case events.MOUSEDOWN: 568this.onKeyDown(e); 569break; 570case events.KEYUP: 571case events.TOUCHEND: 572case events.MOUSEUP: 573this.onKeyUp(e); 574break; 575 } 576 }.bind(this))(e.type, Runner.events); 577 }, 578 579/** 580 * Bind relevant key / mouse / touch listeners. 581*/ 582 startListening: function () { 583// Keys. 584 document.addEventListener(Runner.events.KEYDOWN, this); 585 document.addEventListener(Runner.events.KEYUP, this); 586 587if (IS_MOBILE) { 588// Mobile only touch devices. 589this.touchController.addEventListener(Runner.events.TOUCHSTART, this); 590this.touchController.addEventListener(Runner.events.TOUCHEND, this); 591this.containerEl.addEventListener(Runner.events.TOUCHSTART, this); 592 } else { 593// Mouse. 594 document.addEventListener(Runner.events.MOUSEDOWN, this); 595 document.addEventListener(Runner.events.MOUSEUP, this); 596 } 597 }, 598 599/** 600 * Remove all listeners. 601*/ 602 stopListening: function () { 603 document.removeEventListener(Runner.events.KEYDOWN, this); 604 document.removeEventListener(Runner.events.KEYUP, this); 605 606if (IS_MOBILE) { 607this.touchController.removeEventListener(Runner.events.TOUCHSTART, this); 608this.touchController.removeEventListener(Runner.events.TOUCHEND, this); 609this.containerEl.removeEventListener(Runner.events.TOUCHSTART, this); 610 } else { 611 document.removeEventListener(Runner.events.MOUSEDOWN, this); 612 document.removeEventListener(Runner.events.MOUSEUP, this); 613 } 614 }, 615 616/** 617 * Process keydown. 618 * @param {Event} e 619*/ 620 onKeyDown: function (e) { 621// Prevent native page scrolling whilst tapping on mobile. 622if (IS_MOBILE) { 623 e.preventDefault(); 624 } 625 626if (e.target != this.detailsButton) { 627if (!this.crashed && (Runner.keycodes.JUMP[e.keyCode] || 628 e.type == Runner.events.TOUCHSTART)) { 629if (!this.activated) { 630this.loadSounds(); 631this.activated = true; 632 errorPageController.trackEasterEgg(); 633 } 634 635if (!this.tRex.jumping && !this.tRex.ducking) { 636this.playSound(this.soundFx.BUTTON_PRESS); 637this.tRex.startJump(this.currentSpeed); 638 } 639 } 640 641if (this.crashed && e.type == Runner.events.TOUCHSTART && 642 e.currentTarget == this.containerEl) { 643this.restart(); 644 } 645 } 646 647if (this.activated && !this.crashed && Runner.keycodes.DUCK[e.keyCode]) { 648 e.preventDefault(); 649if (this.tRex.jumping) { 650// Speed drop, activated only when jump key is not pressed. 651this.tRex.setSpeedDrop(); 652 } elseif (!this.tRex.jumping && !this.tRex.ducking) { 653// Duck. 654this.tRex.setDuck(true); 655 } 656 } 657 }, 658 659 660/** 661 * Process key up. 662 * @param {Event} e 663*/ 664 onKeyUp: function (e) { 665var keyCode = String(e.keyCode); 666var isjumpKey = Runner.keycodes.JUMP[keyCode] || 667 e.type == Runner.events.TOUCHEND || 668 e.type == Runner.events.MOUSEDOWN; 669 670if (this.isRunning() && isjumpKey) { 671this.tRex.endJump(); 672 } elseif (Runner.keycodes.DUCK[keyCode]) { 673this.tRex.speedDrop = false; 674this.tRex.setDuck(false); 675 } elseif (this.crashed) { 676// Check that enough time has elapsed before allowing jump key to restart. 677var deltaTime = getTimeStamp() - this.time; 678 679if (Runner.keycodes.RESTART[keyCode] || this.isLeftClickOnCanvas(e) || 680 (deltaTime >= this.config.GAMEOVER_CLEAR_TIME && 681 Runner.keycodes.JUMP[keyCode])) { 682this.restart(); 683 } 684 } elseif (this.paused && isjumpKey) { 685// Reset the jump state 686this.tRex.reset(); 687this.play(); 688 } 689 }, 690 691/** 692 * Returns whether the event was a left click on canvas. 693 * On Windows right click is registered as a click. 694 * @param {Event} e 695 * @return {boolean} 696*/ 697 isLeftClickOnCanvas: function (e) { 698return e.button != null && e.button < 2 && 699 e.type == Runner.events.MOUSEUP && e.target == this.canvas; 700 }, 701 702/** 703 * RequestAnimationFrame wrapper. 704*/ 705 raq: function () { 706if (!this.drawPending) { 707this.drawPending = true; 708this.raqId = requestAnimationFrame(this.update.bind(this)); 709 } 710 }, 711 712/** 713 * Whether the game is running. 714 * @return {boolean} 715*/ 716 isRunning: function () { 717return !!this.raqId; 718 }, 719 720/** 721 * Game over state. 722*/ 723 gameOver: function () { 724this.playSound(this.soundFx.HIT); 725 vibrate(200); 726 727this.stop(); 728this.crashed = true; 729this.distanceMeter.acheivement = false; 730 731this.tRex.update(100, Trex.status.CRASHED); 732 733// Game over panel. 734if (!this.gameOverPanel) { 735this.gameOverPanel = new GameOverPanel(this.canvas, 736this.spriteDef.TEXT_SPRITE, this.spriteDef.RESTART, 737this.dimensions); 738 } else { 739this.gameOverPanel.draw(); 740 } 741 742// Update the high score. 743if (this.distanceRan > this.highestScore) { 744this.highestScore = Math.ceil(this.distanceRan); 745this.distanceMeter.setHighScore(this.highestScore); 746 } 747 748// Reset the time clock. 749this.time = getTimeStamp(); 750 }, 751 752 stop: function () { 753this.activated = false; 754this.paused = true; 755 cancelAnimationFrame(this.raqId); 756this.raqId = 0; 757 }, 758 759 play: function () { 760if (!this.crashed) { 761this.activated = true; 762this.paused = false; 763this.tRex.update(0, Trex.status.RUNNING); 764this.time = getTimeStamp(); 765this.update(); 766 } 767 }, 768 769 restart: function () { 770if (!this.raqId) { 771this.playCount++; 772this.runningTime = 0; 773this.activated = true; 774this.crashed = false; 775this.distanceRan = 0; 776this.setSpeed(this.config.SPEED); 777 778this.time = getTimeStamp(); 779this.containerEl.classList.remove(Runner.classes.CRASHED); 780this.clearCanvas(); 781this.distanceMeter.reset(this.highestScore); 782this.horizon.reset(); 783this.tRex.reset(); 784this.playSound(this.soundFx.BUTTON_PRESS); 785 786this.update(); 787 } 788 }, 789 790/** 791 * Pause the game if the tab is not in focus. 792*/ 793 onVisibilityChange: function (e) { 794if (document.hidden || document.webkitHidden || e.type == ‘blur‘) { 795this.stop(); 796 } elseif (!this.crashed) { 797this.tRex.reset(); 798this.play(); 799 } 800 }, 801 802/** 803 * Play a sound. 804 * @param {SoundBuffer} soundBuffer 805*/ 806 playSound: function (soundBuffer) { 807if (soundBuffer) { 808var sourceNode = this.audioContext.createBufferSource(); 809 sourceNode.buffer = soundBuffer; 810 sourceNode.connect(this.audioContext.destination); 811 sourceNode.start(0); 812 } 813 } 814 }; 815 816 817/** 818 * Updates the canvas size taking into 819 * account the backing store pixel ratio and 820 * the device pixel ratio. 821 * 822 * See article by Paul Lewis: 823 * http://www.html5rocks.com/en/tutorials/canvas/hidpi/ 824 * 825 * @param {HTMLCanvasElement} canvas 826 * @param {number} opt_width 827 * @param {number} opt_height 828 * @return {boolean} Whether the canvas was scaled. 829*/ 830 Runner.updateCanvasScaling = function (canvas, opt_width, opt_height) { 831var context = canvas.getContext(‘2d‘); 832 833// Query the various pixel ratios 834var devicePixelRatio = Math.floor(window.devicePixelRatio) || 1; 835var backingStoreRatio = Math.floor(context.webkitBackingStorePixelRatio) || 1; 836var ratio = devicePixelRatio / backingStoreRatio; 837 838// Upscale the canvas if the two ratios don‘t match 839if (devicePixelRatio !== backingStoreRatio) { 840var oldWidth = opt_width || canvas.width; 841var oldHeight = opt_height || canvas.height; 842 843 canvas.width = oldWidth * ratio; 844 canvas.height = oldHeight * ratio; 845 846 canvas.style.width = oldWidth + ‘px‘; 847 canvas.style.height = oldHeight + ‘px‘; 848 849// Scale the context to counter the fact that we‘ve manually scaled 850// our canvas element. 851 context.scale(ratio, ratio); 852returntrue; 853 } elseif (devicePixelRatio == 1) { 854// Reset the canvas width / height. Fixes scaling bug when the page is 855// zoomed and the devicePixelRatio changes accordingly. 856 canvas.style.width = canvas.width + ‘px‘; 857 canvas.style.height = canvas.height + ‘px‘; 858 } 859returnfalse; 860 }; 861 862 863/** 864 * Get random number. 865 * @param {number} min 866 * @param {number} max 867 * @param {number} 868*/ 869function getRandomNum(min, max) { 870return Math.floor(Math.random() * (max - min + 1)) + min; 871 } 872 873 874/** 875 * Vibrate on mobile devices. 876 * @param {number} duration Duration of the vibration in milliseconds. 877*/ 878function vibrate(duration) { 879if (IS_MOBILE && window.navigator.vibrate) { 880 window.navigator.vibrate(duration); 881 } 882 } 883 884 885/** 886 * Create canvas element. 887 * @param {HTMLElement} container Element to append canvas to. 888 * @param {number} width 889 * @param {number} height 890 * @param {string} opt_classname 891 * @return {HTMLCanvasElement} 892*/ 893function createCanvas(container, width, height, opt_classname) { 894var canvas = document.createElement(‘canvas‘); 895 canvas.className = opt_classname ? Runner.classes.CANVAS + ‘ ‘ + 896 opt_classname : Runner.classes.CANVAS; 897 canvas.width = width; 898 canvas.height = height; 899 container.appendChild(canvas); 900 901return canvas; 902 } 903 904 905/** 906 * Decodes the base 64 audio to ArrayBuffer used by Web Audio. 907 * @param {string} base64String 908*/ 909function decodeBase64ToArrayBuffer(base64String) { 910var len = (base64String.length / 4) * 3; 911var str = atob(base64String); 912var arrayBuffer = new ArrayBuffer(len); 913var bytes = new Uint8Array(arrayBuffer); 914 915for (var i = 0; i < len; i++) { 916 bytes[i] = str.charCodeAt(i); 917 } 918return bytes.buffer; 919 } 920 921 922/** 923 * Return the current timestamp. 924 * @return {number} 925*/ 926function getTimeStamp() { 927return IS_IOS ? new Date().getTime() : performance.now(); 928 } 929 930 931//****************************************************************************** 932 933 934/** 935 * Game over panel. 936 * @param {!HTMLCanvasElement} canvas 937 * @param {Object} textImgPos 938 * @param {Object} restartImgPos 939 * @param {!Object} dimensions Canvas dimensions. 940 * @constructor 941*/ 942function GameOverPanel(canvas, textImgPos, restartImgPos, dimensions) { 943this.canvas = canvas; 944this.canvasCtx = canvas.getContext(‘2d‘); 945this.canvasDimensions = dimensions; 946this.textImgPos = textImgPos; 947this.restartImgPos = restartImgPos; 948this.draw(); 949 }; 950 951 952/** 953 * Dimensions used in the panel. 954 * @enum {number} 955*/ 956 GameOverPanel.dimensions = { 957 TEXT_X: 0, 958 TEXT_Y: 13, 959 TEXT_WIDTH: 191, 960 TEXT_HEIGHT: 11, 961 RESTART_WIDTH: 36, 962 RESTART_HEIGHT: 32 963 }; 964 965 966 GameOverPanel.prototype = { 967/** 968 * Update the panel dimensions. 969 * @param {number} width New canvas width. 970 * @param {number} opt_height Optional new canvas height. 971*/ 972 updateDimensions: function (width, opt_height) { 973this.canvasDimensions.WIDTH = width; 974if (opt_height) { 975this.canvasDimensions.HEIGHT = opt_height; 976 } 977 }, 978 979/** 980 * Draw the panel. 981*/ 982 draw: function () { 983var dimensions = GameOverPanel.dimensions; 984 985var centerX = this.canvasDimensions.WIDTH / 2; 986 987// Game over text. 988var textSourceX = dimensions.TEXT_X; 989var textSourceY = dimensions.TEXT_Y; 990var textSourceWidth = dimensions.TEXT_WIDTH; 991var textSourceHeight = dimensions.TEXT_HEIGHT; 992 993var textTargetX = Math.round(centerX - (dimensions.TEXT_WIDTH / 2)); 994var textTargetY = Math.round((this.canvasDimensions.HEIGHT - 25) / 3); 995var textTargetWidth = dimensions.TEXT_WIDTH; 996var textTargetHeight = dimensions.TEXT_HEIGHT; 997 998var restartSourceWidth = dimensions.RESTART_WIDTH; 999var restartSourceHeight = dimensions.RESTART_HEIGHT; 1000var restartTargetX = centerX - (dimensions.RESTART_WIDTH / 2); 1001var restartTargetY = this.canvasDimensions.HEIGHT / 2; 10021003if (IS_HIDPI) { 1004 textSourceY *= 2; 1005 textSourceX *= 2; 1006 textSourceWidth *= 2; 1007 textSourceHeight *= 2; 1008 restartSourceWidth *= 2; 1009 restartSourceHeight *= 2; 1010 } 10111012 textSourceX += this.textImgPos.x; 1013 textSourceY += this.textImgPos.y; 10141015// Game over text from sprite.1016this.canvasCtx.drawImage(Runner.imageSprite, 1017 textSourceX, textSourceY, textSourceWidth, textSourceHeight, 1018 textTargetX, textTargetY, textTargetWidth, textTargetHeight); 10191020// Restart button.1021this.canvasCtx.drawImage(Runner.imageSprite, 1022this.restartImgPos.x, this.restartImgPos.y, 1023 restartSourceWidth, restartSourceHeight, 1024 restartTargetX, restartTargetY, dimensions.RESTART_WIDTH, 1025 dimensions.RESTART_HEIGHT); 1026 } 1027 }; 102810291030//******************************************************************************10311032/** 1033 * Check for a collision. 1034 * @param {!Obstacle} obstacle 1035 * @param {!Trex} tRex T-rex object. 1036 * @param {HTMLCanvasContext} opt_canvasCtx Optional canvas context for drawing 1037 * collision boxes. 1038 * @return {Array<CollisionBox>} 1039*/1040function checkForCollision(obstacle, tRex, opt_canvasCtx) { 1041var obstacleBoxXPos = Runner.defaultDimensions.WIDTH + obstacle.xPos; 10421043// Adjustments are made to the bounding box as there is a 1 pixel white1044// border around the t-rex and obstacles.1045var tRexBox = new CollisionBox( 1046 tRex.xPos + 1, 1047 tRex.yPos + 1, 1048 tRex.config.WIDTH - 2, 1049 tRex.config.HEIGHT - 2); 10501051var obstacleBox = new CollisionBox( 1052 obstacle.xPos + 1, 1053 obstacle.yPos + 1, 1054 obstacle.typeConfig.width * obstacle.size - 2, 1055 obstacle.typeConfig.height - 2); 10561057// Debug outer box1058if (opt_canvasCtx) { 1059 drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox); 1060 } 10611062// Simple outer bounds check.1063if (boxCompare(tRexBox, obstacleBox)) { 1064var collisionBoxes = obstacle.collisionBoxes; 1065var tRexCollisionBoxes = tRex.ducking ? 1066 Trex.collisionBoxes.DUCKING : Trex.collisionBoxes.RUNNING; 10671068// Detailed axis aligned box check.1069for (var t = 0; t < tRexCollisionBoxes.length; t++) { 1070for (var i = 0; i < collisionBoxes.length; i++) { 1071// Adjust the box to actual positions.1072var adjTrexBox = 1073 createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox); 1074var adjObstacleBox = 1075 createAdjustedCollisionBox(collisionBoxes[i], obstacleBox); 1076var crashed = boxCompare(adjTrexBox, adjObstacleBox); 10771078// Draw boxes for debug.1079if (opt_canvasCtx) { 1080 drawCollisionBoxes(opt_canvasCtx, adjTrexBox, adjObstacleBox); 1081 } 10821083if (crashed) { 1084return [adjTrexBox, adjObstacleBox]; 1085 } 1086 } 1087 } 1088 } 1089returnfalse; 1090 }; 109110921093/** 1094 * Adjust the collision box. 1095 * @param {!CollisionBox} box The original box. 1096 * @param {!CollisionBox} adjustment Adjustment box. 1097 * @return {CollisionBox} The adjusted collision box object. 1098*/1099function createAdjustedCollisionBox(box, adjustment) { 1100returnnew CollisionBox( 1101 box.x + adjustment.x, 1102 box.y + adjustment.y, 1103 box.width, 1104 box.height); 1105 }; 110611071108/** 1109 * Draw the collision boxes for debug. 1110*/1111function drawCollisionBoxes(canvasCtx, tRexBox, obstacleBox) { 1112 canvasCtx.save(); 1113 canvasCtx.strokeStyle = ‘#f00‘; 1114 canvasCtx.strokeRect(tRexBox.x, tRexBox.y, tRexBox.width, tRexBox.height); 11151116 canvasCtx.strokeStyle = ‘#0f0‘; 1117 canvasCtx.strokeRect(obstacleBox.x, obstacleBox.y, 1118 obstacleBox.width, obstacleBox.height); 1119 canvasCtx.restore(); 1120 }; 112111221123/** 1124 * Compare two collision boxes for a collision. 1125 * @param {CollisionBox} tRexBox 1126 * @param {CollisionBox} obstacleBox 1127 * @return {boolean} Whether the boxes intersected. 1128*/1129function boxCompare(tRexBox, obstacleBox) { 1130var crashed = false; 1131var tRexBoxX = tRexBox.x; 1132var tRexBoxY = tRexBox.y; 11331134var obstacleBoxX = obstacleBox.x; 1135var obstacleBoxY = obstacleBox.y; 11361137// Axis-Aligned Bounding Box method.1138if (tRexBox.x < obstacleBoxX + obstacleBox.width && 1139 tRexBox.x + tRexBox.width > obstacleBoxX && 1140 tRexBox.y < obstacleBox.y + obstacleBox.height && 1141 tRexBox.height + tRexBox.y > obstacleBox.y) { 1142 crashed = true; 1143 } 11441145return crashed; 1146 }; 114711481149//******************************************************************************11501151/** 1152 * Collision box object. 1153 * @param {number} x X position. 1154 * @param {number} y Y Position. 1155 * @param {number} w Width. 1156 * @param {number} h Height. 1157*/1158function CollisionBox(x, y, w, h) { 1159this.x = x; 1160this.y = y; 1161this.width = w; 1162this.height = h; 1163 }; 116411651166//******************************************************************************11671168/** 1169 * Obstacle. 1170 * @param {HTMLCanvasCtx} canvasCtx 1171 * @param {Obstacle.type} type 1172 * @param {Object} spritePos Obstacle position in sprite. 1173 * @param {Object} dimensions 1174 * @param {number} gapCoefficient Mutipler in determining the gap. 1175 * @param {number} speed 1176*/1177function Obstacle(canvasCtx, type, spriteImgPos, dimensions, 1178 gapCoefficient, speed) { 11791180this.canvasCtx = canvasCtx; 1181this.spritePos = spriteImgPos; 1182this.typeConfig = type; 1183this.gapCoefficient = gapCoefficient; 1184this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH); 1185this.dimensions = dimensions; 1186this.remove = false; 1187this.xPos = 0; 1188this.yPos = 0; 1189this.width = 0; 1190this.collisionBoxes = []; 1191this.gap = 0; 1192this.speedOffset = 0; 11931194// For animated obstacles.1195this.currentFrame = 0; 1196this.timer = 0; 11971198this.init(speed); 1199 }; 12001201/** 1202 * Coefficient for calculating the maximum gap. 1203 * @const 1204*/1205 Obstacle.MAX_GAP_COEFFICIENT = 1.5; 12061207/** 1208 * Maximum obstacle grouping count. 1209 * @const 1210*/1211 Obstacle.MAX_OBSTACLE_LENGTH = 3, 121212131214 Obstacle.prototype = { 1215/** 1216 * Initialise the DOM for the obstacle. 1217 * @param {number} speed 1218*/1219 init: function (speed) { 1220this.cloneCollisionBoxes(); 12211222// Only allow sizing if we‘re at the right speed.1223if (this.size > 1 && this.typeConfig.multipleSpeed > speed) { 1224this.size = 1; 1225 } 12261227this.width = this.typeConfig.width * this.size; 1228this.xPos = this.dimensions.WIDTH - this.width; 12291230// Check if obstacle can be positioned at various heights.1231if (Array.isArray(this.typeConfig.yPos)) { 1232var yPosConfig = IS_MOBILE ? this.typeConfig.yPosMobile : 1233this.typeConfig.yPos; 1234this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)]; 1235 } else { 1236this.yPos = this.typeConfig.yPos; 1237 } 12381239this.draw(); 12401241// Make collision box adjustments,1242// Central box is adjusted to the size as one box.1243// ____ ______ ________1244// _| |-| _| |-| _| |-|1245// | |<->| | | |<--->| | | |<----->| |1246// | | 1 | | | | 2 | | | | 3 | |1247// |_|___|_| |_|_____|_| |_|_______|_|1248// 1249if (this.size > 1) { 1250this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width - 1251this.collisionBoxes[2].width; 1252this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width; 1253 } 12541255// For obstacles that go at a different speed from the horizon.1256if (this.typeConfig.speedOffset) { 1257this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset : 1258 -this.typeConfig.speedOffset; 1259 } 12601261this.gap = this.getGap(this.gapCoefficient, speed); 1262 }, 12631264/** 1265 * Draw and crop based on size. 1266*/1267 draw: function () { 1268var sourceWidth = this.typeConfig.width; 1269var sourceHeight = this.typeConfig.height; 12701271if (IS_HIDPI) { 1272 sourceWidth = sourceWidth * 2; 1273 sourceHeight = sourceHeight * 2; 1274 } 12751276// X position in sprite.1277var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)) + 1278this.spritePos.x; 12791280// Animation frames.1281if (this.currentFrame > 0) { 1282 sourceX += sourceWidth * this.currentFrame; 1283 } 12841285this.canvasCtx.drawImage(Runner.imageSprite, 1286 sourceX, this.spritePos.y, 1287 sourceWidth * this.size, sourceHeight, 1288this.xPos, this.yPos, 1289this.typeConfig.width * this.size, this.typeConfig.height); 1290 }, 12911292/** 1293 * Obstacle frame update. 1294 * @param {number} deltaTime 1295 * @param {number} speed 1296*/1297 update: function (deltaTime, speed) { 1298if (!this.remove) { 1299if (this.typeConfig.speedOffset) { 1300 speed += this.speedOffset; 1301 } 1302this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime); 13031304// Update frame1305if (this.typeConfig.numFrames) { 1306this.timer += deltaTime; 1307if (this.timer >= this.typeConfig.frameRate) { 1308this.currentFrame = 1309this.currentFrame == this.typeConfig.numFrames - 1 ? 1310 0 : this.currentFrame + 1; 1311this.timer = 0; 1312 } 1313 } 1314this.draw(); 13151316if (!this.isVisible()) { 1317this.remove = true; 1318 } 1319 } 1320 }, 13211322/** 1323 * Calculate a random gap size. 1324 * - Minimum gap gets wider as speed increses 1325 * @param {number} gapCoefficient 1326 * @param {number} speed 1327 * @return {number} The gap size. 1328*/1329 getGap: function (gapCoefficient, speed) { 1330var minGap = Math.round(this.width * speed + 1331this.typeConfig.minGap * gapCoefficient); 1332var maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT); 1333return getRandomNum(minGap, maxGap); 1334 }, 13351336/** 1337 * Check if obstacle is visible. 1338 * @return {boolean} Whether the obstacle is in the game area. 1339*/1340 isVisible: function () { 1341returnthis.xPos + this.width > 0; 1342 }, 13431344/** 1345 * Make a copy of the collision boxes, since these will change based on 1346 * obstacle type and size. 1347*/1348 cloneCollisionBoxes: function () { 1349var collisionBoxes = this.typeConfig.collisionBoxes; 13501351for (var i = collisionBoxes.length - 1; i >= 0; i--) { 1352this.collisionBoxes[i] = new CollisionBox(collisionBoxes[i].x, 1353 collisionBoxes[i].y, collisionBoxes[i].width, 1354 collisionBoxes[i].height); 1355 } 1356 } 1357 }; 135813591360/** 1361 * Obstacle definitions. 1362 * minGap: minimum pixel space betweeen obstacles. 1363 * multipleSpeed: Speed at which multiples are allowed. 1364 * speedOffset: speed faster / slower than the horizon. 1365 * minSpeed: Minimum speed which the obstacle can make an appearance. 1366*/1367 Obstacle.types = [ 1368 { 1369 type: ‘CACTUS_SMALL‘, 1370 width: 17, 1371 height: 35, 1372 yPos: 105, 1373 multipleSpeed: 4, 1374 minGap: 120, 1375 minSpeed: 0, 1376 collisionBoxes: [ 1377new CollisionBox(0, 7, 5, 27), 1378new CollisionBox(4, 0, 6, 34), 1379new CollisionBox(10, 4, 7, 14) 1380 ] 1381 }, 1382 { 1383 type: ‘CACTUS_LARGE‘, 1384 width: 25, 1385 height: 50, 1386 yPos: 90, 1387 multipleSpeed: 7, 1388 minGap: 120, 1389 minSpeed: 0, 1390 collisionBoxes: [ 1391new CollisionBox(0, 12, 7, 38), 1392new CollisionBox(8, 0, 7, 49), 1393new CollisionBox(13, 10, 10, 38) 1394 ] 1395 }, 1396 { 1397 type: ‘PTERODACTYL‘, 1398 width: 46, 1399 height: 40, 1400 yPos: [100, 75, 50], // Variable height.1401 yPosMobile: [100, 50], // Variable height mobile.1402 multipleSpeed: 999, 1403 minSpeed: 8.5, 1404 minGap: 150, 1405 collisionBoxes: [ 1406new CollisionBox(15, 15, 16, 5), 1407new CollisionBox(18, 21, 24, 6), 1408new CollisionBox(2, 14, 4, 3), 1409new CollisionBox(6, 10, 4, 7), 1410new CollisionBox(10, 8, 6, 9) 1411 ], 1412 numFrames: 2, 1413 frameRate: 1000 / 6, 1414 speedOffset: .8 1415 } 1416 ]; 141714181419//******************************************************************************1420/** 1421 * T-rex game character. 1422 * @param {HTMLCanvas} canvas 1423 * @param {Object} spritePos Positioning within image sprite. 1424 * @constructor 1425*/1426function Trex(canvas, spritePos) { 1427this.canvas = canvas; 1428this.canvasCtx = canvas.getContext(‘2d‘); 1429this.spritePos = spritePos; 1430this.xPos = 0; 1431this.yPos = 0; 1432// Position when on the ground.1433this.groundYPos = 0; 1434this.currentFrame = 0; 1435this.currentAnimFrames = []; 1436this.blinkDelay = 0; 1437this.animStartTime = 0; 1438this.timer = 0; 1439this.msPerFrame = 1000 / FPS; 1440this.config = Trex.config; 1441// Current status.1442this.status = Trex.status.WAITING; 14431444this.jumping = false; 1445this.ducking = false; 1446this.jumpVelocity = 0; 1447this.reachedMinHeight = false; 1448this.speedDrop = false; 1449this.jumpCount = 0; 1450this.jumpspotX = 0; 14511452this.init(); 1453 }; 145414551456/** 1457 * T-rex player config. 1458 * @enum {number} 1459*/1460 Trex.config = { 1461 DROP_VELOCITY: -5, 1462 GRAVITY: 0.6, 1463 HEIGHT: 47, 1464 HEIGHT_DUCK: 25, 1465 INIITAL_JUMP_VELOCITY: -10, 1466 INTRO_DURATION: 1500, 1467 MAX_JUMP_HEIGHT: 30, 1468 MIN_JUMP_HEIGHT: 30, 1469 SPEED_DROP_COEFFICIENT: 3, 1470 SPRITE_WIDTH: 262, 1471 START_X_POS: 50, 1472 WIDTH: 44, 1473 WIDTH_DUCK: 59 1474 }; 147514761477/** 1478 * Used in collision detection. 1479 * @type {Array<CollisionBox>} 1480*/1481 Trex.collisionBoxes = { 1482 DUCKING: [ 1483new CollisionBox(1, 18, 55, 25) 1484 ], 1485 RUNNING: [ 1486new CollisionBox(22, 0, 17, 16), 1487new CollisionBox(1, 18, 30, 9), 1488new CollisionBox(10, 35, 14, 8), 1489new CollisionBox(1, 24, 29, 5), 1490new CollisionBox(5, 30, 21, 4), 1491new CollisionBox(9, 34, 15, 4) 1492 ] 1493 }; 149414951496/** 1497 * Animation states. 1498 * @enum {string} 1499*/1500 Trex.status = { 1501 CRASHED: ‘CRASHED‘, 1502 DUCKING: ‘DUCKING‘, 1503 JUMPING: ‘JUMPING‘, 1504 RUNNING: ‘RUNNING‘, 1505 WAITING: ‘WAITING‘ 1506 }; 15071508/** 1509 * Blinking coefficient. 1510 * @const 1511*/1512 Trex.BLINK_TIMING = 7000; 151315141515/** 1516 * Animation config for different states. 1517 * @enum {Object} 1518*/1519 Trex.animFrames = { 1520 WAITING: { 1521 frames: [44, 0], 1522 msPerFrame: 1000 / 3 1523 }, 1524 RUNNING: { 1525 frames: [88, 132], 1526 msPerFrame: 1000 / 12 1527 }, 1528 CRASHED: { 1529 frames: [220], 1530 msPerFrame: 1000 / 60 1531 }, 1532 JUMPING: { 1533 frames: [0], 1534 msPerFrame: 1000 / 60 1535 }, 1536 DUCKING: { 1537 frames: [262, 321], 1538 msPerFrame: 1000 / 8 1539 } 1540 }; 154115421543 Trex.prototype = { 1544/** 1545 * T-rex player initaliser. 1546 * Sets the t-rex to blink at random intervals. 1547*/1548 init: function () { 1549this.blinkDelay = this.setBlinkDelay(); 1550this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT - 1551 Runner.config.BOTTOM_PAD; 1552this.yPos = this.groundYPos; 1553this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT; 15541555this.draw(0, 0); 1556this.update(0, Trex.status.WAITING); 1557 }, 15581559/** 1560 * Setter for the jump velocity. 1561 * The approriate drop velocity is also set. 1562*/1563 setJumpVelocity: function (setting) { 1564this.config.INIITAL_JUMP_VELOCITY = -setting; 1565this.config.DROP_VELOCITY = -setting / 2; 1566 }, 15671568/** 1569 * Set the animation status. 1570 * @param {!number} deltaTime 1571 * @param {Trex.status} status Optional status to switch to. 1572*/1573 update: function (deltaTime, opt_status) { 1574this.timer += deltaTime; 15751576// Update the status.1577if (opt_status) { 1578this.status = opt_status; 1579this.currentFrame = 0; 1580this.msPerFrame = Trex.animFrames[opt_status].msPerFrame; 1581this.currentAnimFrames = Trex.animFrames[opt_status].frames; 15821583if (opt_status == Trex.status.WAITING) { 1584this.animStartTime = getTimeStamp(); 1585this.setBlinkDelay(); 1586 } 1587 } 15881589// Game intro animation, T-rex moves in from the left.1590if (this.playingIntro && this.xPos < this.config.START_X_POS) { 1591this.xPos += Math.round((this.config.START_X_POS / 1592this.config.INTRO_DURATION) * deltaTime); 1593 } 15941595if (this.status == Trex.status.WAITING) { 1596this.blink(getTimeStamp()); 1597 } else { 1598this.draw(this.currentAnimFrames[this.currentFrame], 0); 1599 } 16001601// Update the frame position.1602if (this.timer >= this.msPerFrame) { 1603this.currentFrame = this.currentFrame == 1604this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1; 1605this.timer = 0; 1606 } 16071608// Speed drop becomes duck if the down key is still being pressed.1609if (this.speedDrop && this.yPos == this.groundYPos) { 1610this.speedDrop = false; 1611this.setDuck(true); 1612 } 1613 }, 16141615/** 1616 * Draw the t-rex to a particular position. 1617 * @param {number} x 1618 * @param {number} y 1619*/1620 draw: function (x, y) { 1621var sourceX = x; 1622var sourceY = y; 1623var sourceWidth = this.ducking && this.status != Trex.status.CRASHED ? 1624this.config.WIDTH_DUCK : this.config.WIDTH; 1625var sourceHeight = this.config.HEIGHT; 16261627if (IS_HIDPI) { 1628 sourceX *= 2; 1629 sourceY *= 2; 1630 sourceWidth *= 2; 1631 sourceHeight *= 2; 1632 } 16331634// Adjustments for sprite sheet position.1635 sourceX += this.spritePos.x; 1636 sourceY += this.spritePos.y; 16371638// Ducking.1639if (this.ducking && this.status != Trex.status.CRASHED) { 1640this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY, 1641 sourceWidth, sourceHeight, 1642this.xPos, this.yPos, 1643this.config.WIDTH_DUCK, this.config.HEIGHT); 1644 } else { 1645// Crashed whilst ducking. Trex is standing up so needs adjustment.1646if (this.ducking && this.status == Trex.status.CRASHED) { 1647this.xPos++; 1648 } 1649// Standing / running1650this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY, 1651 sourceWidth, sourceHeight, 1652this.xPos, this.yPos, 1653this.config.WIDTH, this.config.HEIGHT); 1654 } 1655 }, 16561657/** 1658 * Sets a random time for the blink to happen. 1659*/1660 setBlinkDelay: function () { 1661this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING); 1662 }, 16631664/** 1665 * Make t-rex blink at random intervals. 1666 * @param {number} time Current time in milliseconds. 1667*/1668 blink: function (time) { 1669var deltaTime = time - this.animStartTime; 16701671if (deltaTime >= this.blinkDelay) { 1672this.draw(this.currentAnimFrames[this.currentFrame], 0); 16731674if (this.currentFrame == 1) { 1675// Set new random delay to blink.1676this.setBlinkDelay(); 1677this.animStartTime = time; 1678 } 1679 } 1680 }, 16811682/** 1683 * Initialise a jump. 1684 * @param {number} speed 1685*/1686 startJump: function (speed) { 1687if (!this.jumping) { 1688this.update(0, Trex.status.JUMPING); 1689// Tweak the jump velocity based on the speed.1690this.jumpVelocity = this.config.INIITAL_JUMP_VELOCITY - (speed / 10); 1691this.jumping = true; 1692this.reachedMinHeight = false; 1693this.speedDrop = false; 1694 } 1695 }, 16961697/** 1698 * Jump is complete, falling down. 1699*/1700 endJump: function () { 1701if (this.reachedMinHeight && 1702this.jumpVelocity < this.config.DROP_VELOCITY) { 1703this.jumpVelocity = this.config.DROP_VELOCITY; 1704 } 1705 }, 17061707/** 1708 * Update frame for a jump. 1709 * @param {number} deltaTime 1710 * @param {number} speed 1711*/1712 updateJump: function (deltaTime, speed) { 1713var msPerFrame = Trex.animFrames[this.status].msPerFrame; 1714var framesElapsed = deltaTime / msPerFrame; 17151716// Speed drop makes Trex fall faster.1717if (this.speedDrop) { 1718this.yPos += Math.round(this.jumpVelocity * 1719this.config.SPEED_DROP_COEFFICIENT * framesElapsed); 1720 } else { 1721this.yPos += Math.round(this.jumpVelocity * framesElapsed); 1722 } 17231724this.jumpVelocity += this.config.GRAVITY * framesElapsed; 17251726// Minimum height has been reached.1727if (this.yPos < this.minJumpHeight || this.speedDrop) { 1728this.reachedMinHeight = true; 1729 } 17301731// Reached max height1732if (this.yPos < this.config.MAX_JUMP_HEIGHT || this.speedDrop) { 1733this.endJump(); 1734 } 17351736// Back down at ground level. Jump completed.1737if (this.yPos > this.groundYPos) { 1738this.reset(); 1739this.jumpCount++; 1740 } 17411742this.update(deltaTime); 1743 }, 17441745/** 1746 * Set the speed drop. Immediately cancels the current jump. 1747*/1748 setSpeedDrop: function () { 1749this.speedDrop = true; 1750this.jumpVelocity = 1; 1751 }, 17521753/** 1754 * @param {boolean} isDucking. 1755*/1756 setDuck: function (isDucking) { 1757if (isDucking && this.status != Trex.status.DUCKING) { 1758this.update(0, Trex.status.DUCKING); 1759this.ducking = true; 1760 } elseif (this.status == Trex.status.DUCKING) { 1761this.update(0, Trex.status.RUNNING); 1762this.ducking = false; 1763 } 1764 }, 17651766/** 1767 * Reset the t-rex to running at start of game. 1768*/1769 reset: function () { 1770this.yPos = this.groundYPos; 1771this.jumpVelocity = 0; 1772this.jumping = false; 1773this.ducking = false; 1774this.update(0, Trex.status.RUNNING); 1775this.midair = false; 1776this.speedDrop = false; 1777this.jumpCount = 0; 1778 } 1779 }; 178017811782//******************************************************************************17831784/** 1785 * Handles displaying the distance meter. 1786 * @param {!HTMLCanvasElement} canvas 1787 * @param {Object} spritePos Image position in sprite. 1788 * @param {number} canvasWidth 1789 * @constructor 1790*/1791function DistanceMeter(canvas, spritePos, canvasWidth) { 1792this.canvas = canvas; 1793this.canvasCtx = canvas.getContext(‘2d‘); 1794this.image = Runner.imageSprite; 1795this.spritePos = spritePos; 1796this.x = 0; 1797this.y = 5; 17981799this.currentDistance = 0; 1800this.maxScore = 0; 1801this.highScore = 0; 1802this.container = null; 18031804this.digits = []; 1805this.acheivement = false; 1806this.defaultString = ‘‘; 1807this.flashTimer = 0; 1808this.flashIterations = 0; 18091810this.config = DistanceMeter.config; 1811this.maxScoreUnits = this.config.MAX_DISTANCE_UNITS; 1812this.init(canvasWidth); 1813 }; 181418151816/** 1817 * @enum {number} 1818*/1819 DistanceMeter.dimensions = { 1820 WIDTH: 10, 1821 HEIGHT: 13, 1822 DEST_WIDTH: 11 1823 }; 182418251826/** 1827 * Y positioning of the digits in the sprite sheet. 1828 * X position is always 0. 1829 * @type {Array<number>} 1830*/1831 DistanceMeter.yPos = [0, 13, 27, 40, 53, 67, 80, 93, 107, 120]; 183218331834/** 1835 * Distance meter config. 1836 * @enum {number} 1837*/1838 DistanceMeter.config = { 1839// Number of digits.1840 MAX_DISTANCE_UNITS: 5, 18411842// Distance that causes achievement animation.1843 ACHIEVEMENT_DISTANCE: 100, 18441845// Used for conversion from pixel distance to a scaled unit.1846 COEFFICIENT: 0.025, 18471848// Flash duration in milliseconds.1849 FLASH_DURATION: 1000 / 4, 18501851// Flash iterations for achievement animation.1852 FLASH_ITERATIONS: 3 1853 }; 185418551856 DistanceMeter.prototype = { 1857/** 1858 * Initialise the distance meter to ‘00000‘. 1859 * @param {number} width Canvas width in px. 1860*/1861 init: function (width) { 1862var maxDistanceStr = ‘‘; 18631864this.calcXPos(width); 1865this.maxScore = this.maxScoreUnits; 1866for (var i = 0; i < this.maxScoreUnits; i++) { 1867this.draw(i, 0); 1868this.defaultString += ‘0‘; 1869 maxDistanceStr += ‘9‘; 1870 } 18711872this.maxScore = parseInt(maxDistanceStr); 1873 }, 18741875/** 1876 * Calculate the xPos in the canvas. 1877 * @param {number} canvasWidth 1878*/1879 calcXPos: function (canvasWidth) { 1880this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH * 1881 (this.maxScoreUnits + 1)); 1882 }, 18831884/** 1885 * Draw a digit to canvas. 1886 * @param {number} digitPos Position of the digit. 1887 * @param {number} value Digit value 0-9. 1888 * @param {boolean} opt_highScore Whether drawing the high score. 1889*/1890 draw: function (digitPos, value, opt_highScore) { 1891var sourceWidth = DistanceMeter.dimensions.WIDTH; 1892var sourceHeight = DistanceMeter.dimensions.HEIGHT; 1893var sourceX = DistanceMeter.dimensions.WIDTH * value; 1894var sourceY = 0; 18951896var targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH; 1897var targetY = this.y; 1898var targetWidth = DistanceMeter.dimensions.WIDTH; 1899var targetHeight = DistanceMeter.dimensions.HEIGHT; 19001901// For high DPI we 2x source values.1902if (IS_HIDPI) { 1903 sourceWidth *= 2; 1904 sourceHeight *= 2; 1905 sourceX *= 2; 1906 } 19071908 sourceX += this.spritePos.x; 1909 sourceY += this.spritePos.y; 19101911this.canvasCtx.save(); 19121913if (opt_highScore) { 1914// Left of the current score.1915var highScoreX = this.x - (this.maxScoreUnits * 2) * 1916 DistanceMeter.dimensions.WIDTH; 1917this.canvasCtx.translate(highScoreX, this.y); 1918 } else { 1919this.canvasCtx.translate(this.x, this.y); 1920 } 19211922this.canvasCtx.drawImage(this.image, sourceX, sourceY, 1923 sourceWidth, sourceHeight, 1924 targetX, targetY, 1925 targetWidth, targetHeight 1926 ); 19271928this.canvasCtx.restore(); 1929 }, 19301931/** 1932 * Covert pixel distance to a ‘real‘ distance. 1933 * @param {number} distance Pixel distance ran. 1934 * @return {number} The ‘real‘ distance ran. 1935*/1936 getActualDistance: function (distance) { 1937return distance ? Math.round(distance * this.config.COEFFICIENT) : 0; 1938 }, 19391940/** 1941 * Update the distance meter. 1942 * @param {number} distance 1943 * @param {number} deltaTime 1944 * @return {boolean} Whether the acheivement sound fx should be played. 1945*/1946 update: function (deltaTime, distance) { 1947var paint = true; 1948var playSound = false; 19491950if (!this.acheivement) { 1951 distance = this.getActualDistance(distance); 19521953// Score has gone beyond the initial digit count.1954if (distance > this.maxScore && this.maxScoreUnits == 1955this.config.MAX_DISTANCE_UNITS) { 1956this.maxScoreUnits++; 1957this.maxScore = parseInt(this.maxScore + ‘9‘); 1958 } else { 1959this.distance = 0; 1960 } 19611962if (distance > 0) { 1963// Acheivement unlocked1964if (distance % this.config.ACHIEVEMENT_DISTANCE == 0) { 1965// Flash score and play sound.1966this.acheivement = true; 1967this.flashTimer = 0; 1968 playSound = true; 1969 } 19701971// Create a string representation of the distance with leading 0.1972var distanceStr = (this.defaultString + 1973 distance).substr(-this.maxScoreUnits); 1974this.digits = distanceStr.split(‘‘); 1975 } else { 1976this.digits = this.defaultString.split(‘‘); 1977 } 1978 } else { 1979// Control flashing of the score on reaching acheivement.1980if (this.flashIterations <= this.config.FLASH_ITERATIONS) { 1981this.flashTimer += deltaTime; 19821983if (this.flashTimer < this.config.FLASH_DURATION) { 1984 paint = false; 1985 } elseif (this.flashTimer > 1986this.config.FLASH_DURATION * 2) { 1987this.flashTimer = 0; 1988this.flashIterations++; 1989 } 1990 } else { 1991this.acheivement = false; 1992this.flashIterations = 0; 1993this.flashTimer = 0; 1994 } 1995 } 19961997// Draw the digits if not flashing.1998if (paint) { 1999for (var i = this.digits.length - 1; i >= 0; i--) { 2000this.draw(i, parseInt(this.digits[i])); 2001 } 2002 } 20032004this.drawHighScore(); 20052006return playSound; 2007 }, 20082009/** 2010 * Draw the high score. 2011*/2012 drawHighScore: function () { 2013this.canvasCtx.save(); 2014this.canvasCtx.globalAlpha = .8; 2015for (var i = this.highScore.length - 1; i >= 0; i--) { 2016this.draw(i, parseInt(this.highScore[i], 10), true); 2017 } 2018this.canvasCtx.restore(); 2019 }, 20202021/** 2022 * Set the highscore as a array string. 2023 * Position of char in the sprite: H - 10, I - 11. 2024 * @param {number} distance Distance ran in pixels. 2025*/2026 setHighScore: function (distance) { 2027 distance = this.getActualDistance(distance); 2028var highScoreStr = (this.defaultString + 2029 distance).substr(-this.maxScoreUnits); 20302031this.highScore = [‘10‘, ‘11‘, ‘‘].concat(highScoreStr.split(‘‘)); 2032 }, 20332034/** 2035 * Reset the distance meter back to ‘00000‘. 2036*/2037 reset: function () { 2038this.update(0); 2039this.acheivement = false; 2040 } 2041 }; 204220432044//******************************************************************************20452046/** 2047 * Cloud background item. 2048 * Similar to an obstacle object but without collision boxes. 2049 * @param {HTMLCanvasElement} canvas Canvas element. 2050 * @param {Object} spritePos Position of image in sprite. 2051 * @param {number} containerWidth 2052*/2053function Cloud(canvas, spritePos, containerWidth) { 2054this.canvas = canvas; 2055this.canvasCtx = this.canvas.getContext(‘2d‘); 2056this.spritePos = spritePos; 2057this.containerWidth = containerWidth; 2058this.xPos = containerWidth; 2059this.yPos = 0; 2060this.remove = false; 2061this.cloudGap = getRandomNum(Cloud.config.MIN_CLOUD_GAP, 2062 Cloud.config.MAX_CLOUD_GAP); 20632064this.init(); 2065 }; 206620672068/** 2069 * Cloud object config. 2070 * @enum {number} 2071*/2072 Cloud.config = { 2073 HEIGHT: 14, 2074 MAX_CLOUD_GAP: 400, 2075 MAX_SKY_LEVEL: 30, 2076 MIN_CLOUD_GAP: 100, 2077 MIN_SKY_LEVEL: 71, 2078 WIDTH: 46 2079 }; 208020812082 Cloud.prototype = { 2083/** 2084 * Initialise the cloud. Sets the Cloud height. 2085*/2086 init: function () { 2087this.yPos = getRandomNum(Cloud.config.MAX_SKY_LEVEL, 2088 Cloud.config.MIN_SKY_LEVEL); 2089this.draw(); 2090 }, 20912092/** 2093 * Draw the cloud. 2094*/2095 draw: function () { 2096this.canvasCtx.save(); 2097var sourceWidth = Cloud.config.WIDTH; 2098var sourceHeight = Cloud.config.HEIGHT; 20992100if (IS_HIDPI) { 2101 sourceWidth = sourceWidth * 2; 2102 sourceHeight = sourceHeight * 2; 2103 } 21042105this.canvasCtx.drawImage(Runner.imageSprite, this.spritePos.x, 2106this.spritePos.y, 2107 sourceWidth, sourceHeight, 2108this.xPos, this.yPos, 2109 Cloud.config.WIDTH, Cloud.config.HEIGHT); 21102111this.canvasCtx.restore(); 2112 }, 21132114/** 2115 * Update the cloud position. 2116 * @param {number} speed 2117*/2118 update: function (speed) { 2119if (!this.remove) { 2120this.xPos -= Math.ceil(speed); 2121this.draw(); 21222123// Mark as removeable if no longer in the canvas.2124if (!this.isVisible()) { 2125this.remove = true; 2126 } 2127 } 2128 }, 21292130/** 2131 * Check if the cloud is visible on the stage. 2132 * @return {boolean} 2133*/2134 isVisible: function () { 2135returnthis.xPos + Cloud.config.WIDTH > 0; 2136 } 2137 }; 213821392140//******************************************************************************21412142/** 2143 * Horizon Line. 2144 * Consists of two connecting lines. Randomly assigns a flat / bumpy horizon. 2145 * @param {HTMLCanvasElement} canvas 2146 * @param {Object} spritePos Horizon position in sprite. 2147 * @constructor 2148*/2149function HorizonLine(canvas, spritePos) { 2150this.spritePos = spritePos; 2151this.canvas = canvas; 2152this.canvasCtx = canvas.getContext(‘2d‘); 2153this.sourceDimensions = {}; 2154this.dimensions = HorizonLine.dimensions; 2155this.sourceXPos = [this.spritePos.x, this.spritePos.x + 2156this.dimensions.WIDTH]; 2157this.xPos = []; 2158this.yPos = 0; 2159this.bumpThreshold = 0.5; 21602161this.setSourceDimensions(); 2162this.draw(); 2163 }; 216421652166/** 2167 * Horizon line dimensions. 2168 * @enum {number} 2169*/2170 HorizonLine.dimensions = { 2171 WIDTH: 600, 2172 HEIGHT: 12, 2173 YPOS: 127 2174 }; 217521762177 HorizonLine.prototype = { 2178/** 2179 * Set the source dimensions of the horizon line. 2180*/2181 setSourceDimensions: function () { 21822183for (var dimension in HorizonLine.dimensions) { 2184if (IS_HIDPI) { 2185if (dimension != ‘YPOS‘) { 2186this.sourceDimensions[dimension] = 2187 HorizonLine.dimensions[dimension] * 2; 2188 } 2189 } else { 2190this.sourceDimensions[dimension] = 2191 HorizonLine.dimensions[dimension]; 2192 } 2193this.dimensions[dimension] = HorizonLine.dimensions[dimension]; 2194 } 21952196this.xPos = [0, HorizonLine.dimensions.WIDTH]; 2197this.yPos = HorizonLine.dimensions.YPOS; 2198 }, 21992200/** 2201 * Return the crop x position of a type. 2202*/2203 getRandomType: function () { 2204return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0; 2205 }, 22062207/** 2208 * Draw the horizon line. 2209*/2210 draw: function () { 2211this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[0], 2212this.spritePos.y, 2213this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT, 2214this.xPos[0], this.yPos, 2215this.dimensions.WIDTH, this.dimensions.HEIGHT); 22162217this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[1], 2218this.spritePos.y, 2219this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT, 2220this.xPos[1], this.yPos, 2221this.dimensions.WIDTH, this.dimensions.HEIGHT); 2222 }, 22232224/** 2225 * Update the x position of an indivdual piece of the line. 2226 * @param {number} pos Line position. 2227 * @param {number} increment 2228*/2229 updateXPos: function (pos, increment) { 2230var line1 = pos; 2231var line2 = pos == 0 ? 1 : 0; 22322233this.xPos[line1] -= increment; 2234this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH; 22352236if (this.xPos[line1] <= -this.dimensions.WIDTH) { 2237this.xPos[line1] += this.dimensions.WIDTH * 2; 2238this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH; 2239this.sourceXPos[line1] = this.getRandomType() + this.spritePos.x; 2240 } 2241 }, 22422243/** 2244 * Update the horizon line. 2245 * @param {number} deltaTime 2246 * @param {number} speed 2247*/2248 update: function (deltaTime, speed) { 2249var increment = Math.floor(speed * (FPS / 1000) * deltaTime); 22502251if (this.xPos[0] <= 0) { 2252this.updateXPos(0, increment); 2253 } else { 2254this.updateXPos(1, increment); 2255 } 2256this.draw(); 2257 }, 22582259/** 2260 * Reset horizon to the starting position. 2261*/2262 reset: function () { 2263this.xPos[0] = 0; 2264this.xPos[1] = HorizonLine.dimensions.WIDTH; 2265 } 2266 }; 226722682269//******************************************************************************22702271/** 2272 * Horizon background class. 2273 * @param {HTMLCanvasElement} canvas 2274 * @param {Object} spritePos Sprite positioning. 2275 * @param {Object} dimensions Canvas dimensions. 2276 * @param {number} gapCoefficient 2277 * @constructor 2278*/2279function Horizon(canvas, spritePos, dimensions, gapCoefficient) { 2280this.canvas = canvas; 2281this.canvasCtx = this.canvas.getContext(‘2d‘); 2282this.config = Horizon.config; 2283this.dimensions = dimensions; 2284this.gapCoefficient = gapCoefficient; 2285this.obstacles = []; 2286this.obstacleHistory = []; 2287this.horizonOffsets = [0, 0]; 2288this.cloudFrequency = this.config.CLOUD_FREQUENCY; 2289this.spritePos = spritePos; 22902291// Cloud2292this.clouds = []; 2293this.cloudSpeed = this.config.BG_CLOUD_SPEED; 22942295// Horizon2296this.horizonLine = null; 22972298this.init(); 2299 }; 230023012302/** 2303 * Horizon config. 2304 * @enum {number} 2305*/2306 Horizon.config = { 2307 BG_CLOUD_SPEED: 0.2, 2308 BUMPY_THRESHOLD: .3, 2309 CLOUD_FREQUENCY: .5, 2310 HORIZON_HEIGHT: 16, 2311 MAX_CLOUDS: 6 2312 }; 231323142315 Horizon.prototype = { 2316/** 2317 * Initialise the horizon. Just add the line and a cloud. No obstacles. 2318*/2319 init: function () { 2320this.addCloud(); 2321this.horizonLine = new HorizonLine(this.canvas, this.spritePos.HORIZON); 2322 }, 23232324/** 2325 * @param {number} deltaTime 2326 * @param {number} currentSpeed 2327 * @param {boolean} updateObstacles Used as an override to prevent 2328 * the obstacles from being updated / added. This happens in the 2329 * ease in section. 2330*/2331 update: function (deltaTime, currentSpeed, updateObstacles) { 2332this.runningTime += deltaTime; 2333this.horizonLine.update(deltaTime, currentSpeed); 2334this.updateClouds(deltaTime, currentSpeed); 23352336if (updateObstacles) { 2337this.updateObstacles(deltaTime, currentSpeed); 2338 } 2339 }, 23402341/** 2342 * Update the cloud positions. 2343 * @param {number} deltaTime 2344 * @param {number} currentSpeed 2345*/2346 updateClouds: function (deltaTime, speed) { 2347var cloudSpeed = this.cloudSpeed / 1000 * deltaTime * speed; 2348var numClouds = this.clouds.length; 23492350if (numClouds) { 2351for (var i = numClouds - 1; i >= 0; i--) { 2352this.clouds[i].update(cloudSpeed); 2353 } 23542355var lastCloud = this.clouds[numClouds - 1]; 23562357// Check for adding a new cloud.2358if (numClouds < this.config.MAX_CLOUDS && 2359 (this.dimensions.WIDTH - lastCloud.xPos) > lastCloud.cloudGap && 2360this.cloudFrequency > Math.random()) { 2361this.addCloud(); 2362 } 23632364// Remove expired clouds.2365this.clouds = this.clouds.filter(function (obj) { 2366return !obj.remove; 2367 }); 2368 } 2369 }, 23702371/** 2372 * Update the obstacle positions. 2373 * @param {number} deltaTime 2374 * @param {number} currentSpeed 2375*/2376 updateObstacles: function (deltaTime, currentSpeed) { 2377// Obstacles, move to Horizon layer.2378var updatedObstacles = this.obstacles.slice(0); 23792380for (var i = 0; i < this.obstacles.length; i++) { 2381var obstacle = this.obstacles[i]; 2382 obstacle.update(deltaTime, currentSpeed); 23832384// Clean up existing obstacles.2385if (obstacle.remove) { 2386 updatedObstacles.shift(); 2387 } 2388 } 2389this.obstacles = updatedObstacles; 23902391if (this.obstacles.length > 0) { 2392var lastObstacle = this.obstacles[this.obstacles.length - 1]; 23932394if (lastObstacle && !lastObstacle.followingObstacleCreated && 2395 lastObstacle.isVisible() && 2396 (lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) < 2397this.dimensions.WIDTH) { 2398this.addNewObstacle(currentSpeed); 2399 lastObstacle.followingObstacleCreated = true; 2400 } 2401 } else { 2402// Create new obstacles.2403this.addNewObstacle(currentSpeed); 2404 } 2405 }, 24062407/** 2408 * Add a new obstacle. 2409 * @param {number} currentSpeed 2410*/2411 addNewObstacle: function (currentSpeed) { 2412var obstacleTypeIndex = getRandomNum(0, Obstacle.types.length - 1); 2413var obstacleType = Obstacle.types[obstacleTypeIndex]; 24142415// Check for multiples of the same type of obstacle.2416// Also check obstacle is available at current speed.2417if (this.duplicateObstacleCheck(obstacleType.type) || 2418 currentSpeed < obstacleType.minSpeed) { 2419this.addNewObstacle(currentSpeed); 2420 } else { 2421var obstacleSpritePos = this.spritePos[obstacleType.type]; 24222423this.obstacles.push(new Obstacle(this.canvasCtx, obstacleType, 2424 obstacleSpritePos, this.dimensions, 2425this.gapCoefficient, currentSpeed)); 24262427this.obstacleHistory.unshift(obstacleType.type); 24282429if (this.obstacleHistory.length > 1) { 2430this.obstacleHistory.splice(Runner.config.MAX_OBSTACLE_DUPLICATION); 2431 } 2432 } 2433 }, 24342435/** 2436 * Returns whether the previous two obstacles are the same as the next one. 2437 * Maximum duplication is set in config value MAX_OBSTACLE_DUPLICATION. 2438 * @return {boolean} 2439*/2440 duplicateObstacleCheck: function (nextObstacleType) { 2441var duplicateCount = 0; 24422443for (var i = 0; i < this.obstacleHistory.length; i++) { 2444 duplicateCount = this.obstacleHistory[i] == nextObstacleType ? 2445 duplicateCount + 1 : 0; 2446 } 2447return duplicateCount >= Runner.config.MAX_OBSTACLE_DUPLICATION; 2448 }, 24492450/** 2451 * Reset the horizon layer. 2452 * Remove existing obstacles and reposition the horizon line. 2453*/2454 reset: function () { 2455this.obstacles = []; 2456this.horizonLine.reset(); 2457 }, 24582459/** 2460 * Update the canvas width and scaling. 2461 * @param {number} width Canvas width. 2462 * @param {number} height Canvas height. 2463*/2464 resize: function (width, height) { 2465this.canvas.width = width; 2466this.canvas.height = height; 2467 }, 24682469/** 2470 * Add a new cloud to the horizon. 2471*/2472 addCloud: function () { 2473this.clouds.push(new Cloud(this.canvas, this.spritePos.CLOUD, 2474this.dimensions.WIDTH)); 2475 } 2476 }; 2477 })();
原文:http://www.cnblogs.com/micro-chen/p/6405037.html
内容总结
以上是互联网集市为您收集整理的Chrome内置的断网Javascript 小游戏脚本示范全部内容,希望文章能够帮你解决Chrome内置的断网Javascript 小游戏脚本示范所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。