├── README.md ├── animate.js ├── enemy.js ├── fonts └── Ubuntu-R.ttf ├── images ├── asteroid1.png ├── drone.png ├── health-hud.png ├── health-upgrade.png ├── heavy-drone.png ├── intro.png ├── missile1_1.png ├── missile1_2.png ├── nebula1.jpg ├── porcupine.png ├── powerups.svg ├── scout.png ├── ship_1.png ├── ship_2.png ├── speed-upgrade.png └── weapon-upgrade.png ├── index-live.html ├── input.js ├── main.js ├── player.js ├── screen.js ├── stars.js └── weapons.js /README.md: -------------------------------------------------------------------------------- 1 | HTML5 Space Fighter 2 | =================== 3 | 4 | An HTML5 game written using GameJS. 5 | 6 | Live Version 7 | ------------ 8 | 9 | [Play the game online](http://programmer-art.org/dropbox/fighter-static/index.html "Click here to play!") 10 | 11 | Development 12 | ----------- 13 | 14 | In order to develop please download [GameJS](http://gamejs.org/) first, then 15 | clone this repository into e.g. `gamejs/examples/html5-space-fighter`. When 16 | running the GameJS development server you will now see the game listed and 17 | playable. For example: 18 | 19 | cd gamejs/examples 20 | git clone git://github.com/danielgtaylor/html5-space-fighter.git 21 | cd .. 22 | ./gjs-server.sh 23 | 24 | Then go to [localhost:8080](http://localhost:8080/) 25 | 26 | Deployment 27 | ---------- 28 | 29 | You can deploy the game as follows, assuming an installation like the steps 30 | given above: 31 | 32 | cd gamejs 33 | ./gjs-statify.sh examples/html5-space-fighter ~/Desktop/fighter-static 34 | 35 | Then you can upload the `fighter-static` folder on your desktop to a web server 36 | to make it publicly accessible. 37 | 38 | License 39 | ------- 40 | Copyright (c) 2011 Daniel G. Taylor 41 | 42 | Permission is hereby granted, free of charge, to any person obtaining a copy 43 | of this software and associated documentation files (the "Software"), to deal 44 | in the Software without restriction, including without limitation the rights 45 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 46 | copies of the Software, and to permit persons to whom the Software is 47 | furnished to do so, subject to the following conditions: 48 | 49 | The above copyright notice and this permission notice shall be included in 50 | all copies or substantial portions of the Software. 51 | 52 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 53 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 54 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 55 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 56 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 57 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 58 | THE SOFTWARE. 59 | 60 | -------------------------------------------------------------------------------- /animate.js: -------------------------------------------------------------------------------- 1 | var gamejs = require('gamejs'); 2 | 3 | var AnimatedSprite = function () { 4 | AnimatedSprite.superConstructor.apply(this, arguments); 5 | 6 | this.rect = null; 7 | this.frames = []; 8 | this.currentFrame = 0; 9 | this.animationSpeed = 250; 10 | this.frameTimer = 0; 11 | 12 | this.updateAnimation = function (msDuration) { 13 | this.frameTimer -= msDuration; 14 | 15 | while (this.frameTimer <= 0) { 16 | this.currentFrame += 1; 17 | 18 | if (this.currentFrame >= this.frames.length) { 19 | this.currentFrame = 0; 20 | } 21 | 22 | this.frameTimer += this.animationSpeed; 23 | } 24 | }; 25 | 26 | this.update = function (msDuration) { 27 | this.updateAnimation(msDuration); 28 | }; 29 | 30 | this.draw = function (surface) { 31 | surface.blit(this.frames[this.currentFrame], this.rect); 32 | }; 33 | }; 34 | gamejs.utils.objects.extend(AnimatedSprite, gamejs.sprite.Sprite); 35 | 36 | exports.AnimatedSprite = AnimatedSprite; 37 | 38 | -------------------------------------------------------------------------------- /enemy.js: -------------------------------------------------------------------------------- 1 | var gamejs = require('gamejs'); 2 | 3 | var Pulse = function (pos, speed, angle, player, size, strength) { 4 | Pulse.superConstructor.apply(this, arguments); 5 | this.player = player; 6 | this.rect = new gamejs.Rect([pos[0] - 4, pos[1] - 4], [8, 8]); 7 | this.speed = speed; 8 | this.angle = angle; 9 | this.strength = strength; 10 | this.size = size; 11 | 12 | this.update = function (msDuration) { 13 | this.rect.left += Math.cos(this.angle) * speed * (msDuration / 1000); 14 | this.rect.top += Math.sin(this.angle) * speed * (msDuration / 1000); 15 | 16 | if ((this.rect.left <= -this.rect.width) || (this.rect.left >= this.size[0]) || (this.rect.top <= -this.rect.height) || (this.rect.top >= this.size[1])) { 17 | this.kill(); 18 | } 19 | }; 20 | 21 | this.draw = function (surface) { 22 | gamejs.draw.circle(surface, "#f00", [this.rect.left + 4, this.rect.top + 4], 3, 2); 23 | }; 24 | 25 | return this; 26 | }; 27 | gamejs.utils.objects.extend(Pulse, gamejs.sprite.Sprite); 28 | 29 | /* 30 | A base class for enemies in the game. 31 | */ 32 | var Enemy = function () { 33 | Enemy.superConstructor.apply(this, arguments); 34 | 35 | this.health = 1; 36 | 37 | this.damage = function (amount) { 38 | this.health -= amount; 39 | 40 | if (this.health <= 0) { 41 | this.kill(); 42 | } 43 | }; 44 | }; 45 | gamejs.utils.objects.extend(Enemy, gamejs.sprite.Sprite); 46 | 47 | /* 48 | A simple dumb floating asteroid :-) 49 | */ 50 | var Asteroid = function (images, size, player, weapons) { 51 | Asteroid.superConstructor.apply(this, arguments); 52 | 53 | this.size = size 54 | this.origImage = images.asteroid; 55 | this.image = this.origImage 56 | this.angle = Math.random() * Math.PI * 2; 57 | this.rotationSpeed = -75 + Math.random() * 150; 58 | this.speed = 40 + Math.random() * 40; 59 | this.rect = new gamejs.Rect([50 + (Math.random() * (size[0] - 100)), -50], this.image.getSize()); 60 | this.health = 50; 61 | this.strength = 25; 62 | 63 | /* 64 | Move and rotate the asteroid. 65 | */ 66 | this.update = function(msDuration) { 67 | this.angle += this.rotationSpeed * (msDuration / 1000); 68 | this.image = gamejs.transform.rotate(this.origImage, this.angle); 69 | 70 | this.rect.top += this.speed * (msDuration / 1000); 71 | 72 | if (this.rect.top >= this.size[1]) { 73 | this.kill(); 74 | } 75 | }; 76 | 77 | return this; 78 | }; 79 | gamejs.utils.objects.extend(Asteroid, Enemy); 80 | 81 | var Scout = function (images, size, player, weapons) { 82 | Scout.superConstructor.apply(this, arguments); 83 | this.player = player; 84 | this.images = images; 85 | this.image = images.scout; 86 | this.rect = new gamejs.Rect([50 + (Math.random() * (size[0] - 100)), -50], this.image.getSize()); 87 | this.size = size; 88 | this.weapons = weapons 89 | this.fireRate = 3500; 90 | this.nextFire = this.fireRate / 2; 91 | this.strength = 25; 92 | this.health = 25; 93 | this.speed = 50; 94 | 95 | this.update = function (msDuration) { 96 | this.nextFire -= msDuration; 97 | while (this.nextFire < 0) { 98 | this.weapons.add(new Pulse([this.rect.left + (this.rect.width / 2), this.rect.top + this.rect.height], 100, Math.PI * 0.5, this.player, this.size, 10)); 99 | this.nextFire += this.fireRate; 100 | } 101 | 102 | this.rect.top += this.speed * (msDuration / 1000); 103 | 104 | if (this.rect.top >= this.size[1]) { 105 | this.kill(); 106 | } 107 | }; 108 | 109 | return this; 110 | }; 111 | gamejs.utils.objects.extend(Scout, Enemy); 112 | 113 | var Drone = function (images, size, player, weapons) { 114 | Drone.superConstructor.apply(this, arguments); 115 | this.player = player; 116 | this.images = images; 117 | this.image = images.drone; 118 | this.rect = new gamejs.Rect([50 + (Math.random() * (size[0] - 100)), -50], this.image.getSize()); 119 | this.size = size; 120 | this.weapons = weapons 121 | this.fireRate = 3000; 122 | this.nextFire = this.fireRate / 2; 123 | this.strength = 30; 124 | this.health = 40; 125 | this.speed = 50; 126 | 127 | this.update = function (msDuration) { 128 | this.nextFire -= msDuration; 129 | while (this.nextFire < 0) { 130 | var angle = Math.PI - Math.atan2((this.player.rect.left + (this.player.rect.width / 2)) - this.rect.left, (this.player.rect.top + (this.player.rect.height / 2)) - this.rect.top) - (Math.PI / 2); 131 | this.weapons.add(new Pulse([this.rect.left + (this.rect.width / 2), this.rect.top + this.rect.height], 100, angle, this.player, this.size, 20)); 132 | this.nextFire += this.fireRate; 133 | } 134 | 135 | this.rect.top += this.speed * (msDuration / 1000); 136 | 137 | if (this.rect.top >= this.size[1]) { 138 | this.kill(); 139 | } 140 | }; 141 | 142 | return this; 143 | }; 144 | gamejs.utils.objects.extend(Drone, Enemy); 145 | 146 | var HeavyDrone = function (images, size, player, weapons) { 147 | HeavyDrone.superConstructor.apply(this, arguments); 148 | this.player = player; 149 | this.images = images; 150 | this.image = images.heavyDrone; 151 | this.rect = new gamejs.Rect([50 + (Math.random() * (size[0] - 100)), -50], this.image.getSize()); 152 | this.size = size; 153 | this.weapons = weapons 154 | this.fireRate = 3000; 155 | this.nextFire = this.fireRate / 2; 156 | this.strength = 30; 157 | this.health = 40; 158 | this.speed = 50; 159 | 160 | this.update = function (msDuration) { 161 | this.nextFire -= msDuration; 162 | while (this.nextFire < 0) { 163 | var angle = Math.PI - Math.atan2((this.player.rect.left + (this.player.rect.width / 2)) - this.rect.left, (this.player.rect.top + (this.player.rect.height / 2)) - this.rect.top) - (Math.PI / 2); 164 | this.weapons.add(new Pulse([this.rect.left + (this.rect.width / 2), this.rect.top + this.rect.height], 300, angle, this.player, this.size, 40)); 165 | this.nextFire += this.fireRate; 166 | } 167 | 168 | this.rect.top += this.speed * (msDuration / 1000); 169 | 170 | if (this.rect.top >= this.size[1]) { 171 | this.kill(); 172 | } 173 | }; 174 | 175 | return this; 176 | }; 177 | gamejs.utils.objects.extend(HeavyDrone, Enemy); 178 | 179 | var Porcupine = function (images, size, player, weapons) { 180 | Porcupine.superConstructor.apply(this, arguments); 181 | this.player = player; 182 | this.images = images; 183 | this.image = images.porcupine; 184 | this.rect = new gamejs.Rect([50 + (Math.random() * (size[0] - 100)), -50], this.image.getSize()); 185 | this.size = size; 186 | this.weapons = weapons 187 | this.fireRate = 5000; 188 | this.nextFire = this.fireRate / 2; 189 | this.strength = 30; 190 | this.health = 25; 191 | this.speed = 50; 192 | this.alternate = Math.random() < 0.5; 193 | 194 | this.update = function (msDuration) { 195 | this.nextFire -= msDuration; 196 | while (this.nextFire < 0) { 197 | var porcupine = this; 198 | 199 | if (this.alternate) { 200 | var angles = [0, Math.PI * 0.5, Math.PI, Math.PI * 1.5]; 201 | } else { 202 | var angles = [Math.PI * 0.25, Math.PI * 0.75, Math.PI * 1.25, Math.PI * 1.75]; 203 | } 204 | 205 | this.alternate = !this.alternate; 206 | 207 | angles.forEach(function (angle) { 208 | porcupine.weapons.add(new Pulse([porcupine.rect.left + (porcupine.rect.width / 2), porcupine.rect.top + porcupine.rect.height], 100, angle, porcupine.player, porcupine.size, 10)); 209 | }); 210 | 211 | this.nextFire += this.fireRate; 212 | } 213 | 214 | this.rect.top += this.speed * (msDuration / 1000); 215 | 216 | if (this.rect.top >= this.size[1]) { 217 | this.kill(); 218 | } 219 | }; 220 | 221 | return this; 222 | }; 223 | gamejs.utils.objects.extend(Porcupine, Enemy); 224 | 225 | var AiManager = function (size, player) { 226 | this.size = size; 227 | this.player = player; 228 | this.ships = new gamejs.sprite.Group(); 229 | this.weapons = new gamejs.sprite.Group(); 230 | this.images = { 231 | 'asteroid': gamejs.image.load('images/asteroid1.png'), 232 | 'scout': gamejs.image.load('images/scout.png'), 233 | 'drone': gamejs.image.load('images/drone.png'), 234 | 'heavyDrone': gamejs.image.load('images/heavy-drone.png'), 235 | 'porcupine': gamejs.image.load('images/porcupine.png'), 236 | }; 237 | this.levels = [ 238 | { 239 | 'duration': 20 * 1000, 240 | 'maxCount': 10, 241 | 'units': [ 242 | { 243 | 'type': Asteroid, 244 | 'frequency': 25 245 | } 246 | ] 247 | }, 248 | { 249 | 'duration': 60 * 1000, 250 | 'maxCount': 15, 251 | 'units': [ 252 | { 253 | 'type': Asteroid, 254 | 'frequency': 20 255 | }, 256 | { 257 | 'type': Scout, 258 | 'frequency': 25 259 | } 260 | ] 261 | }, 262 | { 263 | 'duration': 30 * 1000, 264 | 'maxCount': 40, 265 | 'units': [ 266 | { 267 | 'type': Asteroid, 268 | 'frequency': 150 269 | } 270 | ] 271 | }, 272 | { 273 | 'duration': 60 * 1000, 274 | 'maxCount': 20, 275 | 'units': [ 276 | { 277 | 'type': Asteroid, 278 | 'frequency': 20 279 | }, 280 | { 281 | 'type': Scout, 282 | 'frequency': 15 283 | }, 284 | { 285 | 'type': Drone, 286 | 'frequency': 30 287 | } 288 | ] 289 | }, 290 | { 291 | 'duration': 45 * 1000, 292 | 'maxCount': 10, 293 | 'units': [ 294 | { 295 | 'type': Porcupine, 296 | 'frequency': 100 297 | }, 298 | ] 299 | }, 300 | { 301 | 'duration': 120 * 1000, 302 | 'maxCount': 30, 303 | 'units': [ 304 | { 305 | 'type': Asteroid, 306 | 'frequency': 10 307 | }, 308 | { 309 | 'type': Scout, 310 | 'frequency': 15 311 | }, 312 | { 313 | 'type': Drone, 314 | 'frequency': 40 315 | }, 316 | { 317 | 'type': Porcupine, 318 | 'frequency': 25 319 | } 320 | ] 321 | }, 322 | { 323 | 'duration': 45 * 1000, 324 | 'maxCount': 5, 325 | 'units': [ 326 | { 327 | 'type': HeavyDrone, 328 | 'frequency': 100 329 | }, 330 | ] 331 | }, 332 | { 333 | 'duration': 120 * 1000, 334 | 'maxCount': 30, 335 | 'units': [ 336 | { 337 | 'type': Asteroid, 338 | 'frequency': 10 339 | }, 340 | { 341 | 'type': Scout, 342 | 'frequency': 15 343 | }, 344 | { 345 | 'type': Drone, 346 | 'frequency': 25 347 | }, 348 | { 349 | 'type': HeavyDrone, 350 | 'frequency': 40 351 | }, 352 | { 353 | 'type': Porcupine, 354 | 'frequency': 40 355 | } 356 | ] 357 | }, 358 | { 359 | 'duration': 45 * 1000, 360 | 'maxCount': 25, 361 | 'units': [ 362 | { 363 | 'type': HeavyDrone, 364 | 'frequency': 100 365 | }, 366 | ] 367 | }, 368 | { 369 | 'duration': 60 * 1000, 370 | 'maxCount': 30, 371 | 'units': [ 372 | { 373 | 'type': HeavyDrone, 374 | 'frequency': 60 375 | }, 376 | { 377 | 'type': Porcupine, 378 | 'frequency': 60 379 | } 380 | ] 381 | }, 382 | ]; 383 | this.level = 0; 384 | this.levelDuration = this.levels[this.level].duration; 385 | 386 | this.update = function (msDuration) { 387 | this.levelDuration -= msDuration; 388 | if (this.levelDuration <= 0 && this.level < this.levels.length - 1) { 389 | this.level += 1; 390 | this.levelDuration = this.levels[this.level].duration; 391 | } 392 | 393 | var manager = this; 394 | var level = this.levels[this.level]; 395 | level.units.forEach(function (unit) { 396 | if (manager.ships.sprites().length <= level.maxCount && Math.random() < (unit.frequency / 100 * (msDuration / 1000))) { 397 | manager.ships.add(new unit.type(manager.images, manager.size, manager.player, manager.weapons)); 398 | } 399 | }); 400 | 401 | this.ships.update(msDuration); 402 | this.weapons.update(msDuration); 403 | }; 404 | 405 | this.collide = function () { 406 | var player = this.player; 407 | 408 | gamejs.sprite.spriteCollide(player, this.weapons).forEach(function (weapon) { 409 | player.damage(weapon.strength); 410 | weapon.kill(); 411 | }); 412 | 413 | gamejs.sprite.spriteCollide(player, this.ships).forEach(function (ship) { 414 | player.damage(ship.strength); 415 | ship.kill(); 416 | }); 417 | }; 418 | 419 | this.draw = function (surface) { 420 | this.ships.draw(surface); 421 | this.weapons.draw(surface); 422 | }; 423 | 424 | return this; 425 | }; 426 | 427 | exports.AiManager = AiManager; 428 | 429 | -------------------------------------------------------------------------------- /fonts/Ubuntu-R.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/fonts/Ubuntu-R.ttf -------------------------------------------------------------------------------- /images/asteroid1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/images/asteroid1.png -------------------------------------------------------------------------------- /images/drone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/images/drone.png -------------------------------------------------------------------------------- /images/health-hud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/images/health-hud.png -------------------------------------------------------------------------------- /images/health-upgrade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/images/health-upgrade.png -------------------------------------------------------------------------------- /images/heavy-drone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/images/heavy-drone.png -------------------------------------------------------------------------------- /images/intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/images/intro.png -------------------------------------------------------------------------------- /images/missile1_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/images/missile1_1.png -------------------------------------------------------------------------------- /images/missile1_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/images/missile1_2.png -------------------------------------------------------------------------------- /images/nebula1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/images/nebula1.jpg -------------------------------------------------------------------------------- /images/porcupine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/images/porcupine.png -------------------------------------------------------------------------------- /images/powerups.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 23 | 27 | 31 | 32 | 41 | 49 | 53 | 54 | 63 | 65 | 69 | 73 | 74 | 82 | 86 | 87 | 96 | 98 | 102 | 106 | 107 | 115 | 119 | 120 | 130 | 137 | 141 | 142 | 143 | 163 | 165 | 166 | 168 | image/svg+xml 169 | 171 | 172 | 173 | 174 | 175 | 180 | 190 | 203 | 216 | 229 | 242 | 255 | 268 | 277 | 290 | 303 | 316 | + 330 | 340 | 350 | 360 | 370 | 380 | + 394 | 395 | 396 | -------------------------------------------------------------------------------- /images/scout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/images/scout.png -------------------------------------------------------------------------------- /images/ship_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/images/ship_1.png -------------------------------------------------------------------------------- /images/ship_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/images/ship_2.png -------------------------------------------------------------------------------- /images/speed-upgrade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/images/speed-upgrade.png -------------------------------------------------------------------------------- /images/weapon-upgrade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgtaylor/html5-space-fighter/c14097b24aec182d5f8fb4074926b391a73d9b0e/images/weapon-upgrade.png -------------------------------------------------------------------------------- /index-live.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HTML5 Space Fighter 6 | 32 | 33 | 43 | 44 | 52 | 53 | 54 | 65 | 66 | 67 | 68 |
69 |

HTML5 Space Fighter

70 |
71 |
72 | 73 | 74 | 75 | 77 |

78 | Restart  |  Homepage  |  Source Code 79 |

80 |

81 | Copyright © 2011 Daniel G. Taylor 82 |

83 | 84 |
85 | 100 | 101 | blog comments powered by Disqus 102 | 103 |
104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /input.js: -------------------------------------------------------------------------------- 1 | var gamejs = require('gamejs'); 2 | 3 | /* 4 | A class to allow the player to control her ship. 5 | */ 6 | exports.UserControls = function(size, ship) { 7 | this.size = size; 8 | this.ship = ship; 9 | this.up = false; 10 | this.down = false; 11 | this.left = false; 12 | this.right = false; 13 | this.fire = false; 14 | this.paused = false; 15 | this.initialClick = false; 16 | 17 | /* 18 | Handle events from the main loop to e.g. store which keys are currently 19 | being pressed by the player. 20 | */ 21 | this.handle = function(event) { 22 | if (event.type === gamejs.event.KEY_DOWN) { 23 | if (event.key === gamejs.event.K_UP) { 24 | this.up = true; 25 | } else if (event.key === gamejs.event.K_DOWN) { 26 | this.down = true; 27 | } else if (event.key === gamejs.event.K_LEFT) { 28 | this.left = true; 29 | } else if (event.key === gamejs.event.K_RIGHT) { 30 | this.right = true; 31 | } else if (event.key === gamejs.event.K_SPACE) { 32 | this.fire = true; 33 | } else if (event.key === gamejs.event.K_ESC) { 34 | this.paused = !this.paused; 35 | } else if (event.key === gamejs.event.K_u) { 36 | this.ship.upgradeWeapon(); 37 | } else if (event.key === gamejs.event.K_k) { 38 | this.ship.kill(); 39 | } else { 40 | console.debug(event.key); 41 | } 42 | } else if (event.type === gamejs.event.KEY_UP) { 43 | if (event.key === gamejs.event.K_UP) { 44 | this.up = false; 45 | } else if (event.key === gamejs.event.K_DOWN) { 46 | this.down = false; 47 | } else if (event.key === gamejs.event.K_LEFT) { 48 | this.left = false; 49 | } else if (event.key === gamejs.event.K_RIGHT) { 50 | this.right = false; 51 | } else if (event.key === gamejs.event.K_SPACE) { 52 | this.fire = false; 53 | } 54 | } else if (event.type === gamejs.event.MOUSE_DOWN) { 55 | if ((event.pos[0] > 0 && event.pos[0] < this.size[0]) && (event.pos[1] > 0 && event.pos[1] < this.size[1])) { 56 | this.initialClick = true; 57 | } 58 | } 59 | } 60 | 61 | /* 62 | Get the angle depending on the keys that are currently pressed. 63 | */ 64 | this.angle = function () { 65 | if (this.up && this.left) { 66 | return Math.PI + (Math.PI * 0.25); 67 | } else if (this.up && this.right) { 68 | return Math.PI * -0.25; 69 | } else if (this.down && this.left) { 70 | return Math.PI - (Math.PI * 0.25); 71 | } else if (this.down && this.right) { 72 | return Math.PI * 0.25; 73 | } else if (this.up) { 74 | return Math.PI * 1.5; 75 | } else if (this.down) { 76 | return Math.PI * 0.5; 77 | } else if (this.left) { 78 | return Math.PI; 79 | } else if (this.right) { 80 | return 0; 81 | } 82 | 83 | return null; 84 | } 85 | 86 | return this; 87 | } 88 | 89 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | /* 2 | Space Fighter Game 3 | */ 4 | 5 | var gamejs = require('gamejs'); 6 | var draw = gamejs.draw; 7 | 8 | var screen = require('./screen'); 9 | var input = require('./input'); 10 | var animate = require('./animate'); 11 | var stars = require('./stars'); 12 | var player = require('./player'); 13 | var enemy = require('./enemy'); 14 | 15 | var SIZE = [800, 600]; 16 | 17 | function main() { 18 | // screen setup 19 | gamejs.display.setMode(SIZE); 20 | gamejs.display.setCaption("Fighter"); 21 | 22 | var intro = gamejs.image.load('images/intro.png'); 23 | 24 | var font = new gamejs.font.Font('48px ubuntu, sans-serif'); 25 | var paused = font.render("Paused", "#08c"); 26 | var gameOver = font.render("Game Over", "#000"); 27 | 28 | var background = gamejs.image.load("images/nebula1.jpg"); 29 | var backgroundPos = SIZE[1] - background.getSize()[1]; 30 | 31 | var starMap = new stars.StarMap(SIZE); 32 | starMap.seed(); 33 | 34 | var powerups = new gamejs.sprite.Group(); 35 | 36 | var enemies = []; 37 | 38 | // create some ship sprites and put them in a group 39 | var ship = new player.Ship([SIZE[0] / 2 - 28, SIZE[1] - 100, 57, 26], SIZE, enemies); 40 | var controls = new input.UserControls(SIZE, ship); 41 | 42 | var ai = new enemy.AiManager(SIZE, ship); 43 | enemies.push(ai.ships); 44 | 45 | var hud = screen.Hud(SIZE); 46 | 47 | var pausedDrawn = false; 48 | 49 | // game loop 50 | var mainSurface = gamejs.display.getSurface(); 51 | // msDuration = time since last tick() call 52 | var tick = function(msDuration) { 53 | gamejs.event.get().forEach(function(event) { 54 | controls.handle(event); 55 | }); 56 | 57 | if (controls.paused) { 58 | if (!pausedDrawn) { 59 | pausedDrawn = true; 60 | // Draw paused overlay 61 | gamejs.draw.rect(mainSurface, "rgba(0, 0, 0, 0.5)", new gamejs.Rect([0, 0], SIZE), 0) 62 | mainSurface.blit(paused, [SIZE[0] / 2 - paused.getSize()[0] / 2, SIZE[1] / 2 - paused.getSize()[1] / 2]); 63 | } 64 | 65 | return; 66 | } 67 | 68 | pausedDrawn = false; 69 | 70 | if (!controls.paused) { 71 | backgroundPos += 10 * (msDuration / 1000); 72 | if (backgroundPos >= SIZE[1] - (background.getSize()[1] / 2)) { 73 | backgroundPos = SIZE[1] - background.getSize()[1]; 74 | } 75 | 76 | starMap.update(msDuration); 77 | 78 | if (controls.initialClick && !ship.isDead()) { 79 | powerups.update(msDuration); 80 | ship.update(msDuration); 81 | ship.clipMotion(); 82 | 83 | ai.update(msDuration); 84 | } 85 | } 86 | 87 | mainSurface.blit(background, [(SIZE[0] / 2) - (background.getSize()[0] / 2), backgroundPos]); 88 | 89 | starMap.draw(mainSurface); 90 | 91 | if (controls.initialClick) { 92 | if (!ship.isDead()) { 93 | powerups.draw(mainSurface); 94 | ship.angle = controls.angle(); 95 | ship.firing = controls.fire; 96 | 97 | ship.draw(mainSurface); 98 | ship.weapons.forEach(function (weapon) { 99 | weapon.draw(mainSurface) 100 | }); 101 | 102 | if (ship.damaged) { 103 | gamejs.draw.rect(mainSurface, "rgba(255, 0, 0, " + (ship.damaged / 150) + ")", new gamejs.Rect([0, 0], SIZE), 0) 104 | ship.damaged = Math.max(0, ship.damaged - msDuration); 105 | } 106 | 107 | if (ship.clearAllEnemies) { 108 | if (ship.clearAllEnemies == 150) { 109 | ai.weapons.empty(); 110 | } 111 | gamejs.draw.rect(mainSurface, "rgba(255, 255, 255, " + (ship.clearAllEnemies / 150) + ")", new gamejs.Rect([0, 0], SIZE), 0) 112 | ship.clearAllEnemies = Math.max(0, ship.clearAllEnemies - msDuration); 113 | } 114 | 115 | ai.draw(mainSurface); 116 | } else { 117 | gamejs.draw.rect(mainSurface, "rgba(255, 0, 0, 0.5)", new gamejs.Rect([0, 0], SIZE), 0) 118 | mainSurface.blit(gameOver, [SIZE[0] / 2 - gameOver.getSize()[0] / 2, SIZE[1] / 2 - gameOver.getSize()[1] / 2]); 119 | } 120 | } else { 121 | mainSurface.blit(intro, [(SIZE[0] / 2) - (intro.getSize()[0] / 2), (SIZE[1] / 2) - (intro.getSize()[1] / 2)]); 122 | } 123 | 124 | hud.health = ship.health; 125 | hud.level = ai.level; 126 | hud.draw(mainSurface); 127 | }; 128 | gamejs.time.fpsCallback(tick, this, 45); 129 | 130 | var collisionTick = function (msDuration) { 131 | var collisions; 132 | 133 | if (controls.paused || ship.isDead()) { 134 | return; 135 | } 136 | 137 | collisions = gamejs.sprite.groupCollide(ship.weapons, ai.ships, true); 138 | collisions.forEach(function (collision) { 139 | var weapon = collision.a; 140 | var enemy = collision.b; 141 | 142 | enemy.damage(weapon.strength); 143 | hud.score += weapon.strength; 144 | 145 | if (enemy.isDead() && Math.random() < 0.1) { 146 | powerups.add(new player.Powerup([enemy.rect.left + (enemy.rect.width / 2) - 12, enemy.rect.top + (enemy.rect.height / 2) - 12], enemy.speed)); 147 | } 148 | }); 149 | 150 | collisions = gamejs.sprite.spriteCollide(ship, powerups, true); 151 | collisions.forEach(function (powerup) { 152 | powerup.engage(ship); 153 | hud.score += 100; 154 | }); 155 | 156 | ai.collide(); 157 | }; 158 | gamejs.time.fpsCallback(collisionTick, this, 45); 159 | } 160 | 161 | /** 162 | * M A I N 163 | */ 164 | gamejs.preload([ 165 | 'images/intro.png', 166 | 'images/nebula1.jpg', 167 | 'images/ship_1.png', 168 | 'images/ship_2.png', 169 | 'images/missile1_1.png', 170 | 'images/missile1_2.png', 171 | 'images/asteroid1.png', 172 | 'images/weapon-upgrade.png', 173 | 'images/speed-upgrade.png', 174 | 'images/health-upgrade.png', 175 | 'images/health-hud.png', 176 | 'images/scout.png', 177 | 'images/drone.png', 178 | 'images/heavy-drone.png', 179 | 'images/porcupine.png', 180 | ]); 181 | gamejs.ready(main); 182 | 183 | -------------------------------------------------------------------------------- /player.js: -------------------------------------------------------------------------------- 1 | var gamejs = require('gamejs'); 2 | var animate = require('./animate'); 3 | var weapons = require('./weapons'); 4 | 5 | /* 6 | Powerups for the player. 7 | */ 8 | var Powerup = function(rect, speed) { 9 | Powerup.superConstructor.apply(this, arguments); 10 | 11 | this.type = [ 12 | 'weapon-upgrade', 13 | 'speed-upgrade', 14 | 'health-upgrade', 15 | ][Math.floor(Math.random() * 3)]; 16 | this.image = gamejs.image.load('images/' + this.type + '.png'); 17 | this.rect = new gamejs.Rect([rect[0], rect[1], this.image.getSize()[0], this.image.getSize()[1]]); 18 | this.speed = speed ? speed : 100; 19 | 20 | this.update = function (msDuration) { 21 | this.rect.top += this.speed * (msDuration / 1000); 22 | }; 23 | 24 | this.engage = function (ship) { 25 | switch (this.type) { 26 | case 'weapon-upgrade': 27 | ship.upgradeWeapon(); 28 | break; 29 | case 'speed-upgrade': 30 | ship.upgradeSpeed(); 31 | break; 32 | case 'health-upgrade': 33 | ship.upgradeHealth(); 34 | break; 35 | } 36 | }; 37 | 38 | return this; 39 | }; 40 | gamejs.utils.objects.extend(Powerup, gamejs.sprite.Sprite); 41 | 42 | /* 43 | The player's ship. 44 | */ 45 | var Ship = function(rect, size, enemies) { 46 | // call superconstructor 47 | Ship.superConstructor.apply(this, arguments); 48 | this.health = 100; 49 | this.speed = 300; 50 | this.angle = null; 51 | this.firing = false; 52 | this.frames = [ 53 | gamejs.image.load('images/ship_1.png'), 54 | gamejs.image.load('images/ship_2.png') 55 | ]; 56 | this.animationSpeed = 100; 57 | this.rect = new gamejs.Rect(rect); 58 | this.size = size; 59 | this.enemies = enemies; 60 | this.weapons = new gamejs.sprite.Group(); 61 | this.weaponClasses = [ 62 | { 63 | 'type': weapons.Laser, 64 | 'fireRate': 250, 65 | 'nextFire': 0 66 | } 67 | ]; 68 | this.weaponStage = 0; 69 | this.clearAllEnemies = 0; 70 | this.damaged = 0; 71 | 72 | this.clipMotion = function () { 73 | if (this.rect.top > this.size[1] - this.rect.height) { 74 | this.rect.top = this.size[1] - this.rect.height; 75 | } else if (this.rect.top < 0) { 76 | this.rect.top = 0; 77 | } 78 | if (this.rect.left < 0) { 79 | this.rect.left = 0; 80 | } else if (this.rect.left > this.size[0] - this.rect.width) { 81 | this.rect.left = this.size[0] - this.rect.width; 82 | } 83 | }; 84 | 85 | this.upgradeWeapon = function () { 86 | this.weaponStage += 1; 87 | if (this.weaponStage == 1) { 88 | this.weaponClasses = [ 89 | { 90 | 'type': weapons.HeavyLaser, 91 | 'fireRate': 175, 92 | 'nextFire': 0 93 | } 94 | ]; 95 | } else if (this.weaponStage == 2) { 96 | this.weaponClasses = [ 97 | { 98 | 'type': weapons.HeavyLaser, 99 | 'fireRate': 150, 100 | 'nextFire': 0 101 | }, 102 | { 103 | 'type': weapons.Missile, 104 | 'fireRate': 650, 105 | 'nextFire': 0 106 | } 107 | ]; 108 | } else if (this.weaponStage == 3) { 109 | this.weaponClasses = [ 110 | { 111 | 'type': weapons.HeavyLaser, 112 | 'fireRate': 150, 113 | 'nextFire': 0 114 | }, 115 | { 116 | 'type': weapons.Missile, 117 | 'fireRate': 650, 118 | 'nextFire': 0 119 | }, 120 | { 121 | 'type': weapons.HomingMissile, 122 | 'fireRate': 500, 123 | 'nextFire': 0 124 | } 125 | ]; 126 | } else { 127 | this.clearAllEnemies = 150; 128 | } 129 | }; 130 | 131 | this.upgradeSpeed = function () { 132 | if (this.speed < 500) { 133 | this.speed += 50; 134 | } 135 | } 136 | 137 | this.upgradeHealth = function () { 138 | if (this.health < 300) { 139 | this.health += 50; 140 | } 141 | } 142 | 143 | this.update = function(msDuration) { 144 | this.updateAnimation(msDuration); 145 | 146 | // moveIp = move in place 147 | if (this.angle !== null) 148 | this.rect.moveIp(Math.cos(this.angle) * this.speed * (msDuration / 1000), Math.sin(this.angle) * this.speed * (msDuration / 1000)); 149 | 150 | if (this.firing) { 151 | var weapons = this.weapons; 152 | var rect = this.rect; 153 | var enemies = this.enemies; 154 | 155 | this.weaponClasses.forEach(function (weaponInfo) { 156 | weaponInfo.nextFire -= msDuration; 157 | while (weaponInfo.nextFire <= 0) { 158 | weaponInfo.nextFire += weaponInfo.fireRate; 159 | weapons.add(new weaponInfo.type([rect.left + (rect.width / 2), rect.top], enemies)); 160 | } 161 | }); 162 | } 163 | 164 | this.weapons.forEach(function (weapon) { 165 | if (!weapon.update(msDuration)) { 166 | weapon.kill(); 167 | } 168 | }) 169 | }; 170 | 171 | this.damage = function (amount) { 172 | this.health -= amount; 173 | 174 | this.damaged = 150; 175 | 176 | if (this.health <= 0) { 177 | this.kill(); 178 | console.debug("You are dead."); 179 | } 180 | }; 181 | 182 | return this; 183 | }; 184 | gamejs.utils.objects.extend(Ship, animate.AnimatedSprite); 185 | 186 | exports.Powerup = Powerup; 187 | exports.Ship = Ship; 188 | 189 | -------------------------------------------------------------------------------- /screen.js: -------------------------------------------------------------------------------- 1 | var gamejs = require('gamejs'); 2 | 3 | var Hud = function (size) { 4 | this.health = 100; 5 | this.score = 0; 6 | this.level = 0; 7 | this.font = new gamejs.font.Font('20px ubuntu, sans-serif'); 8 | this.healthIcon = gamejs.image.load('images/health-hud.png'); 9 | this.size = size; 10 | 11 | var cachedScore = -1; 12 | var scoreSurface = null; 13 | var cachedLevel = -1; 14 | var levelSurface = null; 15 | 16 | this.draw = function (surface) { 17 | if (this.score != cachedScore) { 18 | cachedScore = this.score; 19 | scoreSurface = this.font.render(this.score, "#fff"); 20 | } 21 | if (this.level != cachedLevel) { 22 | cachedLevel = this.level; 23 | levelSurface = this.font.render("Level " + (this.level + 1), "#fff"); 24 | } 25 | 26 | surface.blit(this.healthIcon, [8, 8]); 27 | gamejs.draw.rect(surface, "#0c0", new gamejs.Rect([26 + 16, 19, Math.max(0, this.health), 4]), 0); 28 | surface.blit(scoreSurface, [this.size[0] - scoreSurface.getSize()[0] - 8, 8]); 29 | surface.blit(levelSurface, [this.size[0] - levelSurface.getSize()[0] - 8, scoreSurface.getSize()[1] + 8]); 30 | }; 31 | 32 | return this; 33 | }; 34 | 35 | exports.Hud = Hud 36 | 37 | -------------------------------------------------------------------------------- /stars.js: -------------------------------------------------------------------------------- 1 | var gamejs = require('gamejs'); 2 | 3 | /* 4 | A simple moving star map with a given number of stars and default speed. 5 | */ 6 | exports.StarMap = function (size) { 7 | this.count = 20; 8 | this.speed = 50; 9 | this.speed_variance = 150; 10 | this.stars = []; 11 | this.size = size 12 | 13 | /* 14 | Seed the star map with randomly placed stars. This should be called 15 | before the first time you call draw()! 16 | */ 17 | this.seed = function () { 18 | for (var x = 0; x < this.count; x++) { 19 | this.stars.push([Math.random() * this.size[0], Math.random() * this.size[1], (Math.random() * this.speed_variance) + this.speed]); 20 | } 21 | }; 22 | 23 | /* 24 | Update the stars, i.e. move them. 25 | */ 26 | this.update = function (msDuration) { 27 | var speed = this.speed; 28 | var speed_variance = this.speed_variance; 29 | var size = this.size; 30 | 31 | this.stars.forEach(function (star) { 32 | star[1] += star[2] * (msDuration / 1000); 33 | 34 | if (star[1] > size[1]) { 35 | // Star is off the screen, make a new one 36 | star[0] = Math.random() * size[0] 37 | star[1] = 0; 38 | star[2] = (Math.random() * speed_variance) + speed; 39 | } 40 | }); 41 | }; 42 | 43 | /* 44 | Draw the stars. Each one is a single white pixel. 45 | */ 46 | this.draw = function(surface) { 47 | this.stars.forEach(function (star) { 48 | gamejs.draw.circle(surface, '#ffffff', [star[0], star[1]], 1, 0); 49 | }); 50 | }; 51 | 52 | return this; 53 | }; 54 | 55 | -------------------------------------------------------------------------------- /weapons.js: -------------------------------------------------------------------------------- 1 | var gamejs = require('gamejs'); 2 | var animate = require('./animate'); 3 | 4 | var MISSILE_FRAMES = null; 5 | 6 | /* 7 | A basic laser weapon. 8 | */ 9 | var Laser = function (rect, enemies) { 10 | Laser.superConstructor.apply(this, arguments); 11 | 12 | this.strength = 10; 13 | this.rect = new gamejs.Rect([rect[0], rect[1] - 10, 3, 10]); 14 | 15 | /* 16 | Update the laser beam position and check for collisions. 17 | */ 18 | this.update = function (msDuration) { 19 | this.rect.top -= 650 * (msDuration / 1000); 20 | 21 | if (this.rect.top < -this.rect.height) { 22 | return false; 23 | } 24 | 25 | return true; 26 | } 27 | 28 | /* 29 | Draw the laser beam. 30 | */ 31 | this.draw = function (surface) { 32 | gamejs.draw.line(surface, '#ffeeaa', [this.rect.left, this.rect.top], [this.rect.left, this.rect.top + 10], 3); 33 | } 34 | 35 | return this; 36 | }; 37 | gamejs.utils.objects.extend(Laser, gamejs.sprite.Sprite); 38 | 39 | /* 40 | A heavier laser weapon. 41 | */ 42 | var HeavyLaser = function(rect, enemies) { 43 | HeavyLaser.superConstructor.apply(this, arguments); 44 | 45 | this.strength = 15; 46 | this.rect = new gamejs.Rect([rect[0] - 5, rect[1] - 10, 10, 10]); 47 | 48 | /* 49 | Update the laser beam position and check for collisions. 50 | */ 51 | this.update = function (msDuration) { 52 | this.rect.top -= 750 * (msDuration / 1000); 53 | 54 | if (this.rect.top < -this.rect.height) { 55 | return false; 56 | } 57 | 58 | return true; 59 | } 60 | 61 | /* 62 | Draw the laser beam. 63 | */ 64 | this.draw = function (surface) { 65 | gamejs.draw.line(surface, '#aaeeff', [this.rect.left, this.rect.top], [this.rect.left, this.rect.top + 10], 3); 66 | gamejs.draw.line(surface, '#aaeeff', [this.rect.left + 10, this.rect.top], [this.rect.left + 10, this.rect.top + 10], 3); 67 | } 68 | 69 | return this; 70 | }; 71 | gamejs.utils.objects.extend(HeavyLaser, gamejs.sprite.Sprite); 72 | 73 | /* 74 | A basic missile weapon. 75 | */ 76 | var Missile = function (rect, enemies) { 77 | Missile.superConstructor.apply(this, arguments); 78 | 79 | this.strength = 30; 80 | 81 | if (MISSILE_FRAMES === null) { 82 | MISSILE_FRAMES = [ 83 | gamejs.image.load('images/missile1_1.png'), 84 | gamejs.image.load('images/missile1_2.png') 85 | ]; 86 | } 87 | 88 | this.frames = MISSILE_FRAMES; 89 | this.animationSpeed = 150; 90 | this.rect = new gamejs.Rect([rect[0] - 3, rect[1] - 4, 7, 28]); 91 | this.speed = 300; 92 | 93 | /* 94 | Update the laser beam position and check for collisions. 95 | */ 96 | this.update = function (msDuration) { 97 | this.updateAnimation(msDuration); 98 | 99 | this.rect.top -= this.speed * (msDuration / 1000); 100 | 101 | if (this.rect.top < -this.rect.height) { 102 | return false; 103 | } 104 | 105 | return true; 106 | } 107 | 108 | return this; 109 | }; 110 | gamejs.utils.objects.extend(Missile, animate.AnimatedSprite); 111 | 112 | /* 113 | A basic homing missle weapon. 114 | */ 115 | var HomingMissile = function (rect, enemies) { 116 | HomingMissile.superConstructor.apply(this, arguments); 117 | 118 | this.strength = 5; 119 | this.rect = new gamejs.Rect([rect[0] - 1, rect[1] - 1, 3, 3]); 120 | this.enemies = enemies; 121 | this.angle = Math.PI * 0.5; 122 | this.speed = 300; 123 | this.fuel = 3000; 124 | this.target = null; 125 | 126 | /* 127 | Update the laser beam position and check for collisions. 128 | */ 129 | this.update = function (msDuration) { 130 | if ((!this.target || this.target.isDead()) && this.enemies.length) { 131 | var group = this.enemies[Math.floor(Math.random() * this.enemies.length)]; 132 | if (group.sprites().length) { 133 | this.target = group.sprites()[Math.floor(Math.random() * group.sprites().length)]; 134 | } else { 135 | this.target = null; 136 | } 137 | } 138 | 139 | if (this.target) { 140 | this.angle = Math.atan2((this.target.rect.left + (this.target.rect.width / 2)) - this.rect.left, (this.target.rect.top + (this.target.rect.height / 2)) - this.rect.top) - (Math.PI / 2); 141 | } 142 | 143 | this.rect.left += Math.cos(this.angle) * this.speed * (msDuration / 1000); 144 | this.rect.top -= Math.sin(this.angle) * this.speed * (msDuration / 1000); 145 | 146 | this.fuel -= msDuration; 147 | if (this.fuel < 0) { 148 | return false; 149 | } 150 | 151 | return true; 152 | } 153 | 154 | this.draw = function (surface) { 155 | gamejs.draw.circle(surface, "#aef", [this.rect.left, this.rect.top], 3, 2); 156 | }; 157 | 158 | return this; 159 | }; 160 | gamejs.utils.objects.extend(HomingMissile, gamejs.sprite.Sprite); 161 | 162 | exports.Laser = Laser; 163 | exports.HeavyLaser = HeavyLaser; 164 | exports.Missile = Missile; 165 | exports.HomingMissile = HomingMissile; 166 | 167 | --------------------------------------------------------------------------------