Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
epsylon
GitHub Repository: epsylon/ufonet
Path: blob/master/core/js/spaceinvaders.js
1205 views
1
// Constants for the keyboard.
2
var KEY_LEFT = 37;
3
var KEY_RIGHT = 39;
4
var KEY_SPACE = 32;
5
6
// Creates an instance of the Game class.
7
function Game() {
8
9
// Set the initial config.
10
this.config = {
11
bombRate: 0.05,
12
bombMinVelocity: 50,
13
bombMaxVelocity: 50,
14
invaderInitialVelocity: 25,
15
invaderAcceleration: 0,
16
invaderDropDistance: 20,
17
rocketVelocity: 120,
18
rocketMaxFireRate: 2,
19
gameWidth: 400,
20
gameHeight: 300,
21
fps: 50,
22
debugMode: false,
23
invaderRanks: 5,
24
invaderFiles: 10,
25
shipSpeed: 120,
26
levelDifficultyMultiplier: 0.2,
27
pointsPerInvader: 5,
28
limitLevelIncrease: 25
29
};
30
31
// All state is in the variables below.
32
this.lives = 3;
33
this.width = 0;
34
this.height = 0;
35
this.gameBounds = {left: 0, top: 0, right: 0, bottom: 0};
36
this.intervalId = 0;
37
this.score = 0;
38
this.level = 1;
39
40
// The state stack.
41
this.stateStack = [];
42
43
// Input/output
44
this.pressedKeys = {};
45
this.gameCanvas = null;
46
47
// All sounds.
48
this.sounds = null;
49
50
// The previous x position, used for touch.
51
this.previousX = 0;
52
}
53
54
// Initialis the Game with a canvas.
55
Game.prototype.initialise = function(gameCanvas) {
56
57
// Set the game canvas.
58
this.gameCanvas = gameCanvas;
59
60
// Set the game width and height.
61
this.width = gameCanvas.width;
62
this.height = gameCanvas.height;
63
64
// Set the state game bounds.
65
this.gameBounds = {
66
left: gameCanvas.width / 2 - this.config.gameWidth / 2,
67
right: gameCanvas.width / 2 + this.config.gameWidth / 2,
68
top: gameCanvas.height / 2 - this.config.gameHeight / 2,
69
bottom: gameCanvas.height / 2 + this.config.gameHeight / 2,
70
};
71
};
72
73
Game.prototype.moveToState = function(state) {
74
75
// If we are in a state, leave it.
76
if(this.currentState() && this.currentState().leave) {
77
this.currentState().leave(game);
78
this.stateStack.pop();
79
}
80
81
// If there's an enter function for the new state, call it.
82
if(state.enter) {
83
state.enter(game);
84
}
85
86
// Set the current state.
87
this.stateStack.pop();
88
this.stateStack.push(state);
89
};
90
91
// Start the Game.
92
Game.prototype.start = function() {
93
94
// Move into the 'welcome' state.
95
this.moveToState(new WelcomeState());
96
97
// Set the game variables.
98
this.lives = 3;
99
this.config.debugMode = /debug=true/.test(window.location.href);
100
101
// Start the game loop.
102
var game = this;
103
this.intervalId = setInterval(function () { GameLoop(game);}, 1000 / this.config.fps);
104
105
};
106
107
// Returns the current state.
108
Game.prototype.currentState = function() {
109
return this.stateStack.length > 0 ? this.stateStack[this.stateStack.length - 1] : null;
110
};
111
112
// Mutes or unmutes the game.
113
Game.prototype.mute = function(mute) {
114
115
// If we've been told to mute, mute.
116
if(mute === true) {
117
this.sounds.mute = true;
118
} else if (mute === false) {
119
this.sounds.mute = false;
120
} else {
121
// Toggle mute instead...
122
this.sounds.mute = this.sounds.mute ? false : true;
123
}
124
};
125
126
// The main loop.
127
function GameLoop(game) {
128
var currentState = game.currentState();
129
if(currentState) {
130
131
// Delta t is the time to update/draw.
132
var dt = 1 / game.config.fps;
133
134
// Get the drawing context.
135
var ctx = this.gameCanvas.getContext("2d");
136
137
// Update if we have an update function. Also draw
138
// if we have a draw function.
139
if(currentState.update) {
140
currentState.update(game, dt);
141
}
142
if(currentState.draw) {
143
currentState.draw(game, dt, ctx);
144
}
145
}
146
}
147
148
Game.prototype.pushState = function(state) {
149
150
// If there's an enter function for the new state, call it.
151
if(state.enter) {
152
state.enter(game);
153
}
154
// Set the current state.
155
this.stateStack.push(state);
156
};
157
158
Game.prototype.popState = function() {
159
160
// Leave and pop the state.
161
if(this.currentState()) {
162
if(this.currentState().leave) {
163
this.currentState().leave(game);
164
}
165
166
// Set the current state.
167
this.stateStack.pop();
168
}
169
};
170
171
// The stop function stops the game.
172
Game.prototype.stop = function Stop() {
173
clearInterval(this.intervalId);
174
};
175
176
// Inform the game a key is down.
177
Game.prototype.keyDown = function(keyCode) {
178
this.pressedKeys[keyCode] = true;
179
// Delegate to the current state too.
180
if(this.currentState() && this.currentState().keyDown) {
181
this.currentState().keyDown(this, keyCode);
182
}
183
};
184
185
Game.prototype.touchstart = function(s) {
186
if(this.currentState() && this.currentState().keyDown) {
187
this.currentState().keyDown(this, KEY_SPACE);
188
}
189
};
190
191
Game.prototype.touchend = function(s) {
192
delete this.pressedKeys[KEY_RIGHT];
193
delete this.pressedKeys[KEY_LEFT];
194
};
195
196
Game.prototype.touchmove = function(e) {
197
var currentX = e.changedTouches[0].pageX;
198
if (this.previousX > 0) {
199
if (currentX > this.previousX) {
200
delete this.pressedKeys[KEY_LEFT];
201
this.pressedKeys[KEY_RIGHT] = true;
202
} else {
203
delete this.pressedKeys[KEY_RIGHT];
204
this.pressedKeys[KEY_LEFT] = true;
205
}
206
}
207
this.previousX = currentX;
208
};
209
210
// Inform the game a key is up.
211
Game.prototype.keyUp = function(keyCode) {
212
delete this.pressedKeys[keyCode];
213
// Delegate to the current state too.
214
if(this.currentState() && this.currentState().keyUp) {
215
this.currentState().keyUp(this, keyCode);
216
}
217
};
218
219
function WelcomeState() {
220
221
}
222
223
WelcomeState.prototype.enter = function(game) {
224
225
// Create and load the sounds.
226
game.sounds = new Sounds();
227
game.sounds.init();
228
game.sounds.loadSound('shoot', 'sounds/shoot.wav');
229
game.sounds.loadSound('bang', 'sounds/bang.wav');
230
game.sounds.loadSound('explosion', 'sounds/explosion.wav');
231
};
232
233
WelcomeState.prototype.update = function (game, dt) {
234
235
236
};
237
238
WelcomeState.prototype.draw = function(game, dt, ctx) {
239
240
// Clear the background.
241
ctx.clearRect(0, 0, game.width, game.height);
242
243
ctx.font="30px Arial";
244
ctx.fillStyle = '#ffffff';
245
ctx.textBaseline="middle";
246
ctx.textAlign="center";
247
ctx.fillText("Space Invaders", game.width / 2, game.height/2 - 40);
248
ctx.font="16px Arial";
249
250
ctx.fillText("Press 'Space' or touch to start.", game.width / 2, game.height/2);
251
};
252
253
WelcomeState.prototype.keyDown = function(game, keyCode) {
254
if(keyCode == KEY_SPACE) {
255
// Space starts the game.
256
game.level = 1;
257
game.score = 0;
258
game.lives = 3;
259
game.moveToState(new LevelIntroState(game.level));
260
}
261
};
262
263
function GameOverState() {
264
265
}
266
267
GameOverState.prototype.update = function(game, dt) {
268
269
};
270
271
GameOverState.prototype.draw = function(game, dt, ctx) {
272
273
// Clear the background.
274
ctx.clearRect(0, 0, game.width, game.height);
275
276
ctx.font="30px Arial";
277
ctx.fillStyle = '#ffffff';
278
ctx.textBaseline="center";
279
ctx.textAlign="center";
280
ctx.fillText("Game Over!", game.width / 2, game.height/2 - 40);
281
ctx.font="16px Arial";
282
ctx.fillText("You scored " + game.score + " and got to level " + game.level, game.width / 2, game.height/2);
283
ctx.font="16px Arial";
284
ctx.fillText("Press 'Space' to play again.", game.width / 2, game.height/2 + 40);
285
};
286
287
GameOverState.prototype.keyDown = function(game, keyCode) {
288
if(keyCode == KEY_SPACE) {
289
// Space restarts the game.
290
game.lives = 3;
291
game.score = 0;
292
game.level = 1;
293
game.moveToState(new LevelIntroState(1));
294
}
295
};
296
297
// Create a PlayState with the game config and the level you are on.
298
function PlayState(config, level) {
299
this.config = config;
300
this.level = level;
301
302
// Game state.
303
this.invaderCurrentVelocity = 10;
304
this.invaderCurrentDropDistance = 0;
305
this.invadersAreDropping = false;
306
this.lastRocketTime = null;
307
308
// Game entities.
309
this.ship = null;
310
this.invaders = [];
311
this.rockets = [];
312
this.bombs = [];
313
}
314
315
PlayState.prototype.enter = function(game) {
316
317
// Create the ship.
318
this.ship = new Ship(game.width / 2, game.gameBounds.bottom);
319
320
// Setup initial state.
321
this.invaderCurrentVelocity = 10;
322
this.invaderCurrentDropDistance = 0;
323
this.invadersAreDropping = false;
324
325
// Set the ship speed for this level, as well as invader params.
326
var levelMultiplier = this.level * this.config.levelDifficultyMultiplier;
327
var limitLevel = (this.level < this.config.limitLevelIncrease ? this.level : this.config.limitLevelIncrease);
328
this.shipSpeed = this.config.shipSpeed;
329
this.invaderInitialVelocity = this.config.invaderInitialVelocity + 1.5 * (levelMultiplier * this.config.invaderInitialVelocity);
330
this.bombRate = this.config.bombRate + (levelMultiplier * this.config.bombRate);
331
this.bombMinVelocity = this.config.bombMinVelocity + (levelMultiplier * this.config.bombMinVelocity);
332
this.bombMaxVelocity = this.config.bombMaxVelocity + (levelMultiplier * this.config.bombMaxVelocity);
333
this.rocketMaxFireRate = this.config.rocketMaxFireRate + 0.4 * limitLevel;
334
335
// Create the invaders.
336
var ranks = this.config.invaderRanks + 0.1 * limitLevel;
337
var files = this.config.invaderFiles + 0.2 * limitLevel;
338
var invaders = [];
339
for(var rank = 0; rank < ranks; rank++){
340
for(var file = 0; file < files; file++) {
341
invaders.push(new Invader(
342
(game.width / 2) + ((files/2 - file) * 200 / files),
343
(game.gameBounds.top + rank * 20),
344
rank, file, 'Invader'));
345
}
346
}
347
this.invaders = invaders;
348
this.invaderCurrentVelocity = this.invaderInitialVelocity;
349
this.invaderVelocity = {x: -this.invaderInitialVelocity, y:0};
350
this.invaderNextVelocity = null;
351
};
352
353
PlayState.prototype.update = function(game, dt) {
354
355
// If the left or right arrow keys are pressed, move
356
// the ship. Check this on ticks rather than via a keydown
357
// event for smooth movement, otherwise the ship would move
358
// more like a text editor caret.
359
if(game.pressedKeys[KEY_LEFT]) {
360
this.ship.x -= this.shipSpeed * dt;
361
}
362
if(game.pressedKeys[KEY_RIGHT]) {
363
this.ship.x += this.shipSpeed * dt;
364
}
365
if(game.pressedKeys[KEY_SPACE]) {
366
this.fireRocket();
367
}
368
369
// Keep the ship in bounds.
370
if(this.ship.x < game.gameBounds.left) {
371
this.ship.x = game.gameBounds.left;
372
}
373
if(this.ship.x > game.gameBounds.right) {
374
this.ship.x = game.gameBounds.right;
375
}
376
377
// Move each bomb.
378
for(var i=0; i<this.bombs.length; i++) {
379
var bomb = this.bombs[i];
380
bomb.y += dt * bomb.velocity;
381
382
// If the rocket has gone off the screen remove it.
383
if(bomb.y > this.height) {
384
this.bombs.splice(i--, 1);
385
}
386
}
387
388
// Move each rocket.
389
for(i=0; i<this.rockets.length; i++) {
390
var rocket = this.rockets[i];
391
rocket.y -= dt * rocket.velocity;
392
393
// If the rocket has gone off the screen remove it.
394
if(rocket.y < 0) {
395
this.rockets.splice(i--, 1);
396
}
397
}
398
399
// Move the invaders.
400
var hitLeft = false, hitRight = false, hitBottom = false;
401
for(i=0; i<this.invaders.length; i++) {
402
var invader = this.invaders[i];
403
var newx = invader.x + this.invaderVelocity.x * dt;
404
var newy = invader.y + this.invaderVelocity.y * dt;
405
if(hitLeft == false && newx < game.gameBounds.left) {
406
hitLeft = true;
407
}
408
else if(hitRight == false && newx > game.gameBounds.right) {
409
hitRight = true;
410
}
411
else if(hitBottom == false && newy > game.gameBounds.bottom) {
412
hitBottom = true;
413
}
414
415
if(!hitLeft && !hitRight && !hitBottom) {
416
invader.x = newx;
417
invader.y = newy;
418
}
419
}
420
421
// Update invader velocities.
422
if(this.invadersAreDropping) {
423
this.invaderCurrentDropDistance += this.invaderVelocity.y * dt;
424
if(this.invaderCurrentDropDistance >= this.config.invaderDropDistance) {
425
this.invadersAreDropping = false;
426
this.invaderVelocity = this.invaderNextVelocity;
427
this.invaderCurrentDropDistance = 0;
428
}
429
}
430
// If we've hit the left, move down then right.
431
if(hitLeft) {
432
this.invaderCurrentVelocity += this.config.invaderAcceleration;
433
this.invaderVelocity = {x: 0, y:this.invaderCurrentVelocity };
434
this.invadersAreDropping = true;
435
this.invaderNextVelocity = {x: this.invaderCurrentVelocity , y:0};
436
}
437
// If we've hit the right, move down then left.
438
if(hitRight) {
439
this.invaderCurrentVelocity += this.config.invaderAcceleration;
440
this.invaderVelocity = {x: 0, y:this.invaderCurrentVelocity };
441
this.invadersAreDropping = true;
442
this.invaderNextVelocity = {x: -this.invaderCurrentVelocity , y:0};
443
}
444
// If we've hit the bottom, it's game over.
445
if(hitBottom) {
446
game.lives = 0;
447
}
448
449
// Check for rocket/invader collisions.
450
for(i=0; i<this.invaders.length; i++) {
451
var invader = this.invaders[i];
452
var bang = false;
453
454
for(var j=0; j<this.rockets.length; j++){
455
var rocket = this.rockets[j];
456
457
if(rocket.x >= (invader.x - invader.width/2) && rocket.x <= (invader.x + invader.width/2) &&
458
rocket.y >= (invader.y - invader.height/2) && rocket.y <= (invader.y + invader.height/2)) {
459
460
// Remove the rocket, set 'bang' so we don't process
461
// this rocket again.
462
this.rockets.splice(j--, 1);
463
bang = true;
464
game.score += this.config.pointsPerInvader;
465
break;
466
}
467
}
468
if(bang) {
469
this.invaders.splice(i--, 1);
470
game.sounds.playSound('bang');
471
}
472
}
473
474
// Find all of the front rank invaders.
475
var frontRankInvaders = {};
476
for(var i=0; i<this.invaders.length; i++) {
477
var invader = this.invaders[i];
478
// If we have no invader for game file, or the invader
479
// for game file is futher behind, set the front
480
// rank invader to game one.
481
if(!frontRankInvaders[invader.file] || frontRankInvaders[invader.file].rank < invader.rank) {
482
frontRankInvaders[invader.file] = invader;
483
}
484
}
485
486
// Give each front rank invader a chance to drop a bomb.
487
for(var i=0; i<this.config.invaderFiles; i++) {
488
var invader = frontRankInvaders[i];
489
if(!invader) continue;
490
var chance = this.bombRate * dt;
491
if(chance > Math.random()) {
492
// Fire!
493
this.bombs.push(new Bomb(invader.x, invader.y + invader.height / 2,
494
this.bombMinVelocity + Math.random()*(this.bombMaxVelocity - this.bombMinVelocity)));
495
}
496
}
497
498
// Check for bomb/ship collisions.
499
for(var i=0; i<this.bombs.length; i++) {
500
var bomb = this.bombs[i];
501
if(bomb.x >= (this.ship.x - this.ship.width/2) && bomb.x <= (this.ship.x + this.ship.width/2) &&
502
bomb.y >= (this.ship.y - this.ship.height/2) && bomb.y <= (this.ship.y + this.ship.height/2)) {
503
this.bombs.splice(i--, 1);
504
game.lives--;
505
game.sounds.playSound('explosion');
506
}
507
508
}
509
510
// Check for invader/ship collisions.
511
for(var i=0; i<this.invaders.length; i++) {
512
var invader = this.invaders[i];
513
if((invader.x + invader.width/2) > (this.ship.x - this.ship.width/2) &&
514
(invader.x - invader.width/2) < (this.ship.x + this.ship.width/2) &&
515
(invader.y + invader.height/2) > (this.ship.y - this.ship.height/2) &&
516
(invader.y - invader.height/2) < (this.ship.y + this.ship.height/2)) {
517
// Dead by collision!
518
game.lives = 0;
519
game.sounds.playSound('explosion');
520
}
521
}
522
523
// Check for failure
524
if(game.lives <= 0) {
525
game.moveToState(new GameOverState());
526
}
527
528
// Check for victory
529
if(this.invaders.length === 0) {
530
game.score += this.level * 50;
531
game.level += 1;
532
game.moveToState(new LevelIntroState(game.level));
533
}
534
};
535
536
PlayState.prototype.draw = function(game, dt, ctx) {
537
538
// Clear the background.
539
ctx.clearRect(0, 0, game.width, game.height);
540
541
// Draw ship.
542
ctx.fillStyle = '#999999';
543
ctx.fillRect(this.ship.x - (this.ship.width / 2), this.ship.y - (this.ship.height / 2), this.ship.width, this.ship.height);
544
545
// Draw invaders.
546
ctx.fillStyle = '#006600';
547
for(var i=0; i<this.invaders.length; i++) {
548
var invader = this.invaders[i];
549
ctx.fillRect(invader.x - invader.width/2, invader.y - invader.height/2, invader.width, invader.height);
550
}
551
552
// Draw bombs.
553
ctx.fillStyle = '#ff5555';
554
for(var i=0; i<this.bombs.length; i++) {
555
var bomb = this.bombs[i];
556
ctx.fillRect(bomb.x - 2, bomb.y - 2, 4, 4);
557
}
558
559
// Draw rockets.
560
ctx.fillStyle = '#ff0000';
561
for(var i=0; i<this.rockets.length; i++) {
562
var rocket = this.rockets[i];
563
ctx.fillRect(rocket.x, rocket.y - 2, 1, 4);
564
}
565
566
// Draw info.
567
var textYpos = game.gameBounds.bottom + ((game.height - game.gameBounds.bottom) / 2) + 14/2;
568
ctx.font="14px Arial";
569
ctx.fillStyle = '#ffffff';
570
var info = "Lives: " + game.lives;
571
ctx.textAlign = "left";
572
ctx.fillText(info, game.gameBounds.left, textYpos);
573
info = "Score: " + game.score + ", Level: " + game.level;
574
ctx.textAlign = "right";
575
ctx.fillText(info, game.gameBounds.right, textYpos);
576
577
// If we're in debug mode, draw bounds.
578
if(this.config.debugMode) {
579
ctx.strokeStyle = '#ff0000';
580
ctx.strokeRect(0,0,game.width, game.height);
581
ctx.strokeRect(game.gameBounds.left, game.gameBounds.top,
582
game.gameBounds.right - game.gameBounds.left,
583
game.gameBounds.bottom - game.gameBounds.top);
584
}
585
586
};
587
588
PlayState.prototype.keyDown = function(game, keyCode) {
589
590
if(keyCode == KEY_SPACE) {
591
// Fire!
592
this.fireRocket();
593
}
594
if(keyCode == 80) {
595
// Push the pause state.
596
game.pushState(new PauseState());
597
}
598
};
599
600
PlayState.prototype.keyUp = function(game, keyCode) {
601
602
};
603
604
PlayState.prototype.fireRocket = function() {
605
// If we have no last rocket time, or the last rocket time
606
// is older than the max rocket rate, we can fire.
607
if(this.lastRocketTime === null || ((new Date()).valueOf() - this.lastRocketTime) > (1000 / this.rocketMaxFireRate))
608
{
609
// Add a rocket.
610
this.rockets.push(new Rocket(this.ship.x, this.ship.y - 12, this.config.rocketVelocity));
611
this.lastRocketTime = (new Date()).valueOf();
612
613
// Play the 'shoot' sound.
614
game.sounds.playSound('shoot');
615
}
616
};
617
618
function PauseState() {
619
620
}
621
622
PauseState.prototype.keyDown = function(game, keyCode) {
623
624
if(keyCode == 80) {
625
// Pop the pause state.
626
game.popState();
627
}
628
};
629
630
PauseState.prototype.draw = function(game, dt, ctx) {
631
632
// Clear the background.
633
ctx.clearRect(0, 0, game.width, game.height);
634
635
ctx.font="14px Arial";
636
ctx.fillStyle = '#ffffff';
637
ctx.textBaseline="middle";
638
ctx.textAlign="center";
639
ctx.fillText("Paused", game.width / 2, game.height/2);
640
return;
641
};
642
643
/*
644
Level Intro State
645
646
The Level Intro state shows a 'Level X' message and
647
a countdown for the level.
648
*/
649
function LevelIntroState(level) {
650
this.level = level;
651
this.countdownMessage = "3";
652
}
653
654
LevelIntroState.prototype.update = function(game, dt) {
655
656
// Update the countdown.
657
if(this.countdown === undefined) {
658
this.countdown = 3; // countdown from 3 secs
659
}
660
this.countdown -= dt;
661
662
if(this.countdown < 2) {
663
this.countdownMessage = "2";
664
}
665
if(this.countdown < 1) {
666
this.countdownMessage = "1";
667
}
668
if(this.countdown <= 0) {
669
// Move to the next level, popping this state.
670
game.moveToState(new PlayState(game.config, this.level));
671
}
672
673
};
674
675
LevelIntroState.prototype.draw = function(game, dt, ctx) {
676
677
// Clear the background.
678
ctx.clearRect(0, 0, game.width, game.height);
679
680
ctx.font="36px Arial";
681
ctx.fillStyle = '#ffffff';
682
ctx.textBaseline="middle";
683
ctx.textAlign="center";
684
ctx.fillText("Level " + this.level, game.width / 2, game.height/2);
685
ctx.font="24px Arial";
686
ctx.fillText("Ready in " + this.countdownMessage, game.width / 2, game.height/2 + 36);
687
return;
688
};
689
690
691
/*
692
693
Ship
694
695
The ship has a position and that's about it.
696
697
*/
698
function Ship(x, y) {
699
this.x = x;
700
this.y = y;
701
this.width = 20;
702
this.height = 16;
703
}
704
705
/*
706
Rocket
707
708
Fired by the ship, they've got a position, velocity and state.
709
710
*/
711
function Rocket(x, y, velocity) {
712
this.x = x;
713
this.y = y;
714
this.velocity = velocity;
715
}
716
717
/*
718
Bomb
719
720
Dropped by invaders, they've got position, velocity.
721
722
*/
723
function Bomb(x, y, velocity) {
724
this.x = x;
725
this.y = y;
726
this.velocity = velocity;
727
}
728
729
/*
730
Invader
731
732
Invader's have position, type, rank/file and that's about it.
733
*/
734
735
function Invader(x, y, rank, file, type) {
736
this.x = x;
737
this.y = y;
738
this.rank = rank;
739
this.file = file;
740
this.type = type;
741
this.width = 18;
742
this.height = 14;
743
}
744
745
/*
746
Game State
747
748
A Game State is simply an update and draw proc.
749
When a game is in the state, the update and draw procs are
750
called, with a dt value (dt is delta time, i.e. the number)
751
of seconds to update or draw).
752
753
*/
754
function GameState(updateProc, drawProc, keyDown, keyUp, enter, leave) {
755
this.updateProc = updateProc;
756
this.drawProc = drawProc;
757
this.keyDown = keyDown;
758
this.keyUp = keyUp;
759
this.enter = enter;
760
this.leave = leave;
761
}
762
763
/*
764
765
Sounds
766
767
The sounds class is used to asynchronously load sounds and allow
768
them to be played.
769
770
*/
771
function Sounds() {
772
773
// The audio context.
774
this.audioContext = null;
775
776
// The actual set of loaded sounds.
777
this.sounds = {};
778
}
779
780
Sounds.prototype.init = function() {
781
782
// Create the audio context, paying attention to webkit browsers.
783
context = window.AudioContext || window.webkitAudioContext;
784
this.audioContext = new context();
785
this.mute = false;
786
};
787
788
Sounds.prototype.loadSound = function(name, url) {
789
790
// Reference to ourselves for closures.
791
var self = this;
792
793
// Create an entry in the sounds object.
794
this.sounds[name] = null;
795
796
// Create an asynchronous request for the sound.
797
var req = new XMLHttpRequest();
798
req.open('GET', url, true);
799
req.responseType = 'arraybuffer';
800
req.onload = function() {
801
self.audioContext.decodeAudioData(req.response, function(buffer) {
802
self.sounds[name] = {buffer: buffer};
803
});
804
};
805
try {
806
req.send();
807
} catch(e) {
808
console.log("An exception occured getting sound the sound " + name + " this might be " +
809
"because the page is running from the file system, not a webserver.");
810
console.log(e);
811
}
812
};
813
814
Sounds.prototype.playSound = function(name) {
815
816
// If we've not got the sound, don't bother playing it.
817
if(this.sounds[name] === undefined || this.sounds[name] === null || this.mute === true) {
818
return;
819
}
820
821
// Create a sound source, set the buffer, connect to the speakers and
822
// play the sound.
823
var source = this.audioContext.createBufferSource();
824
source.buffer = this.sounds[name].buffer;
825
source.connect(this.audioContext.destination);
826
source.start(0);
827
};
828
829