OLD | NEW |
1 // Copyright (c) 2014 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 (function() { | 4 (function() { |
5 'use strict'; | 5 'use strict'; |
6 /** | 6 /** |
7 * T-Rex runner. | 7 * T-Rex runner. |
8 * @param {string} outerContainerId Outer containing element id. | 8 * @param {string} outerContainerId Outer containing element id. |
9 * @param {Object} opt_config | 9 * @param {Object} opt_config |
10 * @constructor | 10 * @constructor |
(...skipping 29 matching lines...) Expand all Loading... |
40 this.runningTime = 0; | 40 this.runningTime = 0; |
41 this.msPerFrame = 1000 / FPS; | 41 this.msPerFrame = 1000 / FPS; |
42 this.currentSpeed = this.config.SPEED; | 42 this.currentSpeed = this.config.SPEED; |
43 | 43 |
44 this.obstacles = []; | 44 this.obstacles = []; |
45 | 45 |
46 this.started = false; | 46 this.started = false; |
47 this.activated = false; | 47 this.activated = false; |
48 this.crashed = false; | 48 this.crashed = false; |
49 this.paused = false; | 49 this.paused = false; |
50 | 50 this.inverted = false; |
| 51 this.invertTimer = 0; |
51 this.resizeTimerId_ = null; | 52 this.resizeTimerId_ = null; |
52 | 53 |
53 this.playCount = 0; | 54 this.playCount = 0; |
54 | 55 |
55 // Sound FX. | 56 // Sound FX. |
56 this.audioBuffer = null; | 57 this.audioBuffer = null; |
57 this.soundFx = {}; | 58 this.soundFx = {}; |
58 | 59 |
59 // Global web audio context for playing sounds. | 60 // Global web audio context for playing sounds. |
60 this.audioContext = null; | 61 this.audioContext = null; |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
104 Runner.config = { | 105 Runner.config = { |
105 ACCELERATION: 0.001, | 106 ACCELERATION: 0.001, |
106 BG_CLOUD_SPEED: 0.2, | 107 BG_CLOUD_SPEED: 0.2, |
107 BOTTOM_PAD: 10, | 108 BOTTOM_PAD: 10, |
108 CLEAR_TIME: 3000, | 109 CLEAR_TIME: 3000, |
109 CLOUD_FREQUENCY: 0.5, | 110 CLOUD_FREQUENCY: 0.5, |
110 GAMEOVER_CLEAR_TIME: 750, | 111 GAMEOVER_CLEAR_TIME: 750, |
111 GAP_COEFFICIENT: 0.6, | 112 GAP_COEFFICIENT: 0.6, |
112 GRAVITY: 0.6, | 113 GRAVITY: 0.6, |
113 INITIAL_JUMP_VELOCITY: 12, | 114 INITIAL_JUMP_VELOCITY: 12, |
| 115 INVERT_FADE_DURATION: 12000, |
| 116 INVERT_DISTANCE: 700, |
114 MAX_CLOUDS: 6, | 117 MAX_CLOUDS: 6, |
115 MAX_OBSTACLE_LENGTH: 3, | 118 MAX_OBSTACLE_LENGTH: 3, |
116 MAX_OBSTACLE_DUPLICATION: 2, | 119 MAX_OBSTACLE_DUPLICATION: 2, |
117 MAX_SPEED: 13, | 120 MAX_SPEED: 13, |
118 MIN_JUMP_HEIGHT: 35, | 121 MIN_JUMP_HEIGHT: 35, |
119 MOBILE_SPEED_COEFFICIENT: 1.2, | 122 MOBILE_SPEED_COEFFICIENT: 1.2, |
120 RESOURCE_TEMPLATE_ID: 'audio-resources', | 123 RESOURCE_TEMPLATE_ID: 'audio-resources', |
121 SPEED: 6, | 124 SPEED: 6, |
122 SPEED_DROP_COEFFICIENT: 3 | 125 SPEED_DROP_COEFFICIENT: 3 |
123 }; | 126 }; |
(...skipping 11 matching lines...) Expand all Loading... |
135 | 138 |
136 /** | 139 /** |
137 * CSS class names. | 140 * CSS class names. |
138 * @enum {string} | 141 * @enum {string} |
139 */ | 142 */ |
140 Runner.classes = { | 143 Runner.classes = { |
141 CANVAS: 'runner-canvas', | 144 CANVAS: 'runner-canvas', |
142 CONTAINER: 'runner-container', | 145 CONTAINER: 'runner-container', |
143 CRASHED: 'crashed', | 146 CRASHED: 'crashed', |
144 ICON: 'icon-offline', | 147 ICON: 'icon-offline', |
| 148 INVERTED: 'inverted', |
145 SNACKBAR: 'snackbar', | 149 SNACKBAR: 'snackbar', |
146 SNACKBAR_SHOW: 'snackbar-show', | 150 SNACKBAR_SHOW: 'snackbar-show', |
147 TOUCH_CONTROLLER: 'controller' | 151 TOUCH_CONTROLLER: 'controller' |
148 }; | 152 }; |
149 | 153 |
150 | 154 |
151 /** | 155 /** |
152 * Sprite definition layout of the spritesheet. | 156 * Sprite definition layout of the spritesheet. |
153 * @enum {Object} | 157 * @enum {Object} |
154 */ | 158 */ |
155 Runner.spriteDefinition = { | 159 Runner.spriteDefinition = { |
156 LDPI: { | 160 LDPI: { |
157 CACTUS_LARGE: {x: 332, y: 2}, | 161 CACTUS_LARGE: {x: 332, y: 2}, |
158 CACTUS_SMALL: {x: 228, y: 2}, | 162 CACTUS_SMALL: {x: 228, y: 2}, |
159 CLOUD: {x: 86, y: 2}, | 163 CLOUD: {x: 86, y: 2}, |
160 HORIZON: {x: 2, y: 54}, | 164 HORIZON: {x: 2, y: 54}, |
| 165 MOON: {x: 484, y: 2}, |
161 PTERODACTYL: {x: 134, y: 2}, | 166 PTERODACTYL: {x: 134, y: 2}, |
162 RESTART: {x: 2, y: 2}, | 167 RESTART: {x: 2, y: 2}, |
163 TEXT_SPRITE: {x: 484, y: 2}, | 168 TEXT_SPRITE: {x: 655, y: 2}, |
164 TREX: {x: 677, y: 2} | 169 TREX: {x: 848, y: 2}, |
| 170 STAR: {x: 645, y: 2} |
165 }, | 171 }, |
166 HDPI: { | 172 HDPI: { |
167 CACTUS_LARGE: {x: 652,y: 2}, | 173 CACTUS_LARGE: {x: 652, y: 2}, |
168 CACTUS_SMALL: {x: 446,y: 2}, | 174 CACTUS_SMALL: {x: 446, y: 2}, |
169 CLOUD: {x: 166,y: 2}, | 175 CLOUD: {x: 166, y: 2}, |
170 HORIZON: {x: 2,y: 104}, | 176 HORIZON: {x: 2, y: 104}, |
171 PTERODACTYL: {x: 260,y: 2}, | 177 MOON: {x: 954, y: 2}, |
172 RESTART: {x: 2,y: 2}, | 178 PTERODACTYL: {x: 260, y: 2}, |
173 TEXT_SPRITE: {x: 954,y: 2}, | 179 RESTART: {x: 2, y: 2}, |
174 TREX: {x: 1338,y: 2} | 180 TEXT_SPRITE: {x: 1294, y: 2}, |
| 181 TREX: {x: 1678, y: 2}, |
| 182 STAR: {x: 1276, y: 2} |
175 } | 183 } |
176 }; | 184 }; |
177 | 185 |
178 | 186 |
179 /** | 187 /** |
180 * Sound FX. Reference to the ID of the audio tag on interstitial page. | 188 * Sound FX. Reference to the ID of the audio tag on interstitial page. |
181 * @enum {string} | 189 * @enum {string} |
182 */ | 190 */ |
183 Runner.sounds = { | 191 Runner.sounds = { |
184 BUTTON_PRESS: 'offline-sound-press', | 192 BUTTON_PRESS: 'offline-sound-press', |
(...skipping 333 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
518 // First jump triggers the intro. | 526 // First jump triggers the intro. |
519 if (this.tRex.jumpCount == 1 && !this.playingIntro) { | 527 if (this.tRex.jumpCount == 1 && !this.playingIntro) { |
520 this.playIntro(); | 528 this.playIntro(); |
521 } | 529 } |
522 | 530 |
523 // The horizon doesn't move until the intro is over. | 531 // The horizon doesn't move until the intro is over. |
524 if (this.playingIntro) { | 532 if (this.playingIntro) { |
525 this.horizon.update(0, this.currentSpeed, hasObstacles); | 533 this.horizon.update(0, this.currentSpeed, hasObstacles); |
526 } else { | 534 } else { |
527 deltaTime = !this.started ? 0 : deltaTime; | 535 deltaTime = !this.started ? 0 : deltaTime; |
528 this.horizon.update(deltaTime, this.currentSpeed, hasObstacles); | 536 this.horizon.update(deltaTime, this.currentSpeed, hasObstacles, |
| 537 this.inverted); |
529 } | 538 } |
530 | 539 |
531 // Check for collisions. | 540 // Check for collisions. |
532 var collision = hasObstacles && | 541 var collision = hasObstacles && |
533 checkForCollision(this.horizon.obstacles[0], this.tRex); | 542 checkForCollision(this.horizon.obstacles[0], this.tRex); |
534 | 543 |
535 if (!collision) { | 544 if (!collision) { |
536 this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame; | 545 this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame; |
537 | 546 |
538 if (this.currentSpeed < this.config.MAX_SPEED) { | 547 if (this.currentSpeed < this.config.MAX_SPEED) { |
539 this.currentSpeed += this.config.ACCELERATION; | 548 this.currentSpeed += this.config.ACCELERATION; |
540 } | 549 } |
541 } else { | 550 } else { |
542 this.gameOver(); | 551 this.gameOver(); |
543 } | 552 } |
544 | 553 |
545 var playAcheivementSound = this.distanceMeter.update(deltaTime, | 554 var playAchievementSound = this.distanceMeter.update(deltaTime, |
546 Math.ceil(this.distanceRan)); | 555 Math.ceil(this.distanceRan)); |
547 | 556 |
548 if (playAcheivementSound) { | 557 if (playAchievementSound) { |
549 this.playSound(this.soundFx.SCORE); | 558 this.playSound(this.soundFx.SCORE); |
550 } | 559 } |
| 560 |
| 561 // Night mode. |
| 562 if (this.invertTimer > this.config.INVERT_FADE_DURATION) { |
| 563 this.invertTimer = 0; |
| 564 this.invertTrigger = false; |
| 565 this.invert(); |
| 566 } else if (this.invertTimer) { |
| 567 this.invertTimer += deltaTime; |
| 568 } else { |
| 569 var actualDistance = |
| 570 this.distanceMeter.getActualDistance(Math.ceil(this.distanceRan)); |
| 571 |
| 572 if (actualDistance > 0) { |
| 573 this.invertTrigger = !(actualDistance % |
| 574 this.config.INVERT_DISTANCE); |
| 575 |
| 576 if (this.invertTrigger && this.invertTimer === 0) { |
| 577 this.invertTimer += deltaTime; |
| 578 this.invert(); |
| 579 } |
| 580 } |
| 581 } |
551 } | 582 } |
552 | 583 |
553 if (!this.crashed) { | 584 if (!this.crashed) { |
554 this.tRex.update(deltaTime); | 585 this.tRex.update(deltaTime); |
555 this.raq(); | 586 this.raq(); |
556 } | 587 } |
557 }, | 588 }, |
558 | 589 |
559 /** | 590 /** |
560 * Event handler. | 591 * Event handler. |
(...skipping 206 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
767 }, | 798 }, |
768 | 799 |
769 restart: function() { | 800 restart: function() { |
770 if (!this.raqId) { | 801 if (!this.raqId) { |
771 this.playCount++; | 802 this.playCount++; |
772 this.runningTime = 0; | 803 this.runningTime = 0; |
773 this.activated = true; | 804 this.activated = true; |
774 this.crashed = false; | 805 this.crashed = false; |
775 this.distanceRan = 0; | 806 this.distanceRan = 0; |
776 this.setSpeed(this.config.SPEED); | 807 this.setSpeed(this.config.SPEED); |
777 | |
778 this.time = getTimeStamp(); | 808 this.time = getTimeStamp(); |
779 this.containerEl.classList.remove(Runner.classes.CRASHED); | 809 this.containerEl.classList.remove(Runner.classes.CRASHED); |
780 this.clearCanvas(); | 810 this.clearCanvas(); |
781 this.distanceMeter.reset(this.highestScore); | 811 this.distanceMeter.reset(this.highestScore); |
782 this.horizon.reset(); | 812 this.horizon.reset(); |
783 this.tRex.reset(); | 813 this.tRex.reset(); |
784 this.playSound(this.soundFx.BUTTON_PRESS); | 814 this.playSound(this.soundFx.BUTTON_PRESS); |
785 | 815 this.invert(true); |
786 this.update(); | 816 this.update(); |
787 } | 817 } |
788 }, | 818 }, |
789 | 819 |
790 /** | 820 /** |
791 * Pause the game if the tab is not in focus. | 821 * Pause the game if the tab is not in focus. |
792 */ | 822 */ |
793 onVisibilityChange: function(e) { | 823 onVisibilityChange: function(e) { |
794 if (document.hidden || document.webkitHidden || e.type == 'blur') { | 824 if (document.hidden || document.webkitHidden || e.type == 'blur' || |
| 825 document.visibilityState != 'visible') { |
795 this.stop(); | 826 this.stop(); |
796 } else if (!this.crashed) { | 827 } else if (!this.crashed) { |
797 this.tRex.reset(); | 828 this.tRex.reset(); |
798 this.play(); | 829 this.play(); |
799 } | 830 } |
800 }, | 831 }, |
801 | 832 |
802 /** | 833 /** |
803 * Play a sound. | 834 * Play a sound. |
804 * @param {SoundBuffer} soundBuffer | 835 * @param {SoundBuffer} soundBuffer |
805 */ | 836 */ |
806 playSound: function(soundBuffer) { | 837 playSound: function(soundBuffer) { |
807 if (soundBuffer) { | 838 if (soundBuffer) { |
808 var sourceNode = this.audioContext.createBufferSource(); | 839 var sourceNode = this.audioContext.createBufferSource(); |
809 sourceNode.buffer = soundBuffer; | 840 sourceNode.buffer = soundBuffer; |
810 sourceNode.connect(this.audioContext.destination); | 841 sourceNode.connect(this.audioContext.destination); |
811 sourceNode.start(0); | 842 sourceNode.start(0); |
812 } | 843 } |
| 844 }, |
| 845 |
| 846 /** |
| 847 * Inverts the current page / canvas colors. |
| 848 * @param {boolean} Whether to reset colors. |
| 849 */ |
| 850 invert: function(reset) { |
| 851 if (reset) { |
| 852 document.body.classList.toggle(Runner.classes.INVERTED, false); |
| 853 this.invertTimer = 0; |
| 854 this.inverted = false; |
| 855 } else { |
| 856 this.inverted = document.body.classList.toggle(Runner.classes.INVERTED, |
| 857 this.invertTrigger); |
| 858 } |
813 } | 859 } |
814 }; | 860 }; |
815 | 861 |
816 | 862 |
817 /** | 863 /** |
818 * Updates the canvas size taking into | 864 * Updates the canvas size taking into |
819 * account the backing store pixel ratio and | 865 * account the backing store pixel ratio and |
820 * the device pixel ratio. | 866 * the device pixel ratio. |
821 * | 867 * |
822 * See article by Paul Lewis: | 868 * See article by Paul Lewis: |
(...skipping 343 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1166 //****************************************************************************** | 1212 //****************************************************************************** |
1167 | 1213 |
1168 /** | 1214 /** |
1169 * Obstacle. | 1215 * Obstacle. |
1170 * @param {HTMLCanvasCtx} canvasCtx | 1216 * @param {HTMLCanvasCtx} canvasCtx |
1171 * @param {Obstacle.type} type | 1217 * @param {Obstacle.type} type |
1172 * @param {Object} spritePos Obstacle position in sprite. | 1218 * @param {Object} spritePos Obstacle position in sprite. |
1173 * @param {Object} dimensions | 1219 * @param {Object} dimensions |
1174 * @param {number} gapCoefficient Mutipler in determining the gap. | 1220 * @param {number} gapCoefficient Mutipler in determining the gap. |
1175 * @param {number} speed | 1221 * @param {number} speed |
| 1222 * @param {number} opt_xOffset |
1176 */ | 1223 */ |
1177 function Obstacle(canvasCtx, type, spriteImgPos, dimensions, | 1224 function Obstacle(canvasCtx, type, spriteImgPos, dimensions, |
1178 gapCoefficient, speed) { | 1225 gapCoefficient, speed, opt_xOffset) { |
1179 | 1226 |
1180 this.canvasCtx = canvasCtx; | 1227 this.canvasCtx = canvasCtx; |
1181 this.spritePos = spriteImgPos; | 1228 this.spritePos = spriteImgPos; |
1182 this.typeConfig = type; | 1229 this.typeConfig = type; |
1183 this.gapCoefficient = gapCoefficient; | 1230 this.gapCoefficient = gapCoefficient; |
1184 this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH); | 1231 this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH); |
1185 this.dimensions = dimensions; | 1232 this.dimensions = dimensions; |
1186 this.remove = false; | 1233 this.remove = false; |
1187 this.xPos = 0; | 1234 this.xPos = dimensions.WIDTH + (opt_xOffset || 0); |
1188 this.yPos = 0; | 1235 this.yPos = 0; |
1189 this.width = 0; | 1236 this.width = 0; |
1190 this.collisionBoxes = []; | 1237 this.collisionBoxes = []; |
1191 this.gap = 0; | 1238 this.gap = 0; |
1192 this.speedOffset = 0; | 1239 this.speedOffset = 0; |
1193 | 1240 |
1194 // For animated obstacles. | 1241 // For animated obstacles. |
1195 this.currentFrame = 0; | 1242 this.currentFrame = 0; |
1196 this.timer = 0; | 1243 this.timer = 0; |
1197 | 1244 |
(...skipping 20 matching lines...) Expand all Loading... |
1218 */ | 1265 */ |
1219 init: function(speed) { | 1266 init: function(speed) { |
1220 this.cloneCollisionBoxes(); | 1267 this.cloneCollisionBoxes(); |
1221 | 1268 |
1222 // Only allow sizing if we're at the right speed. | 1269 // Only allow sizing if we're at the right speed. |
1223 if (this.size > 1 && this.typeConfig.multipleSpeed > speed) { | 1270 if (this.size > 1 && this.typeConfig.multipleSpeed > speed) { |
1224 this.size = 1; | 1271 this.size = 1; |
1225 } | 1272 } |
1226 | 1273 |
1227 this.width = this.typeConfig.width * this.size; | 1274 this.width = this.typeConfig.width * this.size; |
1228 this.xPos = this.dimensions.WIDTH - this.width; | |
1229 | 1275 |
1230 // Check if obstacle can be positioned at various heights. | 1276 // Check if obstacle can be positioned at various heights. |
1231 if (Array.isArray(this.typeConfig.yPos)) { | 1277 if (Array.isArray(this.typeConfig.yPos)) { |
1232 var yPosConfig = IS_MOBILE ? this.typeConfig.yPosMobile : | 1278 var yPosConfig = IS_MOBILE ? this.typeConfig.yPosMobile : |
1233 this.typeConfig.yPos; | 1279 this.typeConfig.yPos; |
1234 this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)]; | 1280 this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)]; |
1235 } else { | 1281 } else { |
1236 this.yPos = this.typeConfig.yPos; | 1282 this.yPos = this.typeConfig.yPos; |
1237 } | 1283 } |
1238 | 1284 |
(...skipping 560 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1799 this.currentDistance = 0; | 1845 this.currentDistance = 0; |
1800 this.maxScore = 0; | 1846 this.maxScore = 0; |
1801 this.highScore = 0; | 1847 this.highScore = 0; |
1802 this.container = null; | 1848 this.container = null; |
1803 | 1849 |
1804 this.digits = []; | 1850 this.digits = []; |
1805 this.acheivement = false; | 1851 this.acheivement = false; |
1806 this.defaultString = ''; | 1852 this.defaultString = ''; |
1807 this.flashTimer = 0; | 1853 this.flashTimer = 0; |
1808 this.flashIterations = 0; | 1854 this.flashIterations = 0; |
| 1855 this.invertTrigger = false; |
1809 | 1856 |
1810 this.config = DistanceMeter.config; | 1857 this.config = DistanceMeter.config; |
1811 this.maxScoreUnits = this.config.MAX_DISTANCE_UNITS; | 1858 this.maxScoreUnits = this.config.MAX_DISTANCE_UNITS; |
1812 this.init(canvasWidth); | 1859 this.init(canvasWidth); |
1813 }; | 1860 }; |
1814 | 1861 |
1815 | 1862 |
1816 /** | 1863 /** |
1817 * @enum {number} | 1864 * @enum {number} |
1818 */ | 1865 */ |
(...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1942 * @param {number} distance | 1989 * @param {number} distance |
1943 * @param {number} deltaTime | 1990 * @param {number} deltaTime |
1944 * @return {boolean} Whether the acheivement sound fx should be played. | 1991 * @return {boolean} Whether the acheivement sound fx should be played. |
1945 */ | 1992 */ |
1946 update: function(deltaTime, distance) { | 1993 update: function(deltaTime, distance) { |
1947 var paint = true; | 1994 var paint = true; |
1948 var playSound = false; | 1995 var playSound = false; |
1949 | 1996 |
1950 if (!this.acheivement) { | 1997 if (!this.acheivement) { |
1951 distance = this.getActualDistance(distance); | 1998 distance = this.getActualDistance(distance); |
1952 | |
1953 // Score has gone beyond the initial digit count. | 1999 // Score has gone beyond the initial digit count. |
1954 if (distance > this.maxScore && this.maxScoreUnits == | 2000 if (distance > this.maxScore && this.maxScoreUnits == |
1955 this.config.MAX_DISTANCE_UNITS) { | 2001 this.config.MAX_DISTANCE_UNITS) { |
1956 this.maxScoreUnits++; | 2002 this.maxScoreUnits++; |
1957 this.maxScore = parseInt(this.maxScore + '9'); | 2003 this.maxScore = parseInt(this.maxScore + '9'); |
1958 } else { | 2004 } else { |
1959 this.distance = 0; | 2005 this.distance = 0; |
1960 } | 2006 } |
1961 | 2007 |
1962 if (distance > 0) { | 2008 if (distance > 0) { |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1995 } | 2041 } |
1996 | 2042 |
1997 // Draw the digits if not flashing. | 2043 // Draw the digits if not flashing. |
1998 if (paint) { | 2044 if (paint) { |
1999 for (var i = this.digits.length - 1; i >= 0; i--) { | 2045 for (var i = this.digits.length - 1; i >= 0; i--) { |
2000 this.draw(i, parseInt(this.digits[i])); | 2046 this.draw(i, parseInt(this.digits[i])); |
2001 } | 2047 } |
2002 } | 2048 } |
2003 | 2049 |
2004 this.drawHighScore(); | 2050 this.drawHighScore(); |
2005 | |
2006 return playSound; | 2051 return playSound; |
2007 }, | 2052 }, |
2008 | 2053 |
2009 /** | 2054 /** |
2010 * Draw the high score. | 2055 * Draw the high score. |
2011 */ | 2056 */ |
2012 drawHighScore: function() { | 2057 drawHighScore: function() { |
2013 this.canvasCtx.save(); | 2058 this.canvasCtx.save(); |
2014 this.canvasCtx.globalAlpha = .8; | 2059 this.canvasCtx.globalAlpha = .8; |
2015 for (var i = this.highScore.length - 1; i >= 0; i--) { | 2060 for (var i = this.highScore.length - 1; i >= 0; i--) { |
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2133 */ | 2178 */ |
2134 isVisible: function() { | 2179 isVisible: function() { |
2135 return this.xPos + Cloud.config.WIDTH > 0; | 2180 return this.xPos + Cloud.config.WIDTH > 0; |
2136 } | 2181 } |
2137 }; | 2182 }; |
2138 | 2183 |
2139 | 2184 |
2140 //****************************************************************************** | 2185 //****************************************************************************** |
2141 | 2186 |
2142 /** | 2187 /** |
| 2188 * Nightmode shows a moon and stars on the horizon. |
| 2189 */ |
| 2190 function NightMode(canvas, spritePos, containerWidth) { |
| 2191 this.spritePos = spritePos; |
| 2192 this.canvas = canvas; |
| 2193 this.canvasCtx = canvas.getContext('2d'); |
| 2194 this.xPos = containerWidth - 50; |
| 2195 this.yPos = 30; |
| 2196 this.currentPhase = 0; |
| 2197 this.opacity = 0; |
| 2198 this.containerWidth = containerWidth; |
| 2199 this.stars = []; |
| 2200 this.drawStars = false; |
| 2201 this.placeStars(); |
| 2202 }; |
| 2203 |
| 2204 /** |
| 2205 * @enum {number} |
| 2206 */ |
| 2207 NightMode.config = { |
| 2208 FADE_SPEED: 0.035, |
| 2209 HEIGHT: 40, |
| 2210 MOON_SPEED: 0.25, |
| 2211 NUM_STARS: 2, |
| 2212 STAR_SIZE: 9, |
| 2213 STAR_SPEED: 0.3, |
| 2214 STAR_MAX_Y: 70, |
| 2215 WIDTH: 20 |
| 2216 }; |
| 2217 |
| 2218 NightMode.phases = [140, 120, 100, 60, 40, 20, 0]; |
| 2219 |
| 2220 NightMode.prototype = { |
| 2221 /** |
| 2222 * Update moving moon, changing phases. |
| 2223 * @param {boolean} activated Whether night mode is activated. |
| 2224 * @param {number} delta |
| 2225 */ |
| 2226 update: function(activated, delta) { |
| 2227 // Moon phase. |
| 2228 if (activated && this.opacity == 0) { |
| 2229 this.currentPhase++; |
| 2230 |
| 2231 if (this.currentPhase >= NightMode.phases.length) { |
| 2232 this.currentPhase = 0; |
| 2233 } |
| 2234 } |
| 2235 |
| 2236 // Fade in / out. |
| 2237 if (activated && (this.opacity < 1 || this.opacity == 0)) { |
| 2238 this.opacity += NightMode.config.FADE_SPEED; |
| 2239 } else if (this.opacity > 0) { |
| 2240 this.opacity -= NightMode.config.FADE_SPEED; |
| 2241 } |
| 2242 |
| 2243 // Set moon positioning. |
| 2244 if (this.opacity > 0) { |
| 2245 this.xPos = this.updateXPos(this.xPos, NightMode.config.MOON_SPEED); |
| 2246 |
| 2247 // Update stars. |
| 2248 if (this.drawStars) { |
| 2249 for (var i = 0; i < NightMode.config.NUM_STARS; i++) { |
| 2250 this.stars[i].x = this.updateXPos(this.stars[i].x, |
| 2251 NightMode.config.STAR_SPEED); |
| 2252 } |
| 2253 } |
| 2254 this.draw(); |
| 2255 } else { |
| 2256 this.opacity = 0; |
| 2257 this.placeStars(); |
| 2258 } |
| 2259 this.drawStars = true; |
| 2260 }, |
| 2261 |
| 2262 updateXPos: function(currentPos, speed) { |
| 2263 if (currentPos < -NightMode.config.WIDTH) { |
| 2264 currentPos = this.containerWidth; |
| 2265 } else { |
| 2266 currentPos -= speed; |
| 2267 } |
| 2268 return currentPos; |
| 2269 }, |
| 2270 |
| 2271 draw: function() { |
| 2272 var moonSourceWidth = this.currentPhase == 3 ? NightMode.config.WIDTH * 2 : |
| 2273 NightMode.config.WIDTH; |
| 2274 var moonSourceHeight = NightMode.config.HEIGHT; |
| 2275 var moonSourceX = this.spritePos.x + NightMode.phases[this.currentPhase]; |
| 2276 var moonOutputWidth = moonSourceWidth; |
| 2277 var starSize = NightMode.config.STAR_SIZE; |
| 2278 var starSourceX = Runner.spriteDefinition.LDPI.STAR.x; |
| 2279 |
| 2280 if (IS_HIDPI) { |
| 2281 moonSourceWidth *= 2; |
| 2282 moonSourceHeight *= 2; |
| 2283 moonSourceX = this.spritePos.x + |
| 2284 (NightMode.phases[this.currentPhase] * 2); |
| 2285 starSize *= 2; |
| 2286 starSourceX = Runner.spriteDefinition.HDPI.STAR.x; |
| 2287 } |
| 2288 |
| 2289 this.canvasCtx.save(); |
| 2290 this.canvasCtx.globalAlpha = this.opacity; |
| 2291 |
| 2292 // Stars. |
| 2293 if (this.drawStars) { |
| 2294 for (var i = 0; i < NightMode.config.NUM_STARS; i++) { |
| 2295 this.canvasCtx.drawImage(Runner.imageSprite, |
| 2296 starSourceX, this.stars[i].sourceY, starSize, starSize, |
| 2297 Math.round(this.stars[i].x), this.stars[i].y, |
| 2298 NightMode.config.STAR_SIZE, NightMode.config.STAR_SIZE); |
| 2299 } |
| 2300 } |
| 2301 |
| 2302 // Moon. |
| 2303 this.canvasCtx.drawImage(Runner.imageSprite, moonSourceX, |
| 2304 this.spritePos.y, moonSourceWidth, moonSourceHeight, |
| 2305 Math.round(this.xPos), this.yPos, |
| 2306 moonOutputWidth, NightMode.config.HEIGHT); |
| 2307 |
| 2308 this.canvasCtx.globalAlpha = 1; |
| 2309 this.canvasCtx.restore(); |
| 2310 }, |
| 2311 |
| 2312 // Do star placement. |
| 2313 placeStars: function() { |
| 2314 var segmentSize = Math.round(this.containerWidth / |
| 2315 NightMode.config.NUM_STARS); |
| 2316 |
| 2317 for (var i = 0; i < NightMode.config.NUM_STARS; i++) { |
| 2318 this.stars[i] = {}; |
| 2319 this.stars[i].x = getRandomNum(segmentSize * i, segmentSize * (i + 1)); |
| 2320 this.stars[i].y = getRandomNum(0, NightMode.config.STAR_MAX_Y); |
| 2321 |
| 2322 if (IS_HIDPI) { |
| 2323 this.stars[i].sourceY = Runner.spriteDefinition.HDPI.STAR.y + |
| 2324 NightMode.config.STAR_SIZE * 2 * i; |
| 2325 } else { |
| 2326 this.stars[i].sourceY = Runner.spriteDefinition.LDPI.STAR.y + |
| 2327 NightMode.config.STAR_SIZE * i; |
| 2328 } |
| 2329 } |
| 2330 }, |
| 2331 |
| 2332 reset: function() { |
| 2333 this.currentPhase = 0; |
| 2334 this.opacity = 0; |
| 2335 this.update(false); |
| 2336 } |
| 2337 |
| 2338 }; |
| 2339 |
| 2340 |
| 2341 //****************************************************************************** |
| 2342 |
| 2343 /** |
2143 * Horizon Line. | 2344 * Horizon Line. |
2144 * Consists of two connecting lines. Randomly assigns a flat / bumpy horizon. | 2345 * Consists of two connecting lines. Randomly assigns a flat / bumpy horizon. |
2145 * @param {HTMLCanvasElement} canvas | 2346 * @param {HTMLCanvasElement} canvas |
2146 * @param {Object} spritePos Horizon position in sprite. | 2347 * @param {Object} spritePos Horizon position in sprite. |
2147 * @constructor | 2348 * @constructor |
2148 */ | 2349 */ |
2149 function HorizonLine(canvas, spritePos) { | 2350 function HorizonLine(canvas, spritePos) { |
2150 this.spritePos = spritePos; | 2351 this.spritePos = spritePos; |
2151 this.canvas = canvas; | 2352 this.canvas = canvas; |
2152 this.canvasCtx = canvas.getContext('2d'); | 2353 this.canvasCtx = canvas.getContext('2d'); |
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2280 this.canvas = canvas; | 2481 this.canvas = canvas; |
2281 this.canvasCtx = this.canvas.getContext('2d'); | 2482 this.canvasCtx = this.canvas.getContext('2d'); |
2282 this.config = Horizon.config; | 2483 this.config = Horizon.config; |
2283 this.dimensions = dimensions; | 2484 this.dimensions = dimensions; |
2284 this.gapCoefficient = gapCoefficient; | 2485 this.gapCoefficient = gapCoefficient; |
2285 this.obstacles = []; | 2486 this.obstacles = []; |
2286 this.obstacleHistory = []; | 2487 this.obstacleHistory = []; |
2287 this.horizonOffsets = [0, 0]; | 2488 this.horizonOffsets = [0, 0]; |
2288 this.cloudFrequency = this.config.CLOUD_FREQUENCY; | 2489 this.cloudFrequency = this.config.CLOUD_FREQUENCY; |
2289 this.spritePos = spritePos; | 2490 this.spritePos = spritePos; |
| 2491 this.nightMode = null; |
2290 | 2492 |
2291 // Cloud | 2493 // Cloud |
2292 this.clouds = []; | 2494 this.clouds = []; |
2293 this.cloudSpeed = this.config.BG_CLOUD_SPEED; | 2495 this.cloudSpeed = this.config.BG_CLOUD_SPEED; |
2294 | 2496 |
2295 // Horizon | 2497 // Horizon |
2296 this.horizonLine = null; | 2498 this.horizonLine = null; |
2297 | |
2298 this.init(); | 2499 this.init(); |
2299 }; | 2500 }; |
2300 | 2501 |
2301 | 2502 |
2302 /** | 2503 /** |
2303 * Horizon config. | 2504 * Horizon config. |
2304 * @enum {number} | 2505 * @enum {number} |
2305 */ | 2506 */ |
2306 Horizon.config = { | 2507 Horizon.config = { |
2307 BG_CLOUD_SPEED: 0.2, | 2508 BG_CLOUD_SPEED: 0.2, |
2308 BUMPY_THRESHOLD: .3, | 2509 BUMPY_THRESHOLD: .3, |
2309 CLOUD_FREQUENCY: .5, | 2510 CLOUD_FREQUENCY: .5, |
2310 HORIZON_HEIGHT: 16, | 2511 HORIZON_HEIGHT: 16, |
2311 MAX_CLOUDS: 6 | 2512 MAX_CLOUDS: 6 |
2312 }; | 2513 }; |
2313 | 2514 |
2314 | 2515 |
2315 Horizon.prototype = { | 2516 Horizon.prototype = { |
2316 /** | 2517 /** |
2317 * Initialise the horizon. Just add the line and a cloud. No obstacles. | 2518 * Initialise the horizon. Just add the line and a cloud. No obstacles. |
2318 */ | 2519 */ |
2319 init: function() { | 2520 init: function() { |
2320 this.addCloud(); | 2521 this.addCloud(); |
2321 this.horizonLine = new HorizonLine(this.canvas, this.spritePos.HORIZON); | 2522 this.horizonLine = new HorizonLine(this.canvas, this.spritePos.HORIZON); |
| 2523 this.nightMode = new NightMode(this.canvas, this.spritePos.MOON, |
| 2524 this.dimensions.WIDTH); |
2322 }, | 2525 }, |
2323 | 2526 |
2324 /** | 2527 /** |
2325 * @param {number} deltaTime | 2528 * @param {number} deltaTime |
2326 * @param {number} currentSpeed | 2529 * @param {number} currentSpeed |
2327 * @param {boolean} updateObstacles Used as an override to prevent | 2530 * @param {boolean} updateObstacles Used as an override to prevent |
2328 * the obstacles from being updated / added. This happens in the | 2531 * the obstacles from being updated / added. This happens in the |
2329 * ease in section. | 2532 * ease in section. |
| 2533 * @param {boolean} showNightMode Night mode activated. |
2330 */ | 2534 */ |
2331 update: function(deltaTime, currentSpeed, updateObstacles) { | 2535 update: function(deltaTime, currentSpeed, updateObstacles, showNightMode) { |
2332 this.runningTime += deltaTime; | 2536 this.runningTime += deltaTime; |
2333 this.horizonLine.update(deltaTime, currentSpeed); | 2537 this.horizonLine.update(deltaTime, currentSpeed); |
| 2538 this.nightMode.update(showNightMode); |
2334 this.updateClouds(deltaTime, currentSpeed); | 2539 this.updateClouds(deltaTime, currentSpeed); |
2335 | 2540 |
2336 if (updateObstacles) { | 2541 if (updateObstacles) { |
2337 this.updateObstacles(deltaTime, currentSpeed); | 2542 this.updateObstacles(deltaTime, currentSpeed); |
2338 } | 2543 } |
2339 }, | 2544 }, |
2340 | 2545 |
2341 /** | 2546 /** |
2342 * Update the cloud positions. | 2547 * Update the cloud positions. |
2343 * @param {number} deltaTime | 2548 * @param {number} deltaTime |
(...skipping 14 matching lines...) Expand all Loading... |
2358 if (numClouds < this.config.MAX_CLOUDS && | 2563 if (numClouds < this.config.MAX_CLOUDS && |
2359 (this.dimensions.WIDTH - lastCloud.xPos) > lastCloud.cloudGap && | 2564 (this.dimensions.WIDTH - lastCloud.xPos) > lastCloud.cloudGap && |
2360 this.cloudFrequency > Math.random()) { | 2565 this.cloudFrequency > Math.random()) { |
2361 this.addCloud(); | 2566 this.addCloud(); |
2362 } | 2567 } |
2363 | 2568 |
2364 // Remove expired clouds. | 2569 // Remove expired clouds. |
2365 this.clouds = this.clouds.filter(function(obj) { | 2570 this.clouds = this.clouds.filter(function(obj) { |
2366 return !obj.remove; | 2571 return !obj.remove; |
2367 }); | 2572 }); |
| 2573 } else { |
| 2574 this.addCloud(); |
2368 } | 2575 } |
2369 }, | 2576 }, |
2370 | 2577 |
2371 /** | 2578 /** |
2372 * Update the obstacle positions. | 2579 * Update the obstacle positions. |
2373 * @param {number} deltaTime | 2580 * @param {number} deltaTime |
2374 * @param {number} currentSpeed | 2581 * @param {number} currentSpeed |
2375 */ | 2582 */ |
2376 updateObstacles: function(deltaTime, currentSpeed) { | 2583 updateObstacles: function(deltaTime, currentSpeed) { |
2377 // Obstacles, move to Horizon layer. | 2584 // Obstacles, move to Horizon layer. |
(...skipping 19 matching lines...) Expand all Loading... |
2397 this.dimensions.WIDTH) { | 2604 this.dimensions.WIDTH) { |
2398 this.addNewObstacle(currentSpeed); | 2605 this.addNewObstacle(currentSpeed); |
2399 lastObstacle.followingObstacleCreated = true; | 2606 lastObstacle.followingObstacleCreated = true; |
2400 } | 2607 } |
2401 } else { | 2608 } else { |
2402 // Create new obstacles. | 2609 // Create new obstacles. |
2403 this.addNewObstacle(currentSpeed); | 2610 this.addNewObstacle(currentSpeed); |
2404 } | 2611 } |
2405 }, | 2612 }, |
2406 | 2613 |
| 2614 removeFirstObstacle: function() { |
| 2615 this.obstacles.shift(); |
| 2616 }, |
| 2617 |
2407 /** | 2618 /** |
2408 * Add a new obstacle. | 2619 * Add a new obstacle. |
2409 * @param {number} currentSpeed | 2620 * @param {number} currentSpeed |
2410 */ | 2621 */ |
2411 addNewObstacle: function(currentSpeed) { | 2622 addNewObstacle: function(currentSpeed) { |
2412 var obstacleTypeIndex = getRandomNum(0, Obstacle.types.length - 1); | 2623 var obstacleTypeIndex = getRandomNum(0, Obstacle.types.length - 1); |
2413 var obstacleType = Obstacle.types[obstacleTypeIndex]; | 2624 var obstacleType = Obstacle.types[obstacleTypeIndex]; |
2414 | 2625 |
2415 // Check for multiples of the same type of obstacle. | 2626 // Check for multiples of the same type of obstacle. |
2416 // Also check obstacle is available at current speed. | 2627 // Also check obstacle is available at current speed. |
2417 if (this.duplicateObstacleCheck(obstacleType.type) || | 2628 if (this.duplicateObstacleCheck(obstacleType.type) || |
2418 currentSpeed < obstacleType.minSpeed) { | 2629 currentSpeed < obstacleType.minSpeed) { |
2419 this.addNewObstacle(currentSpeed); | 2630 this.addNewObstacle(currentSpeed); |
2420 } else { | 2631 } else { |
2421 var obstacleSpritePos = this.spritePos[obstacleType.type]; | 2632 var obstacleSpritePos = this.spritePos[obstacleType.type]; |
2422 | 2633 |
2423 this.obstacles.push(new Obstacle(this.canvasCtx, obstacleType, | 2634 this.obstacles.push(new Obstacle(this.canvasCtx, obstacleType, |
2424 obstacleSpritePos, this.dimensions, | 2635 obstacleSpritePos, this.dimensions, |
2425 this.gapCoefficient, currentSpeed)); | 2636 this.gapCoefficient, currentSpeed, obstacleType.width)); |
2426 | 2637 |
2427 this.obstacleHistory.unshift(obstacleType.type); | 2638 this.obstacleHistory.unshift(obstacleType.type); |
2428 | 2639 |
2429 if (this.obstacleHistory.length > 1) { | 2640 if (this.obstacleHistory.length > 1) { |
2430 this.obstacleHistory.splice(Runner.config.MAX_OBSTACLE_DUPLICATION); | 2641 this.obstacleHistory.splice(Runner.config.MAX_OBSTACLE_DUPLICATION); |
2431 } | 2642 } |
2432 } | 2643 } |
2433 }, | 2644 }, |
2434 | 2645 |
2435 /** | 2646 /** |
(...skipping 11 matching lines...) Expand all Loading... |
2447 return duplicateCount >= Runner.config.MAX_OBSTACLE_DUPLICATION; | 2658 return duplicateCount >= Runner.config.MAX_OBSTACLE_DUPLICATION; |
2448 }, | 2659 }, |
2449 | 2660 |
2450 /** | 2661 /** |
2451 * Reset the horizon layer. | 2662 * Reset the horizon layer. |
2452 * Remove existing obstacles and reposition the horizon line. | 2663 * Remove existing obstacles and reposition the horizon line. |
2453 */ | 2664 */ |
2454 reset: function() { | 2665 reset: function() { |
2455 this.obstacles = []; | 2666 this.obstacles = []; |
2456 this.horizonLine.reset(); | 2667 this.horizonLine.reset(); |
| 2668 this.nightMode.reset(); |
2457 }, | 2669 }, |
2458 | 2670 |
2459 /** | 2671 /** |
2460 * Update the canvas width and scaling. | 2672 * Update the canvas width and scaling. |
2461 * @param {number} width Canvas width. | 2673 * @param {number} width Canvas width. |
2462 * @param {number} height Canvas height. | 2674 * @param {number} height Canvas height. |
2463 */ | 2675 */ |
2464 resize: function(width, height) { | 2676 resize: function(width, height) { |
2465 this.canvas.width = width; | 2677 this.canvas.width = width; |
2466 this.canvas.height = height; | 2678 this.canvas.height = height; |
2467 }, | 2679 }, |
2468 | 2680 |
2469 /** | 2681 /** |
2470 * Add a new cloud to the horizon. | 2682 * Add a new cloud to the horizon. |
2471 */ | 2683 */ |
2472 addCloud: function() { | 2684 addCloud: function() { |
2473 this.clouds.push(new Cloud(this.canvas, this.spritePos.CLOUD, | 2685 this.clouds.push(new Cloud(this.canvas, this.spritePos.CLOUD, |
2474 this.dimensions.WIDTH)); | 2686 this.dimensions.WIDTH)); |
2475 } | 2687 } |
2476 }; | 2688 }; |
2477 })(); | 2689 })(); |
OLD | NEW |