├── .github
└── FUNDING.yml
├── README.md
├── assets
├── boss.png
├── bullet.png
├── death-ray.png
├── enemy-blue-bullet.png
├── enemy-blue.png
├── enemy-green.png
├── explode.png
├── player.png
├── spacefont
│ ├── spacefont.png
│ └── spacefont.xml
└── starfield.png
├── game.js
└── index.html
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: jschomay # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | PHASER GAME DEMO
2 | ================
3 | The commits in this repo go step by step through the process of building a polished space shooter game with the [Phaser.js](http://phaser.io) HTML5 game framework.
4 |
5 | 
6 |
7 | You can follow through a written tutorial of this game with more commentary at [on my blog](http://jschomay.tumblr.com/post/103568304133/html5-space-shooter-game-tutorial-with-phaser-js).
8 |
9 |
10 | Credits
11 | -------
12 | - Many of the assets and the base for the game come from the [invaders phaser examples](http://examples.phaser.io/_site/view_full.html?d=games&f=invaders.js&t=invaders).
13 | - Cool enemy ship graphics from http://opengameart.org/users/skorpio
14 | - Nice enemy lasers from http://opengameart.org/users/bonsaiheldin
15 | - Font face is http://www.fontspace.com/nimavisual/trench
16 |
17 |
18 | Very brief introduction to game dev concepts
19 | --------------------------------------------
20 | Games are all about interactivity. A game is an experience that unfolds over time through a collaboration between the game creator(s) and the player.
21 |
22 | ### 3 facets to game design
23 |
24 | 1. Production values - graphics, sounds, special effects, polish
25 | 2. Content - story, theme, concept, characters, artwork
26 | 3. Gameplay - mechanics, challenge, pacing, fun, controls, "feel"
27 |
28 | ### 4 basic parts of game dev
29 |
30 | 1. Game loop - means to change and display state over time (usually for animation (at 60fps) but could be turn based)
31 | 2. Input - get input from the player (otherwise it is a simulation, not a game)
32 | 3. Update - change the game state based on internal logic and values and responding to player input
33 | 4. Render - redraw the visual representation of the game state at that time
34 |
35 |
36 | Resources
37 | ---------
38 | #### Phaser:
39 | - Examples - http://examples.phaser.io/
40 | - API documentation - http://docs.phaser.io/
41 | - Starting templates - https://github.com/photonstorm/phaser/tree/master/resources/Project%20Templates
42 | - Forum - http://www.html5gamedevs.com/forum/14-phaser/
43 | - Interactive mechanics examples - http://gamemechanicexplorer.com/
44 | - Tons of online tutorials - http://www.lessmilk.com/phaser-tutorial/
45 |
46 | #### Game assets:
47 | - Sound effects (sfx) generator - http://www.superflashbros.net/as3sfxr/
48 | - Custom bitmap font generator - http://kvazars.com/littera/
49 | - Free art and sound database - http://opengameart.org
50 | - Links to other more assets - http://letsmakegames.org/resources/art-assets-for-game-developers/
51 | - Assets on redit - http://www.reddit.com/r/GameAssets
52 |
53 |
54 | Additional topics not covered
55 | -----------------------------
56 | - Best practices for more maintainable code and smaller files
57 | - Modular development (commonJS, etc)
58 | - Custom classes inheriting from phaser classes
59 | - Build tools (browserify, gulp, etc)
60 | - yoaman for scaffolding
61 | - Scaling modes
62 | - Optimization / profiling / debugging / testing
63 | - Mobile
64 | - Wrapping for native (CocoonJS, Cordova/PhoneGap)
65 | - Communicating with server / multiplayer
66 | - Tilemaps and other game types
67 | - Marketing / distribution / monetization / 3rd party APIs
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/assets/boss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jschomay/phaser-demo-game/7a9fbe559a7f8043df1a178dc74019fd3cec6e94/assets/boss.png
--------------------------------------------------------------------------------
/assets/bullet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jschomay/phaser-demo-game/7a9fbe559a7f8043df1a178dc74019fd3cec6e94/assets/bullet.png
--------------------------------------------------------------------------------
/assets/death-ray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jschomay/phaser-demo-game/7a9fbe559a7f8043df1a178dc74019fd3cec6e94/assets/death-ray.png
--------------------------------------------------------------------------------
/assets/enemy-blue-bullet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jschomay/phaser-demo-game/7a9fbe559a7f8043df1a178dc74019fd3cec6e94/assets/enemy-blue-bullet.png
--------------------------------------------------------------------------------
/assets/enemy-blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jschomay/phaser-demo-game/7a9fbe559a7f8043df1a178dc74019fd3cec6e94/assets/enemy-blue.png
--------------------------------------------------------------------------------
/assets/enemy-green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jschomay/phaser-demo-game/7a9fbe559a7f8043df1a178dc74019fd3cec6e94/assets/enemy-green.png
--------------------------------------------------------------------------------
/assets/explode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jschomay/phaser-demo-game/7a9fbe559a7f8043df1a178dc74019fd3cec6e94/assets/explode.png
--------------------------------------------------------------------------------
/assets/player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jschomay/phaser-demo-game/7a9fbe559a7f8043df1a178dc74019fd3cec6e94/assets/player.png
--------------------------------------------------------------------------------
/assets/spacefont/spacefont.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jschomay/phaser-demo-game/7a9fbe559a7f8043df1a178dc74019fd3cec6e94/assets/spacefont/spacefont.png
--------------------------------------------------------------------------------
/assets/spacefont/spacefont.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/assets/starfield.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jschomay/phaser-demo-game/7a9fbe559a7f8043df1a178dc74019fd3cec6e94/assets/starfield.png
--------------------------------------------------------------------------------
/game.js:
--------------------------------------------------------------------------------
1 | var game = new Phaser.Game(800,600, Phaser.AUTO, 'phaser-demo', {preload: preload, create: create, update: update, render: render});
2 |
3 | var player;
4 | var greenEnemies;
5 | var blueEnemies;
6 | var enemyBullets;
7 | var starfield;
8 | var cursors;
9 | var bank;
10 | var shipTrail;
11 | var explosions;
12 | var playerDeath;
13 | var bullets;
14 | var fireButton;
15 | var bulletTimer = 0;
16 | var shields;
17 | var score = 0;
18 | var scoreText;
19 | var greenEnemyLaunchTimer;
20 | var greenEnemySpacing = 1000;
21 | var blueEnemyLaunchTimer;
22 | var blueEnemyLaunched = false;
23 | var blueEnemySpacing = 2500;
24 | var bossLaunchTimer;
25 | var bossLaunched = false;
26 | var bossSpacing = 20000;
27 | var bossBulletTimer = 0;
28 | var bossYdirection = -1;
29 | var gameOver;
30 |
31 | var ACCLERATION = 600;
32 | var DRAG = 400;
33 | var MAXSPEED = 400;
34 |
35 | function preload() {
36 | game.load.image('starfield', 'https://raw.githubusercontent.com/jschomay/phaser-demo-game/master/assets/starfield.png');
37 | game.load.image('ship', 'https://raw.githubusercontent.com/jschomay/phaser-demo-game/master/assets/player.png');
38 | game.load.image('bullet', 'https://raw.githubusercontent.com/jschomay/phaser-demo-game/master/assets/bullet.png');
39 | game.load.image('enemy-green', 'https://raw.githubusercontent.com/jschomay/phaser-demo-game/master/assets/enemy-green.png');
40 | game.load.image('enemy-blue', 'https://raw.githubusercontent.com/jschomay/phaser-demo-game/master/assets/enemy-blue.png');
41 | game.load.image('blueEnemyBullet', 'https://raw.githubusercontent.com/jschomay/phaser-demo-game/master/assets/enemy-blue-bullet.png');
42 | game.load.spritesheet('explosion', 'https://raw.githubusercontent.com/jschomay/phaser-demo-game/master/assets/explode.png', 128, 128);
43 | game.load.bitmapFont('spacefont', 'https://raw.githubusercontent.com/jschomay/phaser-demo-game/master/assets/spacefont/spacefont.png', 'https://rawgit.com/jschomay/phaser-demo-game/master/assets/spacefont/spacefont.xml');
44 | game.load.image('boss', 'https://raw.githubusercontent.com/jschomay/phaser-demo-game/master/assets/boss.png');
45 | game.load.image('deathRay', 'https://raw.githubusercontent.com/jschomay/phaser-demo-game/master/assets/death-ray.png');
46 | }
47 |
48 | function create() {
49 | // The scrolling starfield background
50 | starfield = game.add.tileSprite(0, 0, 800, 600, 'starfield');
51 |
52 | // Our bullet group
53 | bullets = game.add.group();
54 | bullets.enableBody = true;
55 | bullets.physicsBodyType = Phaser.Physics.ARCADE;
56 | bullets.createMultiple(30, 'bullet');
57 | bullets.setAll('anchor.x', 0.5);
58 | bullets.setAll('anchor.y', 1);
59 | bullets.setAll('outOfBoundsKill', true);
60 | bullets.setAll('checkWorldBounds', true);
61 |
62 | // The hero!
63 | player = game.add.sprite(400, 500, 'ship');
64 | player.health = 100;
65 | player.anchor.setTo(0.5, 0.5);
66 | game.physics.enable(player, Phaser.Physics.ARCADE);
67 | player.body.maxVelocity.setTo(MAXSPEED, MAXSPEED);
68 | player.body.drag.setTo(DRAG, DRAG);
69 | player.weaponLevel = 1
70 | player.events.onKilled.add(function(){
71 | shipTrail.kill();
72 | });
73 | player.events.onRevived.add(function(){
74 | shipTrail.start(false, 5000, 10);
75 | });
76 |
77 | // The baddies!
78 | greenEnemies = game.add.group();
79 | greenEnemies.enableBody = true;
80 | greenEnemies.physicsBodyType = Phaser.Physics.ARCADE;
81 | greenEnemies.createMultiple(5, 'enemy-green');
82 | greenEnemies.setAll('anchor.x', 0.5);
83 | greenEnemies.setAll('anchor.y', 0.5);
84 | greenEnemies.setAll('scale.x', 0.5);
85 | greenEnemies.setAll('scale.y', 0.5);
86 | greenEnemies.setAll('angle', 180);
87 | greenEnemies.forEach(function(enemy){
88 | addEnemyEmitterTrail(enemy);
89 | enemy.body.setSize(enemy.width * 3 / 4, enemy.height * 3 / 4);
90 | enemy.damageAmount = 20;
91 | enemy.events.onKilled.add(function(){
92 | enemy.trail.kill();
93 | });
94 | });
95 |
96 | game.time.events.add(1000, launchGreenEnemy);
97 |
98 | // Blue enemy's bullets
99 | blueEnemyBullets = game.add.group();
100 | blueEnemyBullets.enableBody = true;
101 | blueEnemyBullets.physicsBodyType = Phaser.Physics.ARCADE;
102 | blueEnemyBullets.createMultiple(30, 'blueEnemyBullet');
103 | blueEnemyBullets.callAll('crop', null, {x: 90, y: 0, width: 90, height: 70});
104 | blueEnemyBullets.setAll('alpha', 0.9);
105 | blueEnemyBullets.setAll('anchor.x', 0.5);
106 | blueEnemyBullets.setAll('anchor.y', 0.5);
107 | blueEnemyBullets.setAll('outOfBoundsKill', true);
108 | blueEnemyBullets.setAll('checkWorldBounds', true);
109 | blueEnemyBullets.forEach(function(enemy){
110 | enemy.body.setSize(20, 20);
111 | });
112 |
113 | // More baddies!
114 | blueEnemies = game.add.group();
115 | blueEnemies.enableBody = true;
116 | blueEnemies.physicsBodyType = Phaser.Physics.ARCADE;
117 | blueEnemies.createMultiple(30, 'enemy-blue');
118 | blueEnemies.setAll('anchor.x', 0.5);
119 | blueEnemies.setAll('anchor.y', 0.5);
120 | blueEnemies.setAll('scale.x', 0.5);
121 | blueEnemies.setAll('scale.y', 0.5);
122 | blueEnemies.setAll('angle', 180);
123 | blueEnemies.forEach(function(enemy){
124 | enemy.damageAmount = 40;
125 | });
126 |
127 | // The boss
128 | boss = game.add.sprite(0, 0, 'boss');
129 | boss.exists = false;
130 | boss.alive = false;
131 | boss.anchor.setTo(0.5, 0.5);
132 | boss.damageAmount = 50;
133 | boss.angle = 180;
134 | boss.scale.x = 0.6;
135 | boss.scale.y = 0.6;
136 | game.physics.enable(boss, Phaser.Physics.ARCADE);
137 | boss.body.maxVelocity.setTo(100, 80);
138 | boss.dying = false;
139 | boss.finishOff = function() {
140 | if (!boss.dying) {
141 | boss.dying = true;
142 | bossDeath.x = boss.x;
143 | bossDeath.y = boss.y;
144 | bossDeath.start(false, 1000, 50, 20);
145 | // kill boss after explotions
146 | game.time.events.add(1000, function(){
147 | var explosion = explosions.getFirstExists(false);
148 | var beforeScaleX = explosions.scale.x;
149 | var beforeScaleY = explosions.scale.y;
150 | var beforeAlpha = explosions.alpha;
151 | explosion.reset(boss.body.x + boss.body.halfWidth, boss.body.y + boss.body.halfHeight);
152 | explosion.alpha = 0.4;
153 | explosion.scale.x = 3;
154 | explosion.scale.y = 3;
155 | var animation = explosion.play('explosion', 30, false, true);
156 | animation.onComplete.addOnce(function(){
157 | explosion.scale.x = beforeScaleX;
158 | explosion.scale.y = beforeScaleY;
159 | explosion.alpha = beforeAlpha;
160 | });
161 | boss.kill();
162 | booster.kill();
163 | boss.dying = false;
164 | bossDeath.on = false;
165 | // queue next boss
166 | bossLaunchTimer = game.time.events.add(game.rnd.integerInRange(bossSpacing, bossSpacing + 5000), launchBoss);
167 | });
168 |
169 | // reset pacing for other enemies
170 | blueEnemySpacing = 2500;
171 | greenEnemySpacing = 1000;
172 |
173 | // give some bonus health
174 | player.health = Math.min(100, player.health + 40);
175 | shields.render();
176 | }
177 | };
178 |
179 | // Boss death ray
180 | function addRay(leftRight) {
181 | var ray = game.add.sprite(leftRight * boss.width * 0.75, 0, 'deathRay');
182 | ray.alive = false;
183 | ray.visible = false;
184 | boss.addChild(ray);
185 | ray.crop({x: 0, y: 0, width: 40, height: 40});
186 | ray.anchor.x = 0.5;
187 | ray.anchor.y = 0.5;
188 | ray.scale.x = 2.5;
189 | ray.damageAmount = boss.damageAmount;
190 | game.physics.enable(ray, Phaser.Physics.ARCADE);
191 | ray.body.setSize(ray.width / 5, ray.height / 4);
192 | ray.update = function() {
193 | this.alpha = game.rnd.realInRange(0.6, 1);
194 | };
195 | boss['ray' + (leftRight > 0 ? 'Right' : 'Left')] = ray;
196 | }
197 | addRay(1);
198 | addRay(-1);
199 | // need to add the ship texture to the group so it renders over the rays
200 | var ship = game.add.sprite(0, 0, 'boss');
201 | ship.anchor = {x: 0.5, y: 0.5};
202 | boss.addChild(ship);
203 |
204 | boss.fire = function() {
205 | if (game.time.now > bossBulletTimer) {
206 | var raySpacing = 3000;
207 | var chargeTime = 1500;
208 | var rayTime = 1500;
209 |
210 | function chargeAndShoot(side) {
211 | ray = boss['ray' + side];
212 | ray.name = side
213 | ray.revive();
214 | ray.y = 80;
215 | ray.alpha = 0;
216 | ray.scale.y = 13;
217 | game.add.tween(ray).to({alpha: 1}, chargeTime, Phaser.Easing.Linear.In, true).onComplete.add(function(ray){
218 | ray.scale.y = 150;
219 | game.add.tween(ray).to({y: -1500}, rayTime, Phaser.Easing.Linear.In, true).onComplete.add(function(ray){
220 | ray.kill();
221 | });
222 | });
223 | }
224 | chargeAndShoot('Right');
225 | chargeAndShoot('Left');
226 |
227 | bossBulletTimer = game.time.now + raySpacing;
228 | }
229 | };
230 |
231 | boss.update = function() {
232 | if (!boss.alive) return;
233 |
234 | boss.rayLeft.update();
235 | boss.rayRight.update();
236 |
237 | if (boss.y > 140) {
238 | boss.body.acceleration.y = -50;
239 | }
240 | if (boss.y < 140) {
241 | boss.body.acceleration.y = 50;
242 | }
243 | if (boss.x > player.x + 50) {
244 | boss.body.acceleration.x = -50;
245 | } else if (boss.x < player.x - 50) {
246 | boss.body.acceleration.x = 50;
247 | } else {
248 | boss.body.acceleration.x = 0;
249 | }
250 |
251 | // Squish and rotate boss for illusion of "banking"
252 | var bank = boss.body.velocity.x / MAXSPEED;
253 | boss.scale.x = 0.6 - Math.abs(bank) / 3;
254 | boss.angle = 180 - bank * 20;
255 |
256 | booster.x = boss.x + -5 * bank;
257 | booster.y = boss.y + 10 * Math.abs(bank) - boss.height / 2;
258 |
259 | // fire if player is in target
260 | var angleToPlayer = game.math.radToDeg(game.physics.arcade.angleBetween(boss, player)) - 90;
261 | var anglePointing = 180 - Math.abs(boss.angle);
262 | if (anglePointing - angleToPlayer < 18) {
263 | boss.fire();
264 | }
265 | }
266 |
267 | // boss's boosters
268 | booster = game.add.emitter(boss.body.x, boss.body.y - boss.height / 2);
269 | booster.width = 0;
270 | booster.makeParticles('blueEnemyBullet');
271 | booster.forEach(function(p){
272 | p.crop({x: 120, y: 0, width: 45, height: 50});
273 | // clever way of making 2 exhaust trails by shifing particles randomly left or right
274 | p.anchor.x = game.rnd.pick([1,-1]) * 0.95 + 0.5;
275 | p.anchor.y = 0.75;
276 | });
277 | booster.setXSpeed(0, 0);
278 | booster.setRotation(0,0);
279 | booster.setYSpeed(-30, -50);
280 | booster.gravity = 0;
281 | booster.setAlpha(1, 0.1, 400);
282 | booster.setScale(0.3, 0, 0.7, 0, 5000, Phaser.Easing.Quadratic.Out);
283 | boss.bringToTop();
284 |
285 | // And some controls to play the game with
286 | cursors = game.input.keyboard.createCursorKeys();
287 | fireButton = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
288 |
289 | // Add an emitter for the ship's trail
290 | shipTrail = game.add.emitter(player.x, player.y + 10, 400);
291 | shipTrail.width = 10;
292 | shipTrail.makeParticles('bullet');
293 | shipTrail.setXSpeed(30, -30);
294 | shipTrail.setYSpeed(200, 180);
295 | shipTrail.setRotation(50,-50);
296 | shipTrail.setAlpha(1, 0.01, 800);
297 | shipTrail.setScale(0.05, 0.4, 0.05, 0.4, 2000, Phaser.Easing.Quintic.Out);
298 | shipTrail.start(false, 5000, 10);
299 |
300 | // An explosion pool
301 | explosions = game.add.group();
302 | explosions.enableBody = true;
303 | explosions.physicsBodyType = Phaser.Physics.ARCADE;
304 | explosions.createMultiple(30, 'explosion');
305 | explosions.setAll('anchor.x', 0.5);
306 | explosions.setAll('anchor.y', 0.5);
307 | explosions.forEach( function(explosion) {
308 | explosion.animations.add('explosion');
309 | });
310 |
311 | // Big explosion
312 | playerDeath = game.add.emitter(player.x, player.y);
313 | playerDeath.width = 50;
314 | playerDeath.height = 50;
315 | playerDeath.makeParticles('explosion', [0,1,2,3,4,5,6,7], 10);
316 | playerDeath.setAlpha(0.9, 0, 800);
317 | playerDeath.setScale(0.1, 0.6, 0.1, 0.6, 1000, Phaser.Easing.Quintic.Out);
318 |
319 | // Big explosion for boss
320 | bossDeath = game.add.emitter(boss.x, boss.y);
321 | bossDeath.width = boss.width / 2;
322 | bossDeath.height = boss.height / 2;
323 | bossDeath.makeParticles('explosion', [0,1,2,3,4,5,6,7], 20);
324 | bossDeath.setAlpha(0.9, 0, 900);
325 | bossDeath.setScale(0.3, 1.0, 0.3, 1.0, 1000, Phaser.Easing.Quintic.Out);
326 |
327 | // Shields stat
328 | shields = game.add.bitmapText(game.world.width - 250, 10, 'spacefont', '' + player.health +'%', 50);
329 | shields.render = function () {
330 | shields.text = 'Shields: ' + Math.max(player.health, 0) +'%';
331 | };
332 | shields.render();
333 |
334 | // Score
335 | scoreText = game.add.bitmapText(10, 10, 'spacefont', '', 50);
336 | scoreText.render = function () {
337 | scoreText.text = 'Score: ' + score;
338 | };
339 | scoreText.render();
340 |
341 | // Game over text
342 | gameOver = game.add.bitmapText(game.world.centerX, game.world.centerY, 'spacefont', 'GAME OVER!', 110);
343 | gameOver.x = gameOver.x - gameOver.textWidth / 2;
344 | gameOver.y = gameOver.y - gameOver.textHeight / 3;
345 | gameOver.visible = false;
346 | }
347 |
348 | function update() {
349 | // Scroll the background
350 | starfield.tilePosition.y += 2;
351 |
352 | // Reset the player, then check for movement keys
353 | player.body.acceleration.x = 0;
354 |
355 | if (cursors.left.isDown)
356 | {
357 | player.body.acceleration.x = -ACCLERATION;
358 | }
359 | else if (cursors.right.isDown)
360 | {
361 | player.body.acceleration.x = ACCLERATION;
362 | }
363 |
364 | // Stop at screen edges
365 | if (player.x > game.width - 50) {
366 | player.x = game.width - 50;
367 | player.body.acceleration.x = 0;
368 | }
369 | if (player.x < 50) {
370 | player.x = 50;
371 | player.body.acceleration.x = 0;
372 | }
373 |
374 | // Fire bullet
375 | if (player.alive && (fireButton.isDown || game.input.activePointer.isDown)) {
376 | fireBullet();
377 | }
378 |
379 | // Move ship towards mouse pointer
380 | if (game.input.x < game.width - 20 &&
381 | game.input.x > 20 &&
382 | game.input.y > 20 &&
383 | game.input.y < game.height - 20) {
384 | var minDist = 200;
385 | var dist = game.input.x - player.x;
386 | player.body.velocity.x = MAXSPEED * game.math.clamp(dist / minDist, -1, 1);
387 | }
388 |
389 | // Squish and rotate ship for illusion of "banking"
390 | bank = player.body.velocity.x / MAXSPEED;
391 | player.scale.x = 1 - Math.abs(bank) / 2;
392 | player.angle = bank * 30;
393 |
394 | // Keep the shipTrail lined up with the ship
395 | shipTrail.x = player.x;
396 |
397 | // Check collisions
398 | game.physics.arcade.overlap(player, greenEnemies, shipCollide, null, this);
399 | game.physics.arcade.overlap(greenEnemies, bullets, hitEnemy, null, this);
400 |
401 | game.physics.arcade.overlap(player, blueEnemies, shipCollide, null, this);
402 | game.physics.arcade.overlap(blueEnemies, bullets, hitEnemy, null, this);
403 |
404 | game.physics.arcade.overlap(boss, bullets, hitEnemy, bossHitTest, this);
405 | game.physics.arcade.overlap(player, boss.rayLeft, enemyHitsPlayer, null, this);
406 | game.physics.arcade.overlap(player, boss.rayRight, enemyHitsPlayer, null, this);
407 |
408 | game.physics.arcade.overlap(blueEnemyBullets, player, enemyHitsPlayer, null, this);
409 |
410 | // Game over?
411 | if (! player.alive && gameOver.visible === false) {
412 | gameOver.visible = true;
413 | gameOver.alpha = 0;
414 | var fadeInGameOver = game.add.tween(gameOver);
415 | fadeInGameOver.to({alpha: 1}, 1000, Phaser.Easing.Quintic.Out);
416 | fadeInGameOver.onComplete.add(setResetHandlers);
417 | fadeInGameOver.start();
418 | function setResetHandlers() {
419 | // The "click to restart" handler
420 | tapRestart = game.input.onTap.addOnce(_restart,this);
421 | spaceRestart = fireButton.onDown.addOnce(_restart,this);
422 | function _restart() {
423 | tapRestart.detach();
424 | spaceRestart.detach();
425 | restart();
426 | }
427 | }
428 | }
429 | }
430 |
431 | function render() {
432 | // for (var i = 0; i < greenEnemies.length; i++)
433 | // {
434 | // game.debug.body(greenEnemies.children[i]);
435 | // }
436 | // game.debug.body(player);
437 | }
438 |
439 | function fireBullet() {
440 | switch (player.weaponLevel) {
441 | case 1:
442 | // To avoid them being allowed to fire too fast we set a time limit
443 | if (game.time.now > bulletTimer)
444 | {
445 | var BULLET_SPEED = 400;
446 | var BULLET_SPACING = 250;
447 | // Grab the first bullet we can from the pool
448 | var bullet = bullets.getFirstExists(false);
449 |
450 | if (bullet)
451 | {
452 | // And fire it
453 | // Make bullet come out of tip of ship with right angle
454 | var bulletOffset = 20 * Math.sin(game.math.degToRad(player.angle));
455 | bullet.reset(player.x + bulletOffset, player.y);
456 | bullet.angle = player.angle;
457 | game.physics.arcade.velocityFromAngle(bullet.angle - 90, BULLET_SPEED, bullet.body.velocity);
458 | bullet.body.velocity.x += player.body.velocity.x;
459 |
460 | bulletTimer = game.time.now + BULLET_SPACING;
461 | }
462 | }
463 | break;
464 |
465 | case 2:
466 | if (game.time.now > bulletTimer) {
467 | var BULLET_SPEED = 400;
468 | var BULLET_SPACING = 550;
469 |
470 |
471 | for (var i = 0; i < 3; i++) {
472 | var bullet = bullets.getFirstExists(false);
473 | if (bullet) {
474 | // Make bullet come out of tip of ship with right angle
475 | var bulletOffset = 20 * Math.sin(game.math.degToRad(player.angle));
476 | bullet.reset(player.x + bulletOffset, player.y);
477 | // "Spread" angle of 1st and 3rd bullets
478 | var spreadAngle;
479 | if (i === 0) spreadAngle = -20;
480 | if (i === 1) spreadAngle = 0;
481 | if (i === 2) spreadAngle = 20;
482 | bullet.angle = player.angle + spreadAngle;
483 | game.physics.arcade.velocityFromAngle(spreadAngle - 90, BULLET_SPEED, bullet.body.velocity);
484 | bullet.body.velocity.x += player.body.velocity.x;
485 | }
486 | bulletTimer = game.time.now + BULLET_SPACING;
487 | }
488 | }
489 | }
490 | }
491 |
492 |
493 | function launchGreenEnemy() {
494 | var ENEMY_SPEED = 300;
495 |
496 | var enemy = greenEnemies.getFirstExists(false);
497 | if (enemy) {
498 | enemy.reset(game.rnd.integerInRange(0, game.width), -20);
499 | enemy.body.velocity.x = game.rnd.integerInRange(-300, 300);
500 | enemy.body.velocity.y = ENEMY_SPEED;
501 | enemy.body.drag.x = 100;
502 |
503 | enemy.trail.start(false, 800, 1);
504 |
505 | // Update function for each enemy ship to update rotation etc
506 | enemy.update = function(){
507 | enemy.angle = 180 - game.math.radToDeg(Math.atan2(enemy.body.velocity.x, enemy.body.velocity.y));
508 |
509 | enemy.trail.x = enemy.x;
510 | enemy.trail.y = enemy.y -10;
511 |
512 | // Kill enemies once they go off screen
513 | if (enemy.y > game.height + 200) {
514 | enemy.kill();
515 | enemy.y = -20;
516 | }
517 | }
518 | }
519 |
520 | // Send another enemy soon
521 | greenEnemyLaunchTimer = game.time.events.add(game.rnd.integerInRange(greenEnemySpacing, greenEnemySpacing + 1000), launchGreenEnemy);
522 | }
523 |
524 | function launchBlueEnemy() {
525 | var startingX = game.rnd.integerInRange(100, game.width - 100);
526 | var verticalSpeed = 180;
527 | var spread = 60;
528 | var frequency = 70;
529 | var verticalSpacing = 70;
530 | var numEnemiesInWave = 5;
531 |
532 | // Launch wave
533 | for (var i =0; i < numEnemiesInWave; i++) {
534 | var enemy = blueEnemies.getFirstExists(false);
535 | if (enemy) {
536 | enemy.startingX = startingX;
537 | enemy.reset(game.width / 2, -verticalSpacing * i);
538 | enemy.body.velocity.y = verticalSpeed;
539 |
540 | // Set up firing
541 | var bulletSpeed = 400;
542 | var firingDelay = 2000;
543 | enemy.bullets = 1;
544 | enemy.lastShot = 0;
545 |
546 | // Update function for each enemy
547 | enemy.update = function(){
548 | // Wave movement
549 | this.body.x = this.startingX + Math.sin((this.y) / frequency) * spread;
550 |
551 | // Squish and rotate ship for illusion of "banking"
552 | bank = Math.cos((this.y + 60) / frequency)
553 | this.scale.x = 0.5 - Math.abs(bank) / 8;
554 | this.angle = 180 - bank * 2;
555 |
556 | // Fire
557 | enemyBullet = blueEnemyBullets.getFirstExists(false);
558 | if (enemyBullet &&
559 | this.alive &&
560 | this.bullets &&
561 | this.y > game.width / 8 &&
562 | game.time.now > firingDelay + this.lastShot) {
563 | this.lastShot = game.time.now;
564 | this.bullets--;
565 | enemyBullet.reset(this.x, this.y + this.height / 2);
566 | enemyBullet.damageAmount = this.damageAmount;
567 | var angle = game.physics.arcade.moveToObject(enemyBullet, player, bulletSpeed);
568 | enemyBullet.angle = game.math.radToDeg(angle);
569 | }
570 |
571 | // Kill enemies once they go off screen
572 | if (this.y > game.height + 200) {
573 | this.kill();
574 | this.y = -20;
575 | }
576 | };
577 | }
578 | }
579 |
580 | // Send another wave soon
581 | blueEnemyLaunchTimer = game.time.events.add(game.rnd.integerInRange(blueEnemySpacing, blueEnemySpacing + 4000), launchBlueEnemy);
582 | }
583 |
584 | function launchBoss() {
585 | boss.reset(game.width / 2, -boss.height);
586 | booster.start(false, 1000, 10);
587 | boss.health = 501;
588 | bossBulletTimer = game.time.now + 5000;
589 | }
590 |
591 | function addEnemyEmitterTrail(enemy) {
592 | var enemyTrail = game.add.emitter(enemy.x, player.y - 10, 100);
593 | enemyTrail.width = 10;
594 | enemyTrail.makeParticles('explosion', [1,2,3,4,5]);
595 | enemyTrail.setXSpeed(20, -20);
596 | enemyTrail.setRotation(50,-50);
597 | enemyTrail.setAlpha(0.4, 0, 800);
598 | enemyTrail.setScale(0.01, 0.1, 0.01, 0.1, 1000, Phaser.Easing.Quintic.Out);
599 | enemy.trail = enemyTrail;
600 | }
601 |
602 |
603 | function shipCollide(player, enemy) {
604 | enemy.kill();
605 |
606 | player.damage(enemy.damageAmount);
607 | shields.render();
608 |
609 | if (player.alive) {
610 | var explosion = explosions.getFirstExists(false);
611 | explosion.reset(player.body.x + player.body.halfWidth, player.body.y + player.body.halfHeight);
612 | explosion.alpha = 0.7;
613 | explosion.play('explosion', 30, false, true);
614 | } else {
615 | playerDeath.x = player.x;
616 | playerDeath.y = player.y;
617 | playerDeath.start(false, 1000, 10, 10);
618 | }
619 | }
620 |
621 |
622 | function hitEnemy(enemy, bullet) {
623 | var explosion = explosions.getFirstExists(false);
624 | explosion.reset(bullet.body.x + bullet.body.halfWidth, bullet.body.y + bullet.body.halfHeight);
625 | explosion.body.velocity.y = enemy.body.velocity.y;
626 | explosion.alpha = 0.7;
627 | explosion.play('explosion', 30, false, true);
628 | if (enemy.finishOff && enemy.health < 5) {
629 | enemy.finishOff();
630 | } else {
631 | enemy.damage(enemy.damageAmount);
632 | }
633 | bullet.kill();
634 |
635 | // Increase score
636 | score += enemy.damageAmount * 10;
637 | scoreText.render();
638 |
639 | // Pacing
640 |
641 | // Enemies come quicker as score increases
642 | greenEnemySpacing *= 0.9;
643 |
644 | // Blue enemies come in after a score of 1000
645 | if (!blueEnemyLaunched && score > 1000) {
646 | blueEnemyLaunched = true;
647 | launchBlueEnemy();
648 | // Slow green enemies down now that there are other enemies
649 | greenEnemySpacing *= 2;
650 | }
651 |
652 | // Launch boss
653 | if (!bossLaunched && score > 15000) {
654 | greenEnemySpacing = 5000;
655 | blueEnemySpacing = 12000;
656 | // dramatic pause before boss
657 | game.time.events.add(2000, function(){
658 | bossLaunched = true;
659 | launchBoss();
660 | });
661 | }
662 |
663 | // Weapon upgrade
664 | if (score > 5000 && player.weaponLevel < 2) {
665 | player.weaponLevel = 2;
666 | }
667 | }
668 |
669 | // Don't count a hit in the lower right and left quarants to aproximate better collisions
670 | function bossHitTest(boss, bullet) {
671 | if ((bullet.x > boss.x + boss.width / 5 &&
672 | bullet.y > boss.y) ||
673 | (bullet.x < boss.x - boss.width / 5 &&
674 | bullet.y > boss.y)) {
675 | return false;
676 | } else {
677 | return true;
678 | }
679 | }
680 |
681 | function enemyHitsPlayer (player, bullet) {
682 | bullet.kill();
683 |
684 | player.damage(bullet.damageAmount);
685 | shields.render()
686 |
687 | if (player.alive) {
688 | var explosion = explosions.getFirstExists(false);
689 | explosion.reset(player.body.x + player.body.halfWidth, player.body.y + player.body.halfHeight);
690 | explosion.alpha = 0.7;
691 | explosion.play('explosion', 30, false, true);
692 | } else {
693 | playerDeath.x = player.x;
694 | playerDeath.y = player.y;
695 | playerDeath.start(false, 1000, 10, 10);
696 | }
697 | }
698 |
699 |
700 | function restart () {
701 | // Reset the enemies
702 | greenEnemies.callAll('kill');
703 | game.time.events.remove(greenEnemyLaunchTimer);
704 | game.time.events.add(1000, launchGreenEnemy);
705 | blueEnemies.callAll('kill');
706 | blueEnemyBullets.callAll('kill');
707 | game.time.events.remove(blueEnemyLaunchTimer);
708 | boss.kill();
709 | booster.kill();
710 | game.time.events.remove(bossLaunchTimer);
711 |
712 | blueEnemies.callAll('kill');
713 | game.time.events.remove(blueEnemyLaunchTimer);
714 | // Revive the player
715 | player.weaponLevel = 1;
716 | player.revive();
717 | player.health = 100;
718 | shields.render();
719 | score = 0;
720 | scoreText.render();
721 |
722 | // Hide the text
723 | gameOver.visible = false;
724 |
725 | // Reset pacing
726 | greenEnemySpacing = 1000;
727 | blueEnemyLaunched = false;
728 | bossLaunched = false;
729 | }
730 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Phaser.js game demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------