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