├── 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 |
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 |
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 |
--------------------------------------------------------------------------------