Add 'GamePlay' game state
[ld40.git] / ld40.js
1 var game = new Phaser.Game(800, 600, Phaser.AUTO, '', { preload: function() { this.state.add('GamePlay', GamePlay, true); } });
2 var cursors;
3 var player;
4 var walls;
5 var determinations; // I know it's grammatically incorrect. :P
6 var monsters;
7 var nexttime_monsterspawn;
8 var nexttime_determination;
9
10 const WAIT_KEY = 200;
11 const WAIT_SHOOT = 200;
12 const WAIT_INVINCIBILITY = 800;
13 const WAIT_SOULMODE = 10000;
14 const WAIT_MONSTERSHOOT = 500;
15 const WAIT_MONSTERSPAWN = 20000;
16 const WAIT_DETERMINATION = 4000;
17 const WALL_THICKNESS = 16;
18 const WALL_BORDER = 8;
19 const WALL_BORDERBOTTOM = 48;
20 const HB_THICKNESS = 20;
21 const MAX_HEALTH = 20;
22 const MONSTER_HEALTH = 20;
23 const MODE_DETERMINATION = 0;
24 const MODE_JUSTICE = 1;
25 const SPEED_PLAYER = 150;
26 const SPEED_PPROJECTILE = 500;
27 const SPEED_MPROJECTILE = 200;
28
29 window.addEventListener("keydown", function(e) {
30 // Prevent default browser action for arrows and spacebar
31 if([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
32 e.preventDefault();
33 }
34 }, false);
35
36 function sign(n) {
37 if (n >= 0) { return 1 } else { return -1 };
38 }
39
40 class Player extends Phaser.Text {
41 constructor(x, y) {
42 super(game, x, y, "♥️", { align: 'center', fill: 'red', font: 'Ubuntu Mono', fontSize: 32, fontWeight: 'bold' });
43 this.anchor.setTo(0.5, 0.5);
44 this.maxHealth = MAX_HEALTH;
45 this.health = this.maxHealth;
46 this.love = 1;
47 this.lastmovetime = 0;
48 this.lasttime_shoot = 0;
49 this.lasttime_mode = 0;
50 this.lasttime_damage = 0;
51 this.speed = SPEED_PLAYER;
52 this.switchmode(MODE_DETERMINATION);
53 this.weapon = new JusticeBlaster(game, this);
54 this.weapon.bulletSpeed = SPEED_PPROJECTILE;
55 this.healthBar = new HealthBar(60, game.world.height - (WALL_BORDERBOTTOM / 2));
56 }
57
58 enablePhysics() {
59 game.physics.arcade.enable(this);
60 this.body.bounce.y = 0.2;
61 this.body.bounce.x = 0.2;
62 this.body.collideWorldBounds = true;
63 }
64
65 overlap_determination(determination) {
66 determination.kill();
67 this.heal(2);
68 this.speed += 50;
69 this.switchmode(MODE_JUSTICE);
70 }
71
72 damage(amount) {
73 if ((game.time.now - this.lasttime_damage) > WAIT_INVINCIBILITY)
74 {
75 super.damage(amount);
76 this.lasttime_damage = game.time.now;
77 }
78 }
79
80 kill() {
81 this.health = 0;
82 this.healthBar.update(this.love, this.maxHealth, this.health);
83 super.kill();
84 }
85
86 loveUp() {
87 this.love++
88 this.maxHealth += this.love;
89 this.heal(this.love);
90 }
91
92 control() {
93 if (this.alive)
94 {
95 if (cursors.left.isDown)
96 {
97 player.body.velocity.x = -player.speed;
98 player.lastmovetime = game.time.now;
99 }
100 else if (cursors.right.isDown)
101 {
102 player.body.velocity.x = player.speed;
103 player.lastmovetime = game.time.now;
104 }
105 if (cursors.up.isDown)
106 {
107 player.body.velocity.y = -player.speed;
108 player.lastmovetime = game.time.now;
109 }
110 else if (cursors.down.isDown)
111 {
112 player.body.velocity.y = player.speed;
113 player.lastmovetime = game.time.now;
114 }
115 if ((this.mode == MODE_JUSTICE) && game.input.keyboard.isDown(Phaser.Keyboard.SPACEBAR) && ((game.time.now - this.lasttime_shoot) > WAIT_SHOOT))
116 {
117 this.shoot();
118 }
119 }
120 }
121
122 switchmode(soulmode) {
123 switch(soulmode)
124 {
125 case MODE_DETERMINATION:
126 this.addColor('red', 0);
127 break;
128 case MODE_JUSTICE:
129 this.addColor('yellow', 0);
130 this.lasttime_mode = game.time.now;
131 break;
132 }
133 this.mode = soulmode;
134 }
135
136 shoot() {
137 this.weapon.fire(this, 0, this.y);
138 this.weapon.fire(this, this.x, 0);
139 this.weapon.fire(this, this.x, game.world.height);
140 this.weapon.fire(this, game.world.width, this.y);
141 this.lasttime_shoot = game.time.now;
142 }
143
144 update() {
145 super.update();
146 this.control();
147 this.healthBar.update(this.love, this.maxHealth, this.health);
148 game.physics.arcade.overlap(monsters, this.weapon.bullets, function(m,b) { b.overlap_monster(m); });
149 if ((game.time.now - this.lasttime_mode) > WAIT_SOULMODE) { this.switchmode(MODE_DETERMINATION); }
150 }
151 }
152
153 class Monster extends Phaser.Sprite {
154 constructor(x, y) {
155 super(game, x, y, 'monster');
156 this.anchor.setTo(0.5, 0.5);
157 this.health = MONSTER_HEALTH;
158 this.nexttime_shoot = game.time.now;
159 this.weapon = new JusticeBlaster(game, this);
160 this.weapon.bulletSpeed = SPEED_MPROJECTILE;
161 this.weapon.trackSprite(this);
162 this.shaketween = game.add.tween(this).to({ x: this.x + (sign(Math.random() - 0.5)) * 5 }, 10, Phaser.Easing.Sinusoidal.InOut, false, 0, 8, true);
163 }
164
165 enablePhysics() {
166 game.physics.arcade.enable(this);
167 }
168
169 damage(amount) {
170 super.damage(amount);
171 if (this.alive) { this.shaketween.start(); }
172 }
173
174 kill() {
175 super.kill();
176 player.loveUp();
177 }
178
179 overlap_player(player) {
180 player.damage(10);
181 player.switchmode(MODE_DETERMINATION);
182 }
183
184 update() {
185 super.update();
186 if (this.alive && player.alive && (game.time.now > this.nexttime_shoot))
187 {
188 this.weapon.fireAtSprite(player);
189 this.nexttime_shoot = game.time.now + (WAIT_MONSTERSHOOT / 2) + (Math.random() * WAIT_MONSTERSHOOT);
190 }
191 game.physics.arcade.overlap(player, this.weapon.bullets, function(p,b) { b.overlap_player(p); });
192 }
193 }
194
195 class Determination extends Phaser.Sprite {
196 constructor(x, y) {
197 super(game, x, y, 'determination');
198 this.anchor.setTo(0.5, 0.5);
199 }
200
201 enablePhysics() {
202 game.physics.arcade.enable(this);
203 }
204 }
205
206 class Justice extends Phaser.Bullet {
207 constructor(game, x, y, key, frame) {
208 super(game, x, y, 'projectile');
209 this.anchor.setTo(0.5, 0.5);
210 }
211
212 overlap_monster(entity) {
213 entity.damage(player.love);
214 this.kill();
215 }
216
217 overlap_player(player) {
218 player.damage(player.love);
219 this.kill();
220 }
221 }
222
223 class JusticeBlaster extends Phaser.Weapon {
224 constructor(game, parent) {
225 super(game, parent);
226 this.bulletClass = Justice;
227 this.multiFire = true;
228 this.createBullets(100, null);
229 }
230 }
231
232 class HealthBar {
233 constructor(x, y) {
234 this.mhealth = game.add.tileSprite(x + 10, y, 0, HB_THICKNESS, 'wall');
235 this.ahealth = game.add.tileSprite(x + 10, y, 0, HB_THICKNESS, 'wall');
236 this.mhealth.anchor.setTo(0, 0.5);
237 this.ahealth.anchor.setTo(0, 0.5);
238 this.mhealth.tint = '0xff0000';
239 this.ahealth.tint = '0xffff00';
240 this.text_love = new Phaser.Text(game, x - 50, y + 2, null, { align: 'center', fill: 'white', font: 'Ubuntu Mono', fontSize: 16, fontWeight: 'bold' });
241 this.text_love.anchor.setTo(0, 0.5);
242 game.add.existing(this.text_love);
243 }
244
245 update(love, mhealth, ahealth) {
246 this.mhealth.width = mhealth;
247 this.ahealth.width = ahealth;
248 this.text_love.text = "LV " + love;
249 }
250 }
251
252
253 class GamePlay extends Phaser.State {
254
255 preload() {
256
257 game.load.image('wall', 'wall.png');
258 game.load.image('player', 'player.png');
259 game.load.image('monster', 'monster.png');
260 game.load.image('determination', 'determination.png');
261 game.load.image('projectile', 'projectile.png');
262
263 }
264
265 create() {
266
267 game.world.setBounds(0, 0, 800, 600);
268 game.stage.backgroundColor = '#000000';
269 game.physics.startSystem(Phaser.Physics.ARCADE);
270
271 walls = game.add.group();
272 walls.classType = Phaser.TileSprite;
273 walls.enableBody = true;
274
275 walls.add(game.add.tileSprite(WALL_BORDER, WALL_BORDER, game.world.width - (WALL_BORDER * 2), WALL_THICKNESS, 'wall'));
276 walls.add(game.add.tileSprite(WALL_BORDER, game.world.height - WALL_THICKNESS - WALL_BORDERBOTTOM, game.world.width - (WALL_BORDER * 2), WALL_THICKNESS, 'wall'));
277 walls.add(game.add.tileSprite(WALL_BORDER, WALL_BORDER, WALL_THICKNESS, game.world.height - WALL_BORDER - WALL_BORDERBOTTOM, 'wall'));
278 walls.add(game.add.tileSprite(game.world.width - WALL_THICKNESS - WALL_BORDER, WALL_BORDER, WALL_THICKNESS, game.world.height - WALL_BORDER - WALL_BORDERBOTTOM, 'wall'));
279 walls.children.forEach(function(wall) { wall.body.immovable = true; });
280
281 determinations = game.add.group();
282 nexttime_determination = Math.random() * WAIT_DETERMINATION;
283
284 monsters = game.add.group();
285 nexttime_monsterspawn = WAIT_MONSTERSPAWN / 4;
286
287 player = new Player(game.world.width / 2, game.world.height / 2);
288 player.enablePhysics();
289 game.add.existing(player);
290 game.camera.follow(player);
291 cursors = game.input.keyboard.createCursorKeys();
292
293 }
294
295 update() {
296
297 game.physics.arcade.collide(player, walls);
298 game.physics.arcade.overlap(player, determinations, function(p,d) { p.overlap_determination(d); });
299 game.physics.arcade.overlap(player, monsters, function(p,m) { m.overlap_player(p); });
300
301 if (game.time.now > nexttime_determination)
302 {
303 var determination = new Determination((Math.random() * (game.world.width - ((WALL_THICKNESS + WALL_BORDER) * 4))) + ((WALL_THICKNESS + WALL_BORDER) * 2), (Math.random() * (game.world.height - ((WALL_THICKNESS + WALL_BORDER) * 4) - WALL_BORDERBOTTOM)) + ((WALL_THICKNESS + WALL_BORDER) * 2));
304 determination.enablePhysics();
305 determinations.add(determination);
306 nexttime_determination = game.time.now + (WAIT_DETERMINATION / 2) + (Math.random() * WAIT_DETERMINATION);
307 }
308
309 if (game.time.now > nexttime_monsterspawn)
310 {
311 var monster = new Monster(Math.random() * game.world.width, Math.random() * game.world.height);
312 monster.enablePhysics();
313 monsters.add(monster);
314 var waittime = WAIT_MONSTERSPAWN - ((Math.random() * 2000) * player.love);
315 nexttime_monsterspawn = game.time.now + waittime;
316 console.log('Next monster in ' + waittime);
317 }
318
319 if ((game.time.now - player.lastmovetime) > WAIT_KEY) player.body.velocity.x = player.body.velocity.y = 0;
320
321 }
322 }