SpaceShooter game code, content of "shooter" folder
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="keywords" content="spaceshooter, JavaScript, game, html5, canvas" />
<meta name="author" content="Michal Goly" />
<title>Space Shooter</title>
<link rel="stylesheet" href="../css/style.css" />
<script src="../js/jquery-3.4.0.js"></script>
<script src="../js/game.js"></script>
<script src="../js/inputManager.js"></script>
<script src="../js/assetsManager.js"></script>
<script src="../js/collisionManager.js"></script>
<script src="../js/scorePanel.js"></script>
<script src="../js/gameplayManager.js"></script>
<script src="../js/background.js"></script>
<script src="../js/spacecraft.js"></script>
<script src="../js/bullet.js"></script>
<script src="../js/meteor.js"></script>
<script src="../js/enemy.js"></script>
<script src="../js/powerUp.js"></script>
<script>
$(document).ready(function () {
$("#high-scores-page").hide();
$("#about-page").hide();
$("#canvas").hide();
$("#game-over-box").hide();
$("#start-game-button").click(function() {
$("#menu-page").hide();
$("#game-over-box").hide();
$("#canvas").show();
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var game = new Game(canvas, context);
game.newGame();
game.run();
});
$("#high-scores-button").click(function() {
$("#menu-page").hide();
$("#high-scores-page").show();
var scores = JSON.parse(localStorage.getItem("scores"));
if (scores === null) {
scores = [];
}
scores.sort(function(score1, score2) {
return score2.points - score1.points;
});
if (scores.length < 10) {
for (var i = 0; i < scores.length; i++) {
$("#scores-ul").append(
"<li>" + scores[i].name + " : <span class='light-grey'>" +
scores[i].points + "</span></li>");
}
} else {
for (var i = 0; i < 10; i++) {
$("#scores-ul").append(
"<li>" + scores[i].name + " : <span class='light-grey'>" +
scores[i].points + "</span></li>");
}
}
});
$("#about-button").click(function() {
$("#menu-page").hide();
$("#about-page").show();
});
$(".back-button").click(function() {
$("#scores-ul").html('');
$("#high-scores-page").hide();
$("#about-page").hide();
$("#menu-page").show();
});
$("#exit-button").click(function() {
var playerName = $("#name-field").val() === "" ? "unnamed" : $("#name-field").val();
console.log(playerName + " scored " + $("#score-field").html());
var score = {
points: $("#score-field").html(),
name: playerName
};
var scores = JSON.parse(localStorage.getItem("scores"));
if (scores === null) { scores = []; }
scores.push(score);
localStorage.setItem("scores", JSON.stringify(scores));
$("#canvas").hide();
$("#game-over-box").hide();
$("#menu-page").show();
});
});
</script>
</head>
<body>
<div id="wrapper">
<div id="menu-page">
<h1>Space Shooter</h1>
<ul>
<li><a id="start-game-button" href="#">Start Game</a></li>
<li><a id="high-scores-button" href="#">High Scores</a></li>
<li><a id="about-button" href="#">About</a></li>
</ul>
</div>
<div id="high-scores-page">
<h1>High Scores</h1>
<a class="back-button" href="#">Menu</a>
<ul id="scores-ul"> </ul>
</div>
<div id="about-page">
<h1>About</h1>
<a class="back-button" href="#">Menu</a>
<h2>HOW TO PLAY</h2>
<p> Use keyboard arrows ... </p>
<h2 id="credits-h2">CREDITS</h2>
<ul>
....
</ul>
</div>
<canvas id="canvas" width="600" height="700">
Your web browser does not support a canvas
</canvas>
<div id="game-over-box">
<h1>Game Over</h1>
<p id="game-over-p">Score: <span id="score-field"></span></p>
<input type="text"
id="name-field" name="name-field" placeholder="Your name" pattern="^[a-zA-Z]+$" />
<input type="button" id="exit-button" name="exit-button" value="Exit" />
</div>
</div>
</body>
</html>
style.css
@font-face {
font-family: "kenvector_future_thin";
src: url("../assets/Bonus/kenvector_future_thin.ttf");
}
body {
background: url("../assets/Backgrounds/black.png") repeat;
}
#wrapper {
width: 600px;
height: 700px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
border: 5px solid #00f2e5;
}
#menu-page, #high-scores-page, #about-page, #canvas, #game-over-box {
position: absolute;
background: url("../assets/Backgrounds/blue.png") repeat;
color: #00f2e5;
width: 600px;
height: 700px;
text-align: center;
font-family: "kenvector_future_thin", serif;
}
h1 {
font-size: 3.8em;
}
ul {
list-style: none;
padding: 0;
font-size: 2em;
}
#about-page li, #about-page p {
margin: 5px;
}
#about-page ul {
margin: 0;
}
#credits-h2 {
margin-bottom: 17px;
}
.about-li {
padding-top: 2px;
font-size: 0.5em;
}
a {
text-decoration: none;
color: #00f2e5;
}
a:visited {
color: #00f2e5;
}
a:hover {
color: #f2f2f2;
}
.light-grey {
color: #f2f2f2;
}
.about-li a {
color: #f2f2f2;
}
.back-button {
font-size: 1.3em;
}
#exit-button {
background-color: #00f2e5;
color: #f2f2f2;
border: none;
padding: 12px 22px;
font-size: 1.3em;
font-weight: bold;
}
#name-field {
padding-top: 10px;
padding-bottom: 10px;
font-size: 1.3em;
}
#game-over-p {
font-size: 1.3em;
}
assetsManager.js
var AssetsManager = function() {
this.images = [];
this.audios = [];
};
// assets by Kenney Vleugels (www.kenney.nl)
AssetsManager.prototype.loadAll = function() {
this.images["spacecraft"] = new Image();
this.images["spacecraft"].src = "assets/PNG/playerShip2_blue.png";
this.images["spacecraftSmallDamage"] = new Image();
this.images["spacecraftSmallDamage"].src = "assets/PNG/Damage/playerShip2_damage1.png";
this.images["spacecraftMediumDamage"] = new Image();
this.images["spacecraftMediumDamage"].src = "assets/PNG/Damage/playerShip2_damage2.png";
this.images["spacecraftBigDamage"] = new Image();
this.images["spacecraftBigDamage"].src = "assets/PNG/Damage/playerShip2_damage3.png";
this.images["shield1"] = new Image();
this.images["shield1"].src = "assets/PNG/Effects/shield1.png";
this.images["shield2"] = new Image();
this.images["shield2"].src = "assets/PNG/Effects/shield2.png";
this.images["shield3"] = new Image();
this.images["shield3"].src = "assets/PNG/Effects/shield3.png";
this.images["background"] = new Image();
this.images["background"].src = "assets/Backgrounds/blueBig.png";
this.images["laserBlue1"] = new Image();
this.images["laserBlue1"].src = "assets/PNG/Lasers/laserBlue02.png";
this.images["laserBlue2"] = new Image();
this.images["laserBlue2"].src = "assets/PNG/Lasers/laserBlue06.png";
this.images["laserGreen1"] = new Image();
this.images["laserGreen1"].src = "assets/PNG/Lasers/laserGreen04.png";
this.images["laserGreen2"] = new Image();
this.images["laserGreen2"].src = "assets/PNG/Lasers/laserGreen12.png";
this.images["laserRed1"] = new Image();
this.images["laserRed1"].src = "assets/PNG/Lasers/laserRed02.png";
this.images["laserRed2"] = new Image();
this.images["laserRed2"].src = "assets/PNG/Lasers/laserRed06.png";
this.images["meteorBig"] = new Image();
this.images["meteorBig"].src = "assets/PNG/Meteors/meteorBrown_big4.png";
this.images["meteorMedium"] = new Image();
this.images["meteorMedium"].src = "assets/PNG/Meteors/meteorGrey_med1.png";
this.images["meteorTiny"] = new Image();
this.images["meteorTiny"].src = "assets/PNG/Meteors/meteorBrown_tiny1.png";
// power ups
this.images["shieldPower"] = new Image();
this.images["shieldPower"].src = "assets/PNG/Power-ups/powerupYellow_shield.png";
this.images["boltPower"] = new Image();
this.images["boltPower"].src = "assets/PNG/Power-ups/powerupGreen_bolt.png";
// explosions by Ville Seppanen, http://villeseppanen.com
for (var i = 0; i < 21; i++) {
this.images["explosion" + i] = new Image();
this.images["explosion" + i].src = "assets/PNG/Effects/explosion" + i + ".png";
}
this.images["laserBlueExplosion1"] = new Image();
this.images["laserBlueExplosion1"].src = "assets/PNG/Lasers/laserBlue09.png";
this.images["laserBlueExplosion2"] = new Image();
this.images["laserBlueExplosion2"].src = "assets/PNG/Lasers/laserBlue08.png";
this.images["laserGreenExplosion1"] = new Image();
this.images["laserGreenExplosion1"].src = "assets/PNG/Lasers/laserGreen15.png";
this.images["laserGreenExplosion2"] = new Image();
this.images["laserGreenExplosion2"].src = "assets/PNG/Lasers/laserGreen14.png";
this.images["laserRedExplosion1"] = new Image();
this.images["laserRedExplosion1"].src = "assets/PNG/Lasers/laserRed09.png";
this.images["laserRedExplosion2"] = new Image();
this.images["laserRedExplosion2"].src = "assets/PNG/Lasers/laserRed08.png";
// enemies
this.images["enemyBlue"] = new Image();
this.images["enemyBlue"].src = "assets/PNG/Enemies/enemyBlue4.png";
this.images["enemyRed"] = new Image();
this.images["enemyRed"].src = "assets/PNG/Enemies/enemyRed4.png";
this.images["enemyGreen"] = new Image();
this.images["enemyGreen"].src = "assets/PNG/Enemies/enemyGreen3.png";
this.images["enemyBlack"] = new Image();
this.images["enemyBlack"].src = "assets/PNG/Enemies/enemyBlack3.png";
// score panel
this.images["livesRemaining"] = new Image();
this.images["livesRemaining"].src = "assets/PNG/UI/playerLife2_blue.png";
// icons by Gregor Črešnar
this.images["pauseIcon"] = new Image();
this.images["pauseIcon"].src = "assets/PNG/UI/pauseButton.png";
this.images["resumeIcon"] = new Image();
this.images["resumeIcon"].src = "assets/PNG/UI/resumeButton.png";
this.loadSounds();
};
AssetsManager.prototype.loadSounds = function() {
this.audios["shieldUp"] = new Audio("assets/Bonus/sfx_shieldUp.ogg");
this.audios["shieldDown"] = new Audio("assets/Bonus/sfx_shieldDown.ogg");
this.audios["laserPlayer"] = new Audio("assets/Bonus/sfx_laser1.ogg");
this.audios["laserEnemy"] = new Audio("assets/Bonus/sfx_laser2.ogg");
this.audios["gameOver"] = new Audio("assets/Bonus/sfx_lose.ogg");
//Sound (c) by Michel Baradari apollo-music.de
this.audios["explosion"] = new Audio("assets/Bonus/explodemini.wav");
};
background.js
var Background = function(canvas, assetsManager) {
this.canvas = canvas;
this.assetsManager = assetsManager;
this.scrollSpeed = 2;
this.xPosition = 0;
this.yPosition = 0;
};
Background.prototype.update = function() {
this.yPosition += this.scrollSpeed;
if (this.yPosition >= this.canvas.height) {
this.yPosition = 0;
}
};
Background.prototype.draw = function(ctx) {
ctx.drawImage(this.assetsManager.images["background"],
this.xPosition, this.yPosition);
ctx.drawImage(this.assetsManager.images["background"],
this.xPosition, this.yPosition - this.canvas.height);
};
bullet.js
var Bullet = function(xPosition, yPosition, color, assetsManager) {
this.xPosition = xPosition;
this.yPosition = yPosition;
this.color = color;
this.assetsManager = assetsManager;
this.speed = this.getSpeed(this.color);
this.width = 13;
this.height = 37;
this.isExploding = false;
this.explosionTimer = 0;
this.isExploded = false;
this.explosionIndex = 1;
};
Bullet.prototype.update = function(delta) {
if (this.isExploded) { return; }
if (!this.isExploded && !this.isExploding) {
if (this.color === "blue" || this.color === "green") {
this.yPosition -= (this.speed / 10);
} else {
this.yPosition += (this.speed / 10);
}
}
if (this.isExploding) {
this.explosionTimer += delta;
if (this.explosionTimer > 100) {
this.explosionIndex++;
this.explosionTimer = 0;
}
if (this.explosionIndex > 2) {
// end bullets's life :(
this.isExploded = true;
this.isExploding = false;
}
}
};
Bullet.prototype.draw = function(ctx) {
if (!this.isExploded && !this.isExploding) {
if (this.color === "blue") {
ctx.drawImage(this.assetsManager.images["laserBlue1"],
this.xPosition, this.yPosition);
ctx.drawImage(this.assetsManager.images["laserBlue2"],
this.xPosition, this.yPosition);
} else if (this.color === "green") {
ctx.drawImage(this.assetsManager.images["laserGreen1"],
this.xPosition, this.yPosition);
ctx.drawImage(this.assetsManager.images["laserGreen2"],
this.xPosition, this.yPosition);
} else if (this.color === "red") {
ctx.drawImage(this.assetsManager.images["laserRed1"],
this.xPosition, this.yPosition);
ctx.drawImage(this.assetsManager.images["laserRed2"],
this.xPosition, this.yPosition);
} else {
console.error(this.color + " is not a valid color!");
}
} else if (this.isExploding) {
// draw explosion
if (this.color === "blue") {
ctx.drawImage(this.assetsManager.images["laserBlueExplosion" + this.explosionIndex],
this.xPosition - this.width, this.yPosition);
} else if (this.color === "green") {
ctx.drawImage(this.assetsManager.images["laserGreenExplosion" + this.explosionIndex],
this.xPosition - this.width, this.yPosition);
} else if (this.color === "red") {
ctx.drawImage(this.assetsManager.images["laserRedExplosion" + this.explosionIndex],
this.xPosition - this.width, this.yPosition + this.height);
} else {
console.error(this.color + " is not a valid color!");
}
}
};
Bullet.prototype.getSpeed = function(color) {
if (color === "blue" || color === "red") {
return 50;
} else if (color === "green") {
return 100;
} else {
console.error(color + " is not a valid color to determine bullet speed");
return NaN;
}
};
Bullet.prototype.explode = function() {
this.isExploding = true;
};
Bullet.prototype.isOnFire = function() {
return this.isExploded || this.isExploding;
};
collisionManager.js
var CollisionManager = function(game) {
this.game = game;
this.spacecraft = game.spacecraft;
this.meteors = game.meteors;
this.powerUps = game.powerUps;
this.enemies = game.enemies;
this.collisionDelayTimer = 0;
};
CollisionManager.prototype.checkAndResolve = function(delta) {
this.collisionDelayTimer += delta;
if (this.collisionDelayTimer > 10) {
this.checkMeteorsWithMeteors();
this.checkSpacecraftWithMeteors();
this.checkSpacecraftWithEnemies();
this.checkSpacecraftBulletsWithMeteorsEnemies();
this.checkEnemyBulletsWithSpacecraft();
this.collisionDelayTimer = 0;
}
this.checkSpacecraftWithWalls();
this.checkSpacecraftWithPowerUps();
};
CollisionManager.prototype.checkMeteorsWithMeteors = function() {
if (this.meteors.length < 2) { return; }
for (var i = 0; i < this.meteors.length - 1; i++) {
for (var j = i + 1; j < this.meteors.length; j++) {
if (!this.meteors[i].isOnFire() && !this.meteors[j].isOnFire()) {
if (this.circleCircleCollision(this.meteors[i], this.meteors[j])) {
//console.log("meteor collison");
this.resolveElasticCollision(this.meteors[i], this.meteors[j]);
this.meteors[i].updateRotation(this.meteors[j].xCentre);
this.meteors[j].updateRotation(this.meteors[i].xCentre);
}
}
}
}
};
CollisionManager.prototype.checkSpacecraftWithWalls = function() {
if (this.spacecraft.xPosition < 5) {
//console.log("LEFT");
this.spacecraft.xVelocity = 0;
this.spacecraft.isLeftWall = true;
this.spacecraft.xPosition = 5;
}
if (this.spacecraft.xPosition + this.spacecraft.width + 5 > this.game.canvas.width) {
//console.log("RIGHT");
this.spacecraft.xVelocity = 0;
this.spacecraft.isRightWall = true;
this.spacecraft.xPosition = this.game.canvas.width - this.spacecraft.width - 5;
}
if (this.spacecraft.yPosition < 5) {
//console.log("TOP");
this.spacecraft.yVelocity = 0;
this.spacecraft.isUpWall = true;
this.spacecraft.yPosition = 5;
}
if (this.spacecraft.yPosition + this.spacecraft.height + 5 > this.game.canvas.height) {
//console.log("BOTTOM");
this.spacecraft.yVelocity = 0;
this.spacecraft.isDownWall = true;
this.spacecraft.yPosition = this.game.canvas.height - this.spacecraft.height - 5;
}
};
CollisionManager.prototype.checkSpacecraftWithEnemies = function() {
for (var i = 0; i < this.enemies.length; i++) {
if (!this.enemies[i].isOnFire() && this.rectRectCollision(this.spacecraft, this.enemies[i])) {
if (this.circleRectCollision(this.spacecraft, this.enemies[i])) {
//console.log("Spacecraft - enemy collision");
this.resolveElasticCollision(this.spacecraft, this.enemies[i]);
// blow up the enemy
this.enemies[i].explode();
if (!this.spacecraft.isShieldUp) {
this.spacecraft.livesRemaining--;
} else {
this.spacecraft.score += 20;
}
}
}
}
};
CollisionManager.prototype.checkSpacecraftWithPowerUps = function() {
// combine rectangular and circular collision detections
for (var i = 0; i < this.powerUps.length; i++) {
// rectangle-rectangle collision
if (this.rectRectCollision(this.spacecraft, this.powerUps[i])) {
if (this.circleRectCollision(this.spacecraft, this.powerUps[i])) {
//console.log("SPACECRAFT-POWERUP COLLISION");
if (this.powerUps[i].type === "boltPower") {
this.spacecraft.boltPowerUp();
} else if (this.powerUps[i].type === "shieldPower") {
this.spacecraft.shieldUp();
} else {
console.error(this.powerUps[i].type + " is not a proper powerUp");
}
this.powerUps[i].isPickedUp = true;
}
}
}
// clean up picked up power ups
for (var i = 0; i < this.powerUps.length; i++) {
if (this.powerUps[i].isPickedUp) {
this.powerUps.splice(i, 1);
i--;
}
}
};
CollisionManager.prototype.rectRectCollision = function(rect1, rect2) {
return rect1.xPosition < rect2.xPosition + rect2.width
&& rect1.xPosition + rect1.width > rect2.xPosition
&& rect1.yPosition < rect2.yPosition + rect2.height
&& rect1.height + rect1.yPosition > rect2.yPosition;
};
CollisionManager.prototype.circleCircleCollision = function(circle1, circle2) {
var distanceX = circle1.xCentre - circle2.xCentre;
var distanceY = circle1.yCentre - circle2.yCentre;
var distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
return circle1.radius + circle2.radius > distance;
};
// http://stackoverflow.com/questions/21089959/detecting-collision-of-rectangle-with-circle
CollisionManager.prototype.circleRectCollision = function(circle, rect) {
var distanceX = Math.abs(circle.xCentre - rect.xPosition - rect.width / 2);
var distanceY = Math.abs(circle.yCentre - rect.yPosition - rect.height / 2);
if (distanceX > (rect.width / 2 + circle.radius)) {
return false;
}
if (distanceY > (rect.height / 2 + circle.radius)) {
return false;
}
if (distanceX <= (rect.width / 2)) {
return true;
}
if (distanceY <= (rect.height / 2)) {
return true;
}
var dx = distanceX - rect.width / 2;
var dy = distanceY - rect.height / 2;
return dx * dx + dy * dy <= (circle.radius * circle.radius);
};
CollisionManager.prototype.checkSpacecraftWithMeteors = function() {
for (var i = 0; i < this.meteors.length; i++) {
if (!this.meteors[i].isOnFire() && this.circleCircleCollision(this.spacecraft, this.meteors[i])) {
if (this.circleRectCollision(this.meteors[i], this.spacecraft)) {
this.resolveElasticCollision(this.spacecraft, this.meteors[i]);
// blow the meteor up
this.meteors[i].explode();
if (!this.spacecraft.isShieldUp) {
this.spacecraft.livesRemaining--;
} else {
this.spacecraft.score += 10;
}
}
}
}
};
CollisionManager.prototype.checkSpacecraftBulletsWithMeteorsEnemies = function() {
for (var i = 0; i < this.spacecraft.bullets.length; i++) {
// ignore bullets that have already hit a target
if (this.spacecraft.bullets[i].isOnFire()) {
continue;
}
// check bullets/meteors
for (var j = 0; j < this.meteors.length; j++) {
if (this.meteors[j].isOnFire()) {
continue;
}
if (this.circleRectCollision(this.meteors[j], this.spacecraft.bullets[i])) {
//console.log("Bullet - Meteor Collision");
this.meteors[j].explode();
this.spacecraft.bullets[i].explode();
this.spacecraft.score += 10;
}
}
// check bullets/enemies
for (var k = 0; k < this.enemies.length; k++) {
if (this.enemies[k].isOnFire()) {
continue;
}
if (this.rectRectCollision(this.enemies[k], this.spacecraft.bullets[i])) {
//console.log("Bullet - Enemy collision");
this.enemies[k].explode();
this.spacecraft.bullets[i].explode();
this.spacecraft.score += 20;
}
}
}
};
CollisionManager.prototype.checkEnemyBulletsWithSpacecraft = function() {
for (var i = 0; i < this.enemies.length; i++) {
if (this.enemies[i].type === "enemyBlue" || this.enemies[i].type === "enemyRed") {
// blue and red enemies have no bullets
continue;
}
for (var j = 0; j < this.enemies[i].bullets.length; j++) {
if (this.enemies[i].bullets[j].isOnFire()) {
continue;
}
if (this.rectRectCollision(this.spacecraft, this.enemies[i].bullets[j])) {
if (this.circleRectCollision(this.spacecraft, this.enemies[i].bullets[j])) {
this.enemies[i].bullets[j].explode();
if (!this.spacecraft.isShieldUp) {
this.spacecraft.livesRemaining--;
}
}
}
}
}
};
CollisionManager.prototype.resolveElasticCollision = function(body1, body2) {
var tempVelX = body1.xVelocity;
var tempVelY = body1.yVelocity;
var totalMass = body1.mass + body2.mass;
// velocity after elastic collision, floor used to simplify the implementation
body1.xVelocity = Math.floor((body1.xVelocity * (body1.mass - body2.mass)
+ 2 * body2.mass * body2.xVelocity) / totalMass);
body1.yVelocity = Math.floor((body1.yVelocity * (body1.mass - body2.mass)
+ 2 * body2.mass * body2.yVelocity) / totalMass);
body2.xVelocity = Math.floor((body2.xVelocity * (body2.mass - body1.mass)
+ 2 * body1.mass * tempVelX) / totalMass);
body2.yVelocity = Math.floor((body2.yVelocity * (body2.mass - body1.mass)
+ 2 * body1.mass * tempVelY) / totalMass);
};
enemy.js
var Enemy = function(xPosition, yPosition, type, assetsManager, spacecraft) {
this.xPosition = xPosition;
this.yPosition = yPosition;
this.type = type;
this.assetsManager = assetsManager;
this.spacecraft = spacecraft;
this.width = 55;
this.height = 56;
this.xVelocity = 0;
this.yVelocity = 0;
this.mass = 200;
this.radius = this.width / 2;
this.xCentre = this.xPosition + this.radius;
this.yCentre = this.yPosition + this.radius;
if (this.type === "enemyBlue" || this.type === "enemyGreen") {
this.accelerateFactor = 1;
this.maxVelocity = 7;
} else {
this.accelerateFactor = 1;
this.maxVelocity = 20;
}
this.behaviourStarted = false;
// for blue and red behaviour (also last bit of black behaviour)
if (this.type === "enemyBlue" || this.type === "enemyRed" || this.type === "enemyBlack") {
// random value <100, 600>
this.initialDescentDistance = Math.floor(Math.random() * (600 - 100 + 1)) + 100;
}
// green and black
if (this.type === "enemyGreen" || this.type === "enemyBlack") {
this.bulletDelayTimer = 0;
this.bullets = [];
this.startFire = false;
this.bulletCleanUpDelayTimer = 0;
}
// black behaviour
this.flewToPlayer = false;
this.flewFromPlayer = false;
this.flyingToLeftWall = false;
this.flyingToRightWall = false;
this.goDown = false;
this.goUp = false;
this.goRight = false;
this.goLeft = false;
this.isExploding = false;
this.explosionTimer = 0;
this.isExploded = false;
this.explosionIndex = 0;
};
Enemy.prototype.update = function(delta) {
if (this.isExploded && (this.type === "enemyBlue" || this.type === "enemyRed")) {
return;
} else if (this.isExploded && this.bullets.length !== 0) {
// make sure bullets move even after enemy blew up
for (var i = 0; i < this.bullets.length; i++) {
this.bullets[i].update(delta);
}
// run bullet clean up code
this.bulletsCleanUp(delta);
return;
} else if (this.isExploded) {
return;
}
this.doBehaviour();
this.slowDown();
this.updateDirection();
this.yPosition += (this.yVelocity / 10);
this.xPosition += (this.xVelocity / 10);
this.radius = this.width / 2;
this.xCentre = this.xPosition + this.radius;
this.yCentre = this.yPosition + this.radius;
if ((this.type === "enemyGreen" || this.type === "enemyBlack") && this.startFire) {
this.bulletDelayTimer += delta;
if (this.bulletDelayTimer > 1000) {
this.fire();
this.bulletDelayTimer = 0;
}
for (var i = 0; i < this.bullets.length; i++) {
this.bullets[i].update(delta);
}
this.bulletsCleanUp(delta);
}
if (this.isExploding) {
this.explosionTimer += delta;
if (this.explosionTimer > 50) {
this.explosionIndex++;
this.explosionTimer = 0;
}
if (this.explosionIndex > 20) {
// end enemy's life :)
this.isExploded = true;
this.isExploding = false;
}
}
};
Enemy.prototype.draw = function(ctx) {
if (!this.isExploded && !this.isExploding) {
ctx.drawImage(this.assetsManager.images[this.type], this.xPosition, this.yPosition,
this.width, this.height);
} else if (this.isExploding) {
ctx.drawImage(this.assetsManager.images["explosion" + this.explosionIndex],
this.xCentre - this.radius, this.yCentre - this.radius, this.radius * 2,
this.radius * 2);
}
if (this.type === "enemyGreen" || this.type === "enemyBlack") {
for (var i = 0; i < this.bullets.length; i++) {
this.bullets[i].draw(ctx);
}
}
};
Enemy.prototype.updateDirection = function() {
// start moving up
if (this.goUp && this.yVelocity === 0) {
this.yVelocity -= this.accelerateFactor;
}
// accelerate further up
if (this.goUp && (Math.abs(this.yVelocity) < this.maxVelocity)) {
this.yVelocity -= this.accelerateFactor;
}
// start moving down
if (this.goDown && this.yVelocity === 0) {
this.yVelocity += this.accelerateFactor;
}
// accelerate further down
if (this.goDown && (Math.abs(this.yVelocity) < this.maxVelocity)) {
this.yVelocity += this.accelerateFactor;
}
// start moving right
if (this.goRight && this.xVelocity === 0) {
this.xVelocity += this.accelerateFactor;
}
// accelerate further right
if (this.goRight && (Math.abs(this.xVelocity) < this.maxVelocity)) {
this.xVelocity += this.accelerateFactor;
}
// start moving left
if (this.goLeft && this.xVelocity === 0) {
this.xVelocity -= this.accelerateFactor;
}
// accelerate further left
if (this.goLeft && (Math.abs(this.xVelocity) < this.maxVelocity)) {
this.xVelocity -= this.accelerateFactor;
}
};
Enemy.prototype.slowDown = function() {
// slow down when going up
if (this.yVelocity < 0 && this.goDown) {
this.yVelocity += this.accelerateFactor;
}
// slow down when going down
if (this.yVelocity > 0 && this.goUp) {
this.yVelocity -= this.accelerateFactor;
}
// slow down when going right
if (this.xVelocity > 0 && this.goLeft) {
this.xVelocity -= this.accelerateFactor;
}
// slow down when going left
if (this.xVelocity < 0 && this.goRight) {
this.xVelocity += this.accelerateFactor;
}
};
Enemy.prototype.doBehaviour = function() {
if (this.type === "enemyBlue") {
this.doBlueBehaviour();
} else if (this.type === "enemyRed") {
this.doRedBehaviour();
} else if (this.type === "enemyGreen") {
this.doGreenBehaviour();
} else if (this.type === "enemyBlack") {
this.doBlackBehaviour();
}
};
// slowly fly into the spacecraft, don't shoot
Enemy.prototype.doBlueBehaviour = function() {
if (!this.behaviourStarted) {
this.goDown = true;
this.behaviourStarted = true;
} else {
if (this.yPosition < this.initialDescentDistance) {
return;
}
if (this.xCentre < this.spacecraft.xCentre) {
this.goLeft = false;
this.goRight = true;
} else if (this.xCentre > this.spacecraft.xCentre) {
this.goLeft = true;
this.goRight = false;
} else {
this.goLeft = false;
this.goRight = false;
}
}
};
// like blue but quicker
Enemy.prototype.doRedBehaviour = function() {
if (!this.behaviourStarted) {
this.goDown = true;
this.behaviourStarted = true;
} else {
if (this.yPosition < this.initialDescentDistance) {
return;
}
if (this.xCentre < this.spacecraft.xCentre) {
this.goLeft = false;
this.goRight = true;
} else if (this.xCentre > this.spacecraft.xCentre) {
this.goLeft = true;
this.goRight = false;
} else {
this.goLeft = false;
this.goRight = false;
}
}
};
// fly slowly straight down the screen while shooting
Enemy.prototype.doGreenBehaviour = function() {
if (!this.behaviourStarted) {
this.goDown = true;
this.behaviourStarted = true;
} else if (this.yPosition > 0) {
// stop shooting after leaving the screen
if (this.yPosition >= 700) {
this.startFire = false;
} else {
this.startFire = true;
}
}
};
// fly down, move in the direction of the player, fly into the direction of the
// wall further away, finally switch to the red behaviour while shooting constantly
Enemy.prototype.doBlackBehaviour = function() {
if (!this.behaviourStarted){
this.goDown = true;
this.behaviourStarted = true;
} else {
if (this.yPosition > 0) {
this.startFire = true;
}
if (this.yPosition > 10 && !this.flewToPlayer && !this.flewFromPlayer) {
this.goDown = false;
this.yVelocity = 0;
if (this.xCentre > this.spacecraft.xPosition && this.xCentre
< this.spacecraft.xPosition + this.spacecraft.width) {
this.flewToPlayer = true;
} else if (this.xCentre < this.spacecraft.xCentre) {
this.goLeft = false;
this.goRight = true;
} else {
this.goLeft = true;
this.goRight = false;
}
} else if (this.flewToPlayer && !this.flewFromPlayer) {
if (this.xCentre > 300 && !this.flyingToRightWall) {
this.goLeft = true;
this.goRight = false;
this.flyingToLeftWall = true;
} else if (!this.flyingToLeftWall){
this.goLeft = false;
this.goRight = true;
this.flyingToRightWall = true;
}
// check if next to wall
if (this.xPosition < 50 || this.xPosition > 500) {
this.goDown = true;
this.goLeft = false;
this.goRight = false;
this.xVelocity = 0;
this.flewFromPlayer = true;
}
} else if (this.flewFromPlayer && this.flewToPlayer) {
// final bit, do red behaviour
if (this.yPosition < this.initialDescentDistance) {
return;
}
if (this.xCentre < this.spacecraft.xCentre) {
this.goLeft = false;
this.goRight = true;
} else if (this.xCentre > this.spacecraft.xCentre) {
this.goLeft = true;
this.goRight = false;
} else {
this.goLeft = false;
this.goRight = false;
}
// stop shooting after leaving the screen
if (this.yPosition >= 700) {
this.startFire = false;
}
}
}
};
Enemy.prototype.bulletsCleanUp = function(delta) {
// every 10 seconds remove bullets that are off the screen
this.bulletCleanUpDelayTimer += delta;
if (this.bulletCleanUpDelayTimer > 10000) {
//console.log("Before: " + this.bullets.length);
for (var i = 0; i < this.bullets.length; i++) {
if (this.bullets[i].yPosition > 1000 || this.bullets[i].isExploded) {
this.bullets.splice(i, 1);
i--;
}
}
//console.log("After: " + this.bullets.length);
this.bulletCleanUpDelayTimer = 0;
}
};
Enemy.prototype.fire = function() {
this.bullets.push(new Bullet(this.xPosition + (this.width / 2) - (14 / 2),
this.yPosition + this.height / 2, "red", this.assetsManager));
this.assetsManager.audios["laserEnemy"].play();
this.assetsManager.audios["laserEnemy"].currentTime = 0;
};
Enemy.prototype.explode = function() {
this.isExploding = true;
this.startFire = false;
this.assetsManager.audios["explosion"].play();
this.assetsManager.audios["explosion"].currentTime = 0;
};
Enemy.prototype.isOnFire = function() {
return this.isExploded || this.isExploding;
};
game.js
var Game = function(canvas, context) {
this.canvas = canvas;
this.context = context;
this.assetsManager = new AssetsManager();
this.assetsManager.loadAll();
this.inputManager = new InputManager();
// game loop variables
this.fps = 60;
this.interval = 1000 / this.fps;
this.lastTime = new Date().getTime();
this.currentTime = 0;
this.delta = 0;
this.frameId = 0;
this.isPaused = false;
};
Game.prototype.newGame = function() {
this.background = new Background(this.canvas, this.assetsManager);
this.spacecraft = new Spacecraft(this.canvas, this.inputManager, this.assetsManager);
this.meteors = [];
this.powerUps = [];
this.enemies = [];
this.collisionManager = new CollisionManager(this);
this.scorePanel = new ScorePanel(this);
this.gameplayManager = new GameplayManager(this);
this.inputManager.registerKeyListener();
this.inputManager.registerMouseListener(this);
};
// https://coderwall.com/p/iygcpa/gameloop-the-correct-way
Game.prototype.run = function() {
this.frameId = window.requestAnimationFrame(this.run.bind(this));
this.currentTime = new Date().getTime();
this.delta = this.currentTime - this.lastTime;
if (this.delta > this.interval) {
if (!this.isPaused) {
this.update(this.delta);
this.collisionManager.checkAndResolve(this.delta);
}
if (this.spacecraft.livesRemaining < 0) {
this.gameOver();
}
this.render();
this.lastTime = this.currentTime - (this.delta % this.interval);
}
};
Game.prototype.update = function(delta) {
this.background.update();
this.spacecraft.update(delta);
this.gameplayManager.update(delta);
for (var i = 0; i < this.meteors.length; i++) {
this.meteors[i].update(delta);
}
for (var i = 0; i < this.enemies.length; i++) {
this.enemies[i].update(delta);
}
};
Game.prototype.render = function() {
this.background.draw(this.context);
this.spacecraft.draw(this.context);
for (var i = 0; i < this.meteors.length; i++) {
this.meteors[i].draw(this.context);
}
for (var i = 0; i < this.powerUps.length; i++) {
this.powerUps[i].draw(this.context);
}
for (var i = 0; i < this.enemies.length; i++) {
this.enemies[i].draw(this.context);
}
this.scorePanel.draw(this.context);
};
Game.prototype.gameOver = function() {
window.cancelAnimationFrame(this.frameId);
this.assetsManager.audios["gameOver"].play();
this.assetsManager.audios["gameOver"].currentTime = 0;
jQuery("#score-field").html(this.spacecraft.score);
jQuery("#game-over-box").show();
// clear the screen to avoid a lag at the start of the next game
this.newGame();
};
gameplayManager.js
var GameplayManager = function(game) {
this.canvas = game.canvas;
this.assetsManager = game.assetsManager;
this.meteors = game.meteors;
this.enemies = game.enemies;
this.powerUps = game.powerUps;
this.spacecraft = game.spacecraft;
this.enemiesSpawnDelay = 5000;
this.enemiesSpawnDelayMin = 1000; // max difficulty
this.enemiesSpawnDelayTimer = 0;
this.meteorsSpawnDelay = 3000;
this.meteorsSpawnDelayMin = 1000; // max difficulty
this.meteorsSpawnDelayTimer = 0;
this.powerUpsSpawnDelay = 20000;
this.powerUpsSpawnDelayTimer = 0;
this.cleanUpDelay = 15000;
this.cleanUpDelayTimer = 0;
this.difficultyDelay = 30000;
this.difficultyDelayTimer = 0;
};
GameplayManager.prototype.update = function(delta) {
this.spawnMeteors(delta);
this.spawnEnemies(delta);
this.spawnPowerUps(delta);
// clean up every 15 seconds
this.cleanUpDelayTimer += delta;
if (this.cleanUpDelayTimer > this.cleanUpDelay) {
for (var i = 0; i < this.enemies.length; i++) {
if (this.isOffCanvas(this.enemies[i])) {
this.enemies.splice(i, 1);
i--;
}
}
for (var i = 0; i < this.meteors.length; i++) {
if (this.meteors[i].isExploded || this.isOffCanvas(this.meteors[i])) {
this.meteors.splice(i, 1);
i--;
}
}
this.cleanUpDelayTimer = 0;
}
this.increaseDifficulty(delta);
};
GameplayManager.prototype.spawnMeteors = function(delta) {
this.meteorsSpawnDelayTimer += delta;
if (this.meteorsSpawnDelayTimer > this.meteorsSpawnDelay) {
// with this implementation meteors can end up spawned on top of each other
// which makes their behaviour unpredictable (game's more fun this way)
this.meteors.push(new Meteor(this.getMeteorXCentre(), this.getMeteorYCentre(),
this.getMeteorType(), this.assetsManager));
this.meteorsSpawnDelayTimer = 0;
}
};
GameplayManager.prototype.spawnEnemies = function(delta) {
this.enemiesSpawnDelayTimer += delta;
if (this.enemiesSpawnDelayTimer > this.enemiesSpawnDelay) {
this.enemies.push(new Enemy(this.getEnemyXPosition(), this.getEnemyYPosition(),
this.getEnemyType(), this.assetsManager, this.spacecraft));
this.enemiesSpawnDelayTimer = 0;
}
};
GameplayManager.prototype.spawnPowerUps = function(delta) {
this.powerUpsSpawnDelayTimer += delta;
if (this.powerUpsSpawnDelayTimer > this.powerUpsSpawnDelay) {
this.powerUps.push(new PowerUp(this.getPowerUpXPosition(),
this.getPowerUpYPosition(), this.getPowerUpType(), this.assetsManager));
this.powerUpsSpawnDelayTimer = 0;
}
};
GameplayManager.prototype.increaseDifficulty = function(delta) {
this.difficultyDelayTimer += delta;
if (this.difficultyDelayTimer > this.difficultyDelay) {
if (this.meteorsSpawnDelay > this.meteorsSpawnDelayMin) {
this.meteorsSpawnDelay -= 200;
}
if (this.enemiesSpawnDelay > this.enemiesSpawnDelayMin) {
this.enemiesSpawnDelay -= 200;
}
this.difficultyDelayTimer = 0;
}
};
// get random integer between 100 and canvas - 100
GameplayManager.prototype.getMeteorXCentre = function() {
return this.getIntegerBetween(100, this.canvas.width - 100);
};
// get random integer between -150 and -50
GameplayManager.prototype.getMeteorYCentre = function() {
return this.getIntegerBetween(-60, -50);
};
GameplayManager.prototype.getMeteorType = function() {
var choice = this.getIntegerBetween(0, 2);
if (choice === 0) {
return "big"
} else if (choice === 1) {
return "medium";
} else {
return "tiny";
}
};
// get random integer between 100 and canvas - 100
GameplayManager.prototype.getEnemyXPosition = function() {
return this.getIntegerBetween(100, this.canvas.width - 100);
};
// get random integer between -150 and -50
GameplayManager.prototype.getEnemyYPosition = function() {
return this.getIntegerBetween(-60, -50);
};
GameplayManager.prototype.getEnemyType = function() {
var choice = this.getIntegerBetween(0, 3);
if (choice === 0) {
return "enemyBlue"
} else if (choice === 1) {
return "enemyRed";
} else if (choice === 2) {
return "enemyGreen";
} else {
return "enemyBlack";
}
};
GameplayManager.prototype.getPowerUpXPosition = function() {
return this.getIntegerBetween(100, this.canvas.width - 100);
};
GameplayManager.prototype.getPowerUpYPosition = function() {
return this.getIntegerBetween(100, this.canvas.height - 100);
};
GameplayManager.prototype.getPowerUpType = function() {
var choice = this.getIntegerBetween(0, 1);
return choice === 0 ? "shieldPower" : "boltPower";
};
GameplayManager.prototype.isOffCanvas = function(entity) {
return entity.xPosition < -300 || entity.xPosition > this.canvas.width + 300
|| entity.yPosition < -300 || entity.yPosition > this.canvas.height + 300;
};
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
GameplayManager.prototype.getIntegerBetween = function(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
inputManager.js
var InputManager = function() {
this.keys = [];
};
InputManager.prototype.registerKeyListener = function() {
document.addEventListener("keydown", function(event) {
this.keys[event.which] = true;
}.bind(this), false);
document.addEventListener("keyup", function(event) {
this.keys[event.which] = false;
}.bind(this), false);
};
// pause, resume game
InputManager.prototype.registerMouseListener = function(game) {
game.canvas.addEventListener("click", function(event) {
var x = event.clientX - game.canvas.getBoundingClientRect().left;
var y = event.clientY - game.canvas.getBoundingClientRect().top;
if (x > 5 && x < 30 && y > 669 && y < 694) {
game.isPaused = game.isPaused ? false : true;
}
}.bind(this), false);
};
meteor.js
var Meteor = function(xCentre, yCentre, type, assetsManager) {
this.xCentre = xCentre;
this.yCentre = yCentre;
this.type = type;
this.assetsManager = assetsManager;
if (this.type === "big") {
this.radius = 48;
this.mass = 1000;
} else if (this.type === "medium") {
this.radius = 21;
this.mass = 500;
} else if (this.type === "tiny") {
this.radius = 9;
this.mass = 100;
} else {
console.error(this.type + " is not a valid type of a meteor!");
}
this.xPosition = this.xCentre - this.radius;
this.yPosition = this.yCentre - this.radius;
// xVelocity is later picked at random
this.xVelocity = 0;
this.yVelocity = 10;
this.rotationAngle = 0;
this.rotationDelayCounter = 0;
this.initialiseRoute();
this.isExploding = false;
this.explosionTimer = 0;
this.isExploded = false;
this.explosionIndex = 0;
};
Meteor.prototype.initialiseRoute = function() {
// get random number between 0 to 4 inclusive
var type = Math.floor(Math.random() * 5);
//var type = 2;
//console.log("Meteor route: " + type);
switch (type) {
case 0:
this.isRotatingClockwise = false;
this.xVelocity = -2;
break;
case 1:
this.isRotatingClockwise = false;
this.xVelocity = -5;
break;
case 2:
this.isRotatingClockwise = true;
break;
case 3:
this.isRotatingClockwise = true;
this.xVelocity = 2;
break;
case 4:
this.isRotatingClockwise = true;
this.xVelocity = 5;
break;
default:
console.error(type + " is not a valid type of route of a meteor!");
break;
}
// random yVelocity between 8 and 12
this.yVelocity = Math.floor(Math.random() * (12 - 8 + 1)) + 8;
};
Meteor.prototype.update = function(delta) {
if (this.isExploded) {
return;
}
this.yPosition += (this.yVelocity / 10);
this.xPosition += (this.xVelocity / 10);
this.xCentre = this.xPosition + this.radius;
this.yCentre = this.yPosition + this.radius;
// rotation of the meteor
this.rotationDelayCounter += delta;
if (this.rotationDelayCounter > 25) {
if (this.isRotatingClockwise) {
this.rotationAngle += 1;
} else {
this.rotationAngle -= 1;
}
this.rotationDelayCounter = 0;
}
if (this.isExploding) {
this.explosionTimer += delta;
if (this.explosionTimer > 50) {
this.explosionIndex++;
this.explosionTimer = 0;
}
if (this.explosionIndex > 20) {
// end meteor's life :(
this.isExploded = true;
this.isExploding = false;
}
}
};
Meteor.prototype.isGoingDown = function() {
return this.yVelocity > 0;
};
Meteor.prototype.isGoingUp = function() {
return this.yVelocity < 0;
};
Meteor.prototype.isGoingRight = function() {
return this.xVelocity > 0;
};
Meteor.prototype.isGoingLeft = function() {
return this.xVelocity < 0;
};
Meteor.prototype.draw = function(ctx) {
if (!this.isExploded && !this.isExploding) {
if (this.type === "big") {
this.drawRotatedImage(ctx, this.assetsManager.images["meteorBig"],
this.xCentre, this.yCentre, this.rotationAngle);
} else if (this.type === "medium") {
this.drawRotatedImage(ctx, this.assetsManager.images["meteorMedium"],
this.xCentre, this.yCentre, this.rotationAngle);
} else {
this.drawRotatedImage(ctx, this.assetsManager.images["meteorTiny"],
this.xCentre, this.yCentre, this.rotationAngle);
}
// DEBUG
//ctx.fillStyle = "#fff";
//ctx.beginPath();
//ctx.arc(this.xCentre, this.yCentre, this.radius, 0, 2 * Math.PI);
//ctx.stroke();
} else if (this.isExploding) {
ctx.drawImage(this.assetsManager.images["explosion" + this.explosionIndex],
this.xCentre - this.radius, this.yCentre - this.radius, this.radius * 2,
this.radius * 2);
}
};
// Inspired by the example here by Seb Lee-Delisle
// http://creativejs.com/2012/01/day-10-drawing-rotated-images-into-canvas/
Meteor.prototype.drawRotatedImage = function(ctx, image, xPosition, yPosition, angle) {
ctx.save();
ctx.translate(xPosition, yPosition);
ctx.rotate(angle * Math.PI / 180);
ctx.drawImage(image, -(image.width / 2), -(image.height / 2));
ctx.restore();
};
// xCentre of the object to collide with
Meteor.prototype.updateRotation = function(xCentre) {
this.isRotatingClockwise = this.xCentre - xCentre > 0;
};
Meteor.prototype.explode = function() {
this.isExploding = true;
this.assetsManager.audios["explosion"].play();
this.assetsManager.audios["explosion"].currentTime = 0;
};
Meteor.prototype.isOnFire = function() {
return this.isExploded || this.isExploding;
};
powerUp.js
var PowerUp = function(xPosition, yPosition, type, assetsManager) {
this.xPosition = xPosition;
this.yPosition = yPosition;
this.type = type;
this.assetsManager = assetsManager;
this.width = 34;
this.height = 33;
this.isPickedUp = false;
};
PowerUp.prototype.update = function() {
// could be used in the future
};
PowerUp.prototype.draw = function(ctx) {
ctx.drawImage(this.assetsManager.images[this.type], this.xPosition, this.yPosition,
this.width, this.height);
};
scorePanel.js
var ScorePanel = function(game) {
this.game = game;
this.assetsManager = game.assetsManager;
this.spacecraft = game.spacecraft;
};
ScorePanel.prototype.draw = function(ctx) {
ctx.fillStyle = "#f2f2f2";
ctx.font = "20px kenvector_future_thin";
ctx.fillText(this.spacecraft.livesRemaining, 540, 30);
ctx.drawImage(this.assetsManager.images["livesRemaining"], 555, 10);
ctx.fillText("Score: " + this.spacecraft.score, 10, 28);
if (this.game.isPaused) {
ctx.drawImage(this.assetsManager.images["resumeIcon"], 5, 670);
ctx.font = "50px kenvector_future_thin";
ctx.fillText("Paused", 200, 300);
} else {
ctx.drawImage(this.assetsManager.images["pauseIcon"], 5, 670);
}
};
spacecraft.js
var Spacecraft = function(canvas, inputManager, assetsManager) {
this.canvas = canvas;
this.inputManager = inputManager;
this.assetsManager = assetsManager;
this.width = 90;
this.height = 60;
this.xPosition = 300 - (this.width / 2);
this.yPosition = 600;
this.mass = 100;
this.xVelocity = 0;
this.yVelocity = 0;
this.maxVelocity = 100;
this.accelerateFactor = 2;
// circular collision detection variables
this.radius = this.width / 2;
this.xCentre = this.xPosition + this.radius;
this.yCentre = this.yPosition + this.radius;
// collisions with walls detection
this.isLeftWall = false;
this.isRightWall = false;
this.isUpWall = false;
this.isDownWall = false;
this.bulletDelayTimer = 0;
this.bullets = [];
this.isBoltPower = false;
this.boltDuration = 15000;
this.boltTimer = 0;
this.bulletCleanUpDelayTimer = 0;
this.isShieldAnimating = false;
this.isShieldUp = false;
this.shieldDuriation = 15000;
this.shieldDelayTimer = 0;
this.shieldIndex = 0;
this.isShieldUpAudio = false;
this.isShieldDownAudio = false;
this.livesRemaining = 3;
this.score = 0;
};
Spacecraft.prototype.update = function(delta) {
this.slowDown();
this.updateDirection();
this.yPosition += (this.yVelocity / 10);
this.xPosition += (this.xVelocity / 10);
this.radius = this.width / 2;
this.xCentre = this.xPosition + this.radius;
this.yCentre = this.yPosition + this.radius;
// fire normal bullet every second, powered up bullet every 0.3 second
this.bulletDelayTimer += delta;
if (!this.isBoltPower && this.bulletDelayTimer > 1000) {
this.fire("blue");
this.bulletDelayTimer = 0;
} else if (this.isBoltPower && this.bulletDelayTimer > 300) {
this.fire("green");
this.bulletDelayTimer = 0;
}
for (var i = 0; i < this.bullets.length; i++) {
this.bullets[i].update(delta);
}
// every 10 seconds remove bullets that are off the screen
this.bulletCleanUpDelayTimer += delta;
if (this.bulletCleanUpDelayTimer > 10000) {
//console.log("Before: " + this.bullets.length);
for (var i = 0; i < this.bullets.length; i++) {
if (this.bullets[i].yPosition < -50 || this.bullets[i].isExploded) {
this.bullets.splice(i, 1);
i--;
}
}
//console.log("After: " + this.bullets.length);
this.bulletCleanUpDelayTimer = 0;
}
this.updateShield(delta);
this.updateBolt(delta);
};
Spacecraft.prototype.draw = function(ctx) {
for (var i = 0; i < this.bullets.length; i++) {
this.bullets[i].draw(ctx);
}
ctx.drawImage(this.assetsManager.images["spacecraft"], this.xPosition,
this.yPosition, this.width, this.height);
// draw the spacecraft's damage
if (this.livesRemaining == 2) {
ctx.drawImage(this.assetsManager.images["spacecraftSmallDamage"], this.xPosition,
this.yPosition, this.width, this.height);
} else if (this.livesRemaining == 1) {
ctx.drawImage(this.assetsManager.images["spacecraftMediumDamage"], this.xPosition,
this.yPosition, this.width, this.height);
} else if (this.livesRemaining == 0){
ctx.drawImage(this.assetsManager.images["spacecraftBigDamage"], this.xPosition,
this.yPosition, this.width, this.height);
}
// draw the shield
if (this.shieldIndex > 0) {
ctx.drawImage(this.assetsManager.images["shield" + this.shieldIndex], this.xPosition,
this.yPosition, this.width, this.height);
}
// collision outline for debugging
//ctx.fillStyle = "#fff";
//ctx.beginPath();
//ctx.arc(this.xCentre, this.yCentre, this.radius, 0, 2 * Math.PI);
//ctx.stroke();
//
//ctx.rect(this.xPosition, this.yPosition, this.width, this.height);
//ctx.stroke();
};
Spacecraft.prototype.slowDown = function() {
// prevents the bug where spacecraft would not stop after a collision
if (this.xVelocity > -2 && this.xVelocity < 2) {
this.xVelocity = 0;
}
if (this.yVelocity > -2 && this.yVelocity < 2) {
this.yVelocity = 0;
}
// slow down when going up
if (this.yVelocity < 0 && !this.inputManager.keys[38]) {
this.yVelocity += this.accelerateFactor;
}
// slow down when going down
if (this.yVelocity > 0 && !this.inputManager.keys[40]) {
this.yVelocity -= this.accelerateFactor;
}
// slow down when going right
if (this.xVelocity > 0 && !this.inputManager.keys[39]) {
this.xVelocity -= this.accelerateFactor;
}
// slow down when going left
if (this.xVelocity < 0 && !this.inputManager.keys[37]) {
this.xVelocity += this.accelerateFactor;
}
};
Spacecraft.prototype.updateDirection = function() {
//console.log(this.isMovingHorizontally() + ", " + this.xVelocity + ", "
// + this.isMovingVertically() + ", " + this.yVelocity);
// start moving up
if (this.inputManager.keys[38] && this.yVelocity === 0 && !this.isUpWall) {
this.yVelocity -= this.accelerateFactor;
this.isDownWall = false;
}
// accelerate further up
if (this.inputManager.keys[38] && (Math.abs(this.yVelocity) <= this.maxVelocity)) {
this.yVelocity -= this.accelerateFactor;
}
// start moving down
if (this.inputManager.keys[40] && this.yVelocity === 0 && !this.isDownWall) {
this.yVelocity += this.accelerateFactor;
this.isUpWall = false;
}
// accelerate further down
if (this.inputManager.keys[40] && (Math.abs(this.yVelocity) <= this.maxVelocity)) {
this.yVelocity += this.accelerateFactor;
}
// start moving right
if (this.inputManager.keys[39] && this.xVelocity === 0 && !this.isRightWall) {
this.xVelocity += this.accelerateFactor;
this.isLeftWall = false;
}
// accelerate further right
if (this.inputManager.keys[39] && (Math.abs(this.xVelocity) <= this.maxVelocity)) {
this.xVelocity += this.accelerateFactor;
}
// start moving left
if (this.inputManager.keys[37] && this.xVelocity === 0 && !this.isLeftWall) {
this.xVelocity -= this.accelerateFactor;
this.isRightWall = false;
}
// accelerate further left
if (this.inputManager.keys[37] && (Math.abs(this.xVelocity) <= this.maxVelocity)) {
this.xVelocity -= this.accelerateFactor;
}
};
Spacecraft.prototype.updateShield = function(delta) {
// shield stuff
if (this.isShieldAnimating && !this.isShieldUp) {
this.shieldDelayTimer += delta;
if (!this.isShieldUpAudio) {
this.assetsManager.audios["shieldUp"].play();
this.assetsManager.audios["shieldUp"].currentTime = 0;
this.isShieldUpAudio = true;
}
if (this.shieldDelayTimer > 500) {
if (this.shieldIndex < 3) {
this.shieldIndex++;
//console.log("Shield Up: " + this.shieldIndex);
} else {
this.isShieldUp = true;
this.isShieldAnimating = false;
}
this.shieldDelayTimer = 0;
}
} else if (this.isShieldAnimating && this.isShieldUp) {
// shield down
this.shieldDelayTimer += delta;
if (!this.isShieldDownAudio) {
this.assetsManager.audios["shieldDown"].play();
this.assetsManager.audios["shieldDown"].currentTime = 0;
this.isShieldDownAudio = true;
}
if (this.shieldDelayTimer > 500) {
if (this.shieldIndex > 0) {
this.shieldIndex--;
//console.log("Shield down: " + this.shieldIndex);
} else {
this.isShieldUp = false;
this.isShieldAnimating = false;
}
this.shieldDelayTimer = 0;
}
} else if (this.isShieldUp) {
// count time
this.shieldDelayTimer += delta;
if (this.isShieldDownAudio || this.isShieldUpAudio) {
this.isShieldDownAudio = false;
this.isShieldUpAudio = false;
}
if (this.shieldDelayTimer > this.shieldDuriation) {
// put the shield down
this.isShieldAnimating = true;
this.shieldDelayTimer = 0;
}
}
};
Spacecraft.prototype.updateBolt = function(delta) {
if (this.isBoltPower) {
this.boltTimer += delta;
//console.log("boltTimer: " + this.boltTimer);
if (this.boltTimer > this.boltDuration) {
// switch back to blue bullets
this.isBoltPower = false;
this.boltTimer = 0;
}
}
};
Spacecraft.prototype.fire = function(color) {
if (color === "blue" || color === "green") {
this.bullets.push(new Bullet(this.xPosition + (this.width / 2) - (14 / 2) ,
this.yPosition, color, this.assetsManager));
this.assetsManager.audios["laserPlayer"].play();
this.assetsManager.audios["laserPlayer"].currentTime = 0;
} else {
console.error(color + " is not an appropriate color to fire a bullet!");
}
};
Spacecraft.prototype.shieldUp = function() {
if (this.isShieldUp) {
this.shieldDelayTimer = 0;
this.isShieldAnimating = false;
} else {
this.isShieldAnimating = true;
}
};
Spacecraft.prototype.boltPowerUp = function() {
if (this.isBoltPower) {
this.boltTimer = 0;
} else {
this.isBoltPower = true;
}
};
Back to Main Page