Files
2026-06-24 15:34:55 +08:00

2961 lines
103 KiB
JavaScript

const bgCanvas = document.getElementById("canvas-bg");
const mainCanvas = document.getElementById("canvas-main");
const bgCtx = bgCanvas.getContext("2d");
const mainCtx = mainCanvas.getContext("2d");
mainCanvas.width = 840;
mainCanvas.height = 480;
bgCanvas.width = 840;
bgCanvas.height = 480;
const buttonUp = document.getElementById("button-up");
const buttonDown = document.getElementById("button-down");
const buttonLeft = document.getElementById("button-left");
const buttonRight = document.getElementById("button-right");
const buttonFire = document.getElementById("button-fire");
const buttonX = document.getElementById("button-x");
const mainSprites = new Image();
mainSprites.src = "./static/Space-Impact-Web/img/gameSprites.png";
const bossSprites = new Image();
bossSprites.src = "./static/Space-Impact-Web/img/bossSprites.png";
const bgSprites1 = new Image();
bgSprites1.src = "./static/Space-Impact-Web/img/bgSprites1.png";
const bgSprites2 = new Image();
bgSprites2.src = "./static/Space-Impact-Web/img/bgSprites2.png";
//for all images, sprite dimensions and game dimensions of the character is same
const bgArray2 = [0, 1, 2, 1, 1, 2, 2, 1, 2, 1, 1, 1, 2, 0, 1, 1, 2];
const bgArray3 = [0, 1, 2, 2, 1, 0, 2, 0, 1, 2, 1, 0, 2, 0, 2, 1, 0, 1, 2, 0, 1];
const bgArray4 = [0, 1, 2, 2, 1, 0, 2, 0, 1, 2, 1, 0, 2, 0, 2, 1, 0, 1, 2, 2, 1, 0, 2, 0, 1, 0];
const bgArray5 = [0, 1, 2, 2, 0, 2, 1, 1, 0, 1, 0, 0, 1, 1, 2, 1, 0, 1, 2, 2, 0, 2, 1, 1, 0, 1, 0, 0];
const bgArray6 = [2, 2, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 2, 1, 2, 2, 1, 1, 0, 0, 1, 1];
const bgArray7 = [0, 0, 0];
const bgArray8 = [0];
// game helper variables
const keys = {
ArrowLeft: {
pressed: false
},
ArrowRight: {
pressed: false
},
ArrowUp: {
pressed: false
},
ArrowDown: {
pressed: false
},
space: {
pressed: false
},
x: {
pressed: false
}
}
let gameOver = false;
let gamePause = false;
let isLevelDark = true;
let specialAtttack = "missile"; //3 spAtk available, "missile", "laser", "wall";
let specialCount = 3;
let lives = 4;
const maxLives = 7;
let playerScore = 0;
class InputHandler {
constructor(level) {
this.level = level;
// keyboard events
window.addEventListener("keydown", (e) => {
e.preventDefault(); //prevents the page scrolling
if (gameOver || !level.active || gamePause) return;
switch (e.key) {
case "ArrowLeft":
keys.ArrowLeft.pressed = true;
break;
case "ArrowRight":
keys.ArrowRight.pressed = true;
break;
case "ArrowUp":
keys.ArrowUp.pressed = true;
break;
case "ArrowDown":
keys.ArrowDown.pressed = true;
break;
case " ":
if (!keys.space.pressed) {
this.level.playerProjectiles.push(new Projectile(true, this.level.player));
keys.space.pressed = true;
}
break;
case "X":
case "x":
if (!keys.x.pressed) {
if (specialCount > 0) {
switch (specialAtttack) {
case "missile": this.level.playerSpecial.push(new Missile(this.level));
break;
case "laser": this.level.playerSpecial.push(new Laser(this.level));
break;
case "wall": this.level.playerSpecial.push(new Wall(this.level));
break;
}
specialCount--;
}
keys.x.pressed = true;
}
break;
}
});
window.addEventListener("keyup", (e) => {
switch (e.key) {
case "ArrowLeft":
keys.ArrowLeft.pressed = false;
break;
case "ArrowRight":
keys.ArrowRight.pressed = false;
break;
case "ArrowUp":
keys.ArrowUp.pressed = false;
break;
case "ArrowDown":
keys.ArrowDown.pressed = false;
break;
case " ":
keys.space.pressed = false;
break;
case "X":
case "x":
keys.x.pressed = false;
break;
}
});
// onscreen-buttons events
buttonLeft.addEventListener("pointerdown", ()=>{
if (gameOver || !level.active || gamePause) return;
keys.ArrowLeft.pressed = true;
})
buttonLeft.addEventListener("pointerup", ()=>{
keys.ArrowLeft.pressed = false;
})
buttonLeft.addEventListener("pointerout", ()=>{
keys.ArrowLeft.pressed = false;
})
buttonRight.addEventListener("pointerdown", ()=>{
if (gameOver || !level.active || gamePause) return;
keys.ArrowRight.pressed = true;
})
buttonRight.addEventListener("pointerup", ()=>{
keys.ArrowRight.pressed = false;
})
buttonRight.addEventListener("pointerout", ()=>{
keys.ArrowRight.pressed = false;
})
buttonUp.addEventListener("pointerdown", ()=>{
if (gameOver || !level.active || gamePause) return;
keys.ArrowUp.pressed = true;
})
buttonUp.addEventListener("pointerup", ()=>{
keys.ArrowUp.pressed = false;
})
buttonUp.addEventListener("pointerout", ()=>{
keys.ArrowUp.pressed = false;
})
buttonDown.addEventListener("pointerdown", ()=>{
if (gameOver || !level.active || gamePause) return;
keys.ArrowDown.pressed = true;
})
buttonDown.addEventListener("pointerup", ()=>{
keys.ArrowDown.pressed = false;
})
buttonDown.addEventListener("pointerout", ()=>{
keys.ArrowDown.pressed = false;
})
// firing button events
buttonFire.addEventListener("pointerdown", ()=>{
if (gameOver || !level.active || gamePause) return;
if (!keys.space.pressed) {
this.level.playerProjectiles.push(new Projectile(true, this.level.player));
keys.space.pressed = true;
}
})
buttonFire.addEventListener("pointerup", ()=>{
keys.space.pressed = false;
})
buttonFire.addEventListener("pointerout", ()=>{
keys.space.pressed = false;
})
buttonX.addEventListener("pointerdown", ()=>{
if (gameOver || !level.active || gamePause) return;
if (!keys.x.pressed) {
if (specialCount > 0) {
switch (specialAtttack) {
case "missile": this.level.playerSpecial.push(new Missile(this.level));
break;
case "laser": this.level.playerSpecial.push(new Laser(this.level));
break;
case "wall": this.level.playerSpecial.push(new Wall(this.level));
break;
}
specialCount--;
}
keys.x.pressed = true;
}
})
buttonX.addEventListener("pointerup", ()=>{
keys.x.pressed = false;
})
buttonX.addEventListener("pointerout", ()=>{
keys.x.pressed = false;
})
}
}
// Hitbox class for player, background and boss
class Hitbox {
constructor(x, y, width, height, offsetX, offsetY, immune) {
this.offsetX = offsetX;
this.offsetY = offsetY;
this.x = x + this.offsetX;
this.y = y + this.offsetY;
this.width = width;
this.height = height;
this.immune = immune;
}
update(x, y) {
this.x = x + this.offsetX;
this.y = y + this.offsetY;
//this.draw(); //uncomment for debugging hitbox
}
draw() {
bgCtx.fillStyle = "red";
if (this.immune) bgCtx.fillStyle = "blue";
bgCtx.fillRect(this.x, this.y, this.width, this.height);
}
}
class Shield {
constructor(player) {
this.player = player;
this.image = mainSprites
this.x = this.player.x - 20;
this.y = this.player.y - 20;
this.width = 130;
this.height = 110;
this.spriteSize = 150;
this.maxFrames = 2;
this.frameXStart = 2;
this.frameY = 0;
if (isLevelDark) this.frameXStart = 4;
this.frameX = this.frameXStart;
this.spriteTimer = 0;
this.spriteInterval = 150;
}
update(deltaTime) {
this.x = this.player.x - 20;
this.y = this.player.y - 20;
//sprite animation
if (this.spriteTimer > this.spriteInterval) {
this.frameX++;
if (this.frameX - this.frameXStart >= this.maxFrames) this.frameX = this.frameXStart;
this.spriteTimer = 0;
} else this.spriteTimer += deltaTime;
this.draw();
}
draw() {
mainCtx.drawImage(this.image, this.frameX * this.spriteSize, this.frameY * this.spriteSize, this.width, this.height, this.x, this.y, this.width, this.height);
}
}
class Player {
constructor(level) {
this.level = level;
this.image = mainSprites;
this.width = 100; // sprite width and game width is same
this.height = 70;
this.x = 20;
this.y = 220;
this.speedX = 2.5;
this.speedY = 3;
this.frameX = 0; //starting x coordinate of the box in sprite
this.frameY = 0; //starting y coordinate of the box in sprite
this.spriteSize = 150; // size of each box(uniform all over sprite)
if (isLevelDark) this.frameX = 1;
this.hit = false; // is player hit
this.delete = false;
this.hitbox = new Hitbox(this.x, this.y, this.width - 10, this.height - 20, 0, 10, false);
this.shield = new Shield(this);
this.shieldOn = true;
this.shieldInterval = 4000;
this.shieldTimer = 0;
}
update(deltaTime) {
// shield
if (this.shieldTimer <= this.shieldInterval) {
this.shieldTimer += deltaTime;
this.shield.update(deltaTime);
}
else this.shieldOn = false;
// update hitbox
this.hitbox.update(this.x, this.y);
// x-axis limits
if (keys.ArrowLeft.pressed && this.x >= 0) this.x -= this.speedX;
if (keys.ArrowRight.pressed && this.x <= mainCanvas.width - this.width) this.x += this.speedX;
else this.x += 0;
// y-axis limits
if (keys.ArrowUp.pressed && this.y >= 50) this.y -= this.speedY;
if (keys.ArrowDown.pressed && this.y <= mainCanvas.height - this.height) this.y += this.speedY;
else this.y += 0;
this.draw();
}
draw() {
mainCtx.drawImage(this.image, this.frameX * this.spriteSize, this.frameY * this.spriteSize, this.width, this.height, this.x, this.y, this.width, this.height);
}
}
class Projectile {
constructor(isPlayer, object) {
this.object = object;
this.isPlayer = isPlayer; //boolean to differentiate enemy and player
this.width = 20;
this.height = 10;
if (isPlayer) {
this.x = this.object.x + this.object.width;
this.y = this.object.y + this.object.height * 0.5 - this.height * 0.5;
this.speed = 3;
} else {
this.x = this.object.x;
this.y = this.object.y + this.object.height * 0.5 - this.height * 0.5;
this.speed = -object.speedX - 1;
}
this.delete = false;
}
update() {
this.x += this.speed;
if (this.x >= mainCanvas.width || this.x < 0) this.delete = true;
this.draw();
}
draw() {
mainCtx.fillStyle = "#282828";
if (isLevelDark) mainCtx.fillStyle = "#aad69c";
mainCtx.fillRect(this.x, this.y, this.width, this.height);
}
}
// Special Power Ups
class PowerUp {
constructor(x, y, speedX, speedY, movement, range, xbreak) {
this.image = mainSprites;
this.isPowerUp = true;
this.width = 80;
this.height = 70;
this.x = x;
this.y = y;
this.spriteSize = 150;
this.maxFrames = 2;
this.frameXStart = 4;
this.frameY = 6;
if (isLevelDark) this.frameY = 7;
this.frameX = this.frameXStart;
this.delete = false; // powerUp marked for deletion;
this.speedX = speedX;
this.speedY = speedY;
this.movement = movement;
this.range = range;
this.xbreak = xbreak;
this.angle = 0;
this.spriteTimer = 0; //sprite animation helper
this.spriteInterval = 300;
this.hit = false; // flag for single collision else multiple power added
//randomizing the power up awarded to the player
this.randomize = Math.random();
if (this.randomize < 0.25) this.powerup = "life";
else if (this.randomize < 0.5) this.powerup = "missile";
else if (this.randomize < 0.75) this.powerup = "laser";
else this.powerup = "wall";
}
update(deltaTime) {
//sprite animation
if (this.spriteTimer > this.spriteInterval) {
this.frameX++;
if (this.frameX - this.frameXStart >= this.maxFrames) this.frameX = this.frameXStart;
this.spriteTimer = 0;
} else this.spriteTimer += deltaTime;
//movement
switch (this.movement) {
case "wave":
this.x -= this.speedX;
this.y = this.xbreak + this.range * Math.sin(this.angle);
this.angle += this.speedY;
break;
case "linear":
default:
this.x -= this.speedX;
}
// garbage collection
if (this.x + this.width < 0) this.delete = true;
this.draw();
}
draw() {
mainCtx.drawImage(this.image, this.frameX * this.spriteSize, this.frameY * this.spriteSize, this.width, this.height, this.x, this.y, this.width, this.height);
}
}
class Missile {
constructor(level) {
this.image = mainSprites;
this.spriteX = 900;
this.spriteY = 0;
if (isLevelDark) this.spriteY = 150;
this.level = level;
this.width = 50;
this.height = 30;
this.x = this.level.player.x + this.level.player.width;
this.y = this.level.player.y + this.level.player.height * 0.5 - this.height * 0.5;
this.targetSet = false;
this.target = null;
this.speedX = 3;
this.speedY = 0;
this.delete = false;
this.damage = 50;
this.specialType = "missile";
this.hit = false;
}
update() {
// setting the target
if (!this.targetSet) {
this.level.enemies.forEach(enemy => {
if (enemy.x > this.x + 40 && !this.targetSet) {
this.target = enemy;
this.targetSet = true;
this.speedX = 4;
}
});
}
if (this.target != null) {
// unsetting target
if (this.target.delete || this.target.x + this.target.width < this.x) {
this.targetSet = false;
this.speedX = 3;
}
// following target
if (this.y >= this.target.y + this.target.height * 0.5) this.speedY = -5;
else if (this.y + this.height * 0.5 <= this.target.y) this.speedY = 5;
else this.speedY = 0;
}
this.x += this.speedX;
this.y += this.speedY;
if (this.x >= 840) this.delete = true;
this.draw();
}
draw() {
mainCtx.drawImage(this.image, this.spriteX, this.spriteY, this.width, this.height, this.x, this.y, this.width, this.height);
}
}
class Laser {
constructor(level) {
this.level = level;
this.x = this.level.player.x + this.level.player.width;
this.y = this.level.player.y + this.level.player.height * 0.5 - 5;
this.width = 600;
this.height = 10;
this.duration = 500;
this.timer = 0;
this.delete = false;
this.hit = false; //helper for score deduction for boss
this.damage = 100;
this.specialType = "laser";
}
update(deltaTime) {
if (this.timer < this.duration) {
this.draw();
this.timer += deltaTime;
}
else this.delete = true;
}
draw() {
if (isLevelDark) mainCtx.fillStyle = "#aad69c";
else mainCtx.fillStyle = "#282828";
mainCtx.fillRect(this.x, this.y - 10, 10, 10);
mainCtx.fillRect(this.x, this.y + 10, 10, 10);
mainCtx.fillRect(this.x + 10, this.y, this.width, this.height);
}
}
class Wall {
constructor(level) {
this.level = level;
this.x = this.level.player.x + this.level.player.width;
this.y = 50;
this.width = 10;
this.height = 430;
this.speed = 4;
this.hit = false;
this.delete = false;
this.damage = 100;
this.specialType = "wall";
}
update() {
this.x += this.speed;
if (this.x > 840) this.delete = true;
this.draw();
}
draw() {
if (isLevelDark) mainCtx.fillStyle = "#aad69c";
else mainCtx.fillStyle = "#282828";
mainCtx.fillRect(this.x, this.y, this.width, this.height);
}
}
class EnemyMissile {
constructor(boss) {
this.boss = boss;
this.x = this.boss.x;
this.y = this.boss.y + this.boss.height * 0.5;
this.width = 50;
this.height = 30;
this.target = currentLevel.player;
this.speedX = 3;
this.speedY = 0;
this.delete = false;
}
update() {
// seeking missile targeting the player
if (this.y >= this.target.y + this.target.height * 0.5) this.speedY = -3;
else if (this.y + this.height * 0.5 <= this.target.y) this.speedY = 3;
else this.speedY = 0;
this.x -= this.speedX;
this.y += this.speedY;
if (this.x + this.width < 0) this.delete = true;
this.draw();
}
draw() {
mainCtx.fillStyle = "#282828";
mainCtx.fillRect(this.x, this.y + 10, 10, 10);
mainCtx.fillRect(this.x + 10, this.y, 20, 30);
mainCtx.fillRect(this.x + 30, this.y + 10, 10, 10);
mainCtx.fillRect(this.x + 40, this.y, 10, 30);
}
}
//Explosion effect
class Explosion {
constructor(x, y) {
this.image = mainSprites;
this.x = x;
this.y = y;
this.width = 70;
this.height = 70;
this.spriteSize = 150;
this.maxFrames = 5;
this.frames = 1;
this.staggeredFrames = 5;
this.timer = 0;
this.frameX = 0;
this.frameY = 8;
if (isLevelDark) this.frameY = 9;
this.delete = false;
}
update() {
if (this.frames % this.staggeredFrames === 0) {
this.frameX++;
this.frames = 1;
}
else this.frames++;
if (this.frameX >= this.maxFrames) this.delete = true;
this.draw();
}
draw() {
mainCtx.drawImage(this.image, this.frameX * this.spriteSize, this.frameY * this.spriteSize, this.width, this.height, this.x, this.y, this.width, this.height);
}
}
//enemy classes
class Enemy {
constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) {
this.image = mainSprites;
this.x = x;
this.y = y;
this.spriteSize = 150;
this.hp = hp;
this.score = this.hp;
this.shoots = shoots; // boolean; whether the enemy shoots back
this.speedX = speedX; // enemy speed x direction
this.speedY = speedY; // enemy speed y direction || angle speed for sin
this.movement = movement; // string: type of movement
this.range = range; // y axis range of enemy movement || 2nd break point on x axis for z movement
this.xbreak = xbreak; // x coordinate where the enemy starts z movement || the base on y for sinusoid
this.delete = false; // Enemy marked for Deletion
this.angle = 0;
this.spriteTimer = 0; //sprite animation helper
this.spriteInterval = Math.floor(Math.random() * 80) + 120;
this.fireTimer = 0; // projectile firing timer
this.fireInteval = Math.floor(Math.random() * 2000) + 1000; // projectile random firing interval b/w 1500 and 3500ms
this.hit = false;
}
update(deltaTime) {
//sprite animation
if (this.spriteTimer > this.spriteInterval) {
this.frameX++;
if (this.frameX - this.frameXStart >= this.maxFrames) this.frameX = this.frameXStart;
this.spriteTimer = 0;
} else this.spriteTimer += deltaTime;
// projectile generation
if (this.shoots) {
if (this.fireTimer > this.fireInteval) {
currentLevel.enemyProjectiles.push(new Projectile(false, this));
this.fireTimer = 0;
}
else this.fireTimer += deltaTime;
}
//movement
switch (this.movement) {
case "wave":
this.x -= this.speedX;
this.y = this.xbreak + this.range * Math.sin(this.angle);
this.angle += this.speedY;
break;
case "zigzag":
this.x -= this.speedX;
if (this.x < this.xbreak && this.x >= this.range) this.y += this.speedY;
break;
case "mini1": // mini boss 1 in level 3
if (this.x > this.xbreak) this.x -= this.speedX;
else {
if (this.y > 330 || this.y < 70) this.speedY *= -1;
this.y += this.speedY;
}
break;
case "mini2": // mini boss 2 in level 5
if (this.x > this.xbreak) this.x -= this.speedX;
else {
if (this.y > 430 || this.y < 150) this.speedY *= -1;
this.y += this.speedY;
}
break;
case "linear":
default:
this.x -= this.speedX;
}
// garbage collection
if (this.x + this.width < 0) this.delete = true;
this.draw();
}
draw() {
mainCtx.drawImage(this.image, this.frameX * this.spriteSize, this.frameY * this.spriteSize, this.width, this.height, this.x, this.y, this.width, this.height);
}
}
class Meteor extends Enemy {
constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) {
super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak);
this.width = 100;
this.height = 50;
this.maxFrames = 2;
this.frameXStart = 0;
this.frameX = this.frameXStart;
this.frameY = 1;
}
}
class Triship extends Enemy {
constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) {
super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak);
this.width = 60;
this.height = 70;
this.maxFrames = 2;
this.frameXStart = 2;
if (isLevelDark) this.frameXStart += this.maxFrames;
this.frameX = this.frameXStart;
this.frameY = 1;
}
}
class Squid extends Enemy {
constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) {
super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak);
this.width = 80;
this.height = 50;
this.maxFrames = 1;
this.frameXStart = 0;
if (isLevelDark) this.frameXStart += this.maxFrames;
this.frameX = this.frameXStart;
this.frameY = 2;
}
}
class Shuttle extends Enemy {
constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) {
super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak);
this.width = 90;
this.height = 50;
this.maxFrames = 1;
this.frameXStart = 2;
if (isLevelDark) this.frameXStart += this.maxFrames;
this.frameX = this.frameXStart;
this.frameY = 2;
}
}
class Saucer extends Enemy {
constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) {
super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak);
this.width = 90;
this.height = 50;
this.maxFrames = 1;
this.frameXStart = 4;
if (isLevelDark) this.frameXStart += this.maxFrames;
this.frameX = this.frameXStart;
this.frameY = 2;
}
}
class Tadpole extends Enemy {
constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) {
super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak);
this.width = 90;
this.height = 50;
this.maxFrames = 2;
this.frameXStart = 0;
if (isLevelDark) this.frameXStart += this.maxFrames;
this.frameX = this.frameXStart;
this.frameY = 3;
}
}
class Kraken extends Enemy {
constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) {
super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak);
this.width = 70;
this.height = 80;
this.maxFrames = 1;
this.frameXStart = 4;
if (isLevelDark) this.frameXStart += this.maxFrames;
this.frameX = this.frameXStart;
this.frameY = 3;
}
}
class Marble1 extends Enemy {
constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) {
super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak);
this.width = 50;
this.height = 50;
this.maxFrames = 1;
this.frameXStart = 0;
this.frameX = this.frameXStart;
this.frameY = 4;
}
}
class Marble2 extends Enemy {
constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) {
super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak);
this.width = 50;
this.height = 40;
this.maxFrames = 1;
this.frameXStart = 1;
if (isLevelDark) this.frameXStart += this.maxFrames;
this.frameX = this.frameXStart;
this.frameY = 4;
}
}
class Marble3 extends Enemy {
constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) {
super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak);
this.width = 50;
this.height = 30;
this.maxFrames = 1;
this.frameXStart = 3;
if (isLevelDark) this.frameXStart += this.maxFrames;
this.frameX = this.frameXStart;
this.frameY = 4;
}
}
class Beetle extends Enemy {
constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) {
super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak);
this.width = 80;
this.height = 50;
this.maxFrames = 2;
this.frameXStart = 0;
if (isLevelDark) this.frameXStart += this.maxFrames;
this.frameX = this.frameXStart;
this.frameY = 5;
}
}
class Rock extends Enemy {
constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) {
super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak);
this.width = 70;
this.height = 70;
this.maxFrames = 1;
this.frameXStart = 4;
this.frameX = this.frameXStart;
this.frameY = 5;
}
}
class Flipper extends Enemy {
constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) {
super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak);
this.width = 60;
this.height = 60;
this.maxFrames = 2;
this.frameXStart = 0;
if (isLevelDark) this.frameXStart += this.maxFrames;
this.frameX = this.frameXStart;
this.frameY = 6;
}
}
class Dragonfly extends Enemy {
constructor(hp, x, y, shoots, speedX, speedY, movement, range, xbreak) {
super(hp, x, y, shoots, speedX, speedY, movement, range, xbreak);
this.width = 100;
this.height = 50;
this.maxFrames = 2;
this.frameXStart = 0;
if (isLevelDark) this.frameXStart += this.maxFrames;
this.frameX = this.frameXStart;
this.frameY = 7;
}
}
class Torpedo {
constructor(x, y, offsetX, offsetY) {
this.image = mainSprites;
this.offsetX = offsetX;
this.offsetY = offsetY;
this.width = 70;
this.height = 50;
this.x = x + offsetX;
this.y = y + offsetY;
this.delete = false;
this.hp = 150;
this.torpToggle = true; // toggling torpedoes up and down
}
update(x) {
if (this.hp <= 0) {
this.x -= 7;
} else {
this.x = x + this.offsetX;
}
if (this.x + this.width < 0) this.delete = true;
this.draw();
}
draw() {
mainCtx.drawImage(this.image, 900, 300, this.width, this.height, this.x, this.y, this.width, this.height);
}
}
// Boss Classes
class Boss {
constructor() {
this.image = bossSprites;
this.isBoss = true;
this.spriteTimer = 0; //sprite animation helper
this.spriteInterval = 200;
this.fireTimer = 0; // projectile firing timer
this.spriteSize = 400;
this.frameX = 0;
this.maxFrames = 2;
this.speedX = 2;
this.speedY = 1.5;
this.supportEnemies = [];
this.chargeType = 0; // 0: no charge, 1: fwd , 2: back and then fwd
this.xMin = 20;
this.charge = false;
this.retreat = false;
this.chargeTimer = 0;
this.chargeInterval = 9000;
this.support = false;
this.delete = false;
}
update(deltaTime) {
//sprite animation
if (this.spriteTimer > this.spriteInterval) {
this.frameX++;
if (this.frameX >= this.maxFrames) this.frameX = 0;
this.spriteTimer = 0;
} else this.spriteTimer += deltaTime;
// projectile generation
if (this.fireTimer > this.fireInteval) {
currentLevel.enemyProjectiles.push(new Projectile(false, this));
this.fireTimer = 0;
}
else this.fireTimer += deltaTime;
// movement
if (!this.charge && !this.retreat) {
//entry and basic movement;
if (this.x > this.xbreak + 1) this.x -= this.speedX;
else if (this.x < this.xbreak - 1) this.x += this.speedX;
else {
if (this.y + this.height >= this.yMax || this.y <= this.yMin) this.speedY *= -1;
this.y += this.speedY;
}
}
if (this.chargeTimer > this.chargeInterval) {
switch (this.chargeType) {
case 1: this.charge = true;
break;
case 2: this.retreat = true;
break;
case 0: this.charge = false;
break;
}
this.chargeTimer = 0;
}
else this.chargeTimer += deltaTime;
if (this.retreat) {
this.retreatMovement();
}
if (this.charge) {
this.chargeMovement();
}
//update hitbox
this.hitbox.forEach(hb => hb.update(this.x, this.y));
// support ship generation, collision, and deletion
if (this.support) {
if (this.supportTimer >= this.supportInterval) {
this.supportGen();
this.supportTimer = 0;
} else this.supportTimer += deltaTime;
this.supportEnemies.forEach((e, i) => {
currentLevel.playerProjectiles.forEach(pp => {
if (checkCollision(pp, e)) {
e.hp -= 10;
if (e.hp <= 0) e.delete = true;
pp.delete = true;
}
});
currentLevel.playerSpecial.forEach(sp => {
if (checkCollision(sp, e)) {
e.delete = true;
currentLevel.explosions.push(new Explosion(e.x, e.y));
if (sp.specialType === "missile") sp.delete = true;
}
});
// collision detection with player and shield
if (currentLevel.player.shieldOn) {
if (checkCollision(currentLevel.player.shield, e)) {
e.delete = true;
currentLevel.explosions.push(new Explosion(e.x, e.y));
}
}
else if (checkCollision(currentLevel.player, e)) {
if (!currentLevel.player.hit) {
currentLevel.player.hit = true;
currentLevel.explosions.push(new Explosion(e.x, e.y));
e.delete = true;
currentLevel.playerDead();
}
}
e.update(deltaTime);
if (e.delete) this.supportEnemies.splice(i, 1);
});
}
// draw
this.draw();
}
chargeMovement() {
if (this.x > this.xMin) this.x -= 4;
else this.charge = false;
}
retreatMovement() {
if (this.x <= 840) this.x += 2
else {
this.retreat = false;
this.charge = true;
this.y = this.chargeY;
}
}
draw() {
mainCtx.drawImage(this.image, this.frameX * this.spriteSize, this.frameY * this.spriteSize, this.width, this.height, this.x, this.y, this.width, this.height);
}
}
class Boss1 extends Boss {
constructor() {
super();
this.width = 200;
this.height = 230;
this.x = 840;
this.y = 120;
this.hitbox = [new Hitbox(this.x, this.y, this.width - 50, this.height - 30, 50, 0, false)];
this.xbreak = 550;
this.hp = 200;
this.score = 100;
this.delete = false;
this.frameY = 0;
this.yMin = 50;
this.yMax = 480;
this.fireInteval = 1500;
}
}
class Boss2 extends Boss {
constructor() {
super();
this.width = 230;
this.height = 210;
this.x = 840;
this.y = 120;
this.hitbox = [new Hitbox(this.x, this.y, this.width - 50, this.height - 10, 50, 0, false)];
this.xbreak = 550;
this.hp = 200;
this.score = 100;
this.delete = false;
this.frameY = 1;
this.yMin = 50;
this.yMax = 480;
this.fireInteval = 1500;
}
}
class Boss3 extends Boss {
constructor() {
super();
this.width = 220;
this.height = 200;
this.x = 840;
this.y = 120;
this.hitbox = [new Hitbox(this.x, this.y, this.width - 10, this.height - 10, 10, 10, false)];
this.xbreak = 500;
this.hp = 300;
this.score = 100;
this.delete = false;
this.frameY = 2;
this.yMin = 50;
this.yMax = 340;
this.fireInteval = 1500;
this.chargeType = 1;
}
}
class Boss4 extends Boss {
constructor() {
super();
this.width = 150;
this.height = 250;
this.x = 840;
this.y = 120;
this.hitbox = [
new Hitbox(this.x, this.y, this.width, 70, 0, 0, false),
new Hitbox(this.x, this.y, 100, 140, 50, 70, false)
];
this.xbreak = 550;
this.hp = 250;
this.score = 100;
this.delete = false;
this.frameY = 3;
this.yMin = 50;
this.yMax = 450;
this.fireInteval = 1500;
this.support = true;
this.supportTimer = 0;
this.supportInterval = 3000;
}
supportGen() {
currentLevel.enemyProjectiles.push(new EnemyMissile(this));
}
}
class Boss5 extends Boss {
constructor() {
super();
this.width = 190;
this.height = 210;
this.x = 840;
this.y = 220;
this.hitbox = [new Hitbox(this.x, this.y, this.width - 10, this.height - 10, 10, 10, false)];
this.xbreak = 500;
this.hp = 350;
this.score = 100;
this.delete = false;
this.frameY = 4;
this.yMin = 210;
this.yMax = 480;
this.fireInteval = 1500;
this.chargeType = 1;
this.support = true;
this.supportTimer = 0;
this.supportInterval = 4000;
}
supportGen() {
this.supportEnemies.push(new Beetle(20, this.x, this.y + Math.random() * (this.height - 50), false, 3, 0, "linear", 0, 0));
}
}
class Boss6 extends Boss {
constructor() {
super();
this.width = 200;
this.height = 190;
this.x = 840;
this.y = 220;
this.hitbox = [new Hitbox(this.x, this.y, this.width - 30, this.height, 30, 0, false)];
this.xbreak = 500;
this.hp = 350;
this.score = 100;
this.delete = false;
this.frameY = 5;
this.yMin = 140;
this.yMax = 480;
this.fireInteval = 1500;
this.speedX = 3;
this.chargeY = 270;
this.chargeInterval = 12000;
this.chargeType = 2;
this.support = true;
this.supportTimer = 0;
this.supportInterval = 4000;
}
supportGen() {
this.supportEnemies.push(new Tadpole(20, this.x, this.y + Math.random() * (this.height - 50), false, 3, 0, "linear", 0, 0));
}
}
class Boss7 extends Boss {
constructor() {
super();
this.width = 300;
this.height = 250;
this.x = 840;
this.y = 120;
this.hitbox = [
new Hitbox(this.x, this.y, this.width - 20, 120, 20, 10, false),
new Hitbox(this.x, this.y, 220, this.height - 20, 80, 10, false),
new Hitbox(this.x, this.y, this.width, 50, 0, 200, false)
];
this.xbreak = 550;
this.hp = 300;
this.score = 100;
this.delete = false;
this.frameY = 6;
this.yMin = 50;
this.yMax = 400;
this.fireInteval = 1500;
this.chargeY = 60;
this.chargeInterval = 12000;
this.chargeType = 2;
this.support = true;
this.supportTimer = 0;
this.supportInterval = 4000;
}
supportGen() {
this.supportEnemies.push(new Flipper(20, this.x, this.y + Math.random() * (this.height - 60), false, 3, 0, "linear", 0, 0));
}
}
class Boss8 extends Boss {
constructor() {
super();
this.width = 380;
this.height = 380;
this.x = 840;
this.y = 50;
this.hitbox = [
new Hitbox(this.x, this.y, this.width - 20, 120, 20, 10, true),
new Hitbox(this.x, this.y, 100, 60, 100, 130, true),
new Hitbox(this.x, this.y, 100, 60, 100, 220, true),
new Hitbox(this.x, this.y, 90, 100, 210, 150, false)
];
this.xbreak = 460;
this.hp = 250;
this.score = 100;
this.delete = false;
this.frameY = 7;
this.fireTimer = 0;
this.fireInteval = 3000;
this.torpedoes = [
new Torpedo(this.x, this.y, 50, 150),
new Torpedo(this.x, this.y, 30, 220),
];
this.retreat = false;
this.retreatTimer = 0;
this.retreatInterval = 6000;
this.retreatFire = false; // to trigger retreat swarm only once
}
update(deltaTime) {
//sprite animation
if (this.spriteTimer > this.spriteInterval) {
this.frameX++;
this.torpedoes.forEach(torp => {
if (torp.hp > 0) {
if (torp.torpToggle) {
torp.y += 10;
torp.torpToggle = !torp.torpToggle;
} else {
torp.y -= 10;
torp.torpToggle = !torp.torpToggle;
}
}
});
if (this.frameX >= this.maxFrames) this.frameX = 0;
this.spriteTimer = 0;
} else this.spriteTimer += deltaTime;
// torpedo
this.torpedoes.forEach((torp, i) => {
currentLevel.playerProjectiles.forEach(pp => {
if (checkCollision(pp, torp)) {
torp.hp -= 5;
pp.delete = true;
}
});
currentLevel.playerSpecial.forEach(sp => {
if (checkCollision(sp, torp)) {
if (!sp.hit) {
sp.hit = true;
if (sp.specialType === "missile") {
sp.delete = true;
torp.hp -= 30;
}
else torp.hp -= 50;
}
}
});
torp.update(this.x);
if (torp.delete) this.torpedoes.splice(i, 1);
});
// projectile generation aka support ships
if (this.fireTimer >= this.fireInteval && !this.retreat) {
this.supportEnemies.push(new Triship(30, this.x, 240, false, 3, 0, "linear", 0, 0));
this.fireTimer = 0;
} else this.fireTimer += deltaTime;
this.supportEnemies.forEach((e, i) => {
currentLevel.playerProjectiles.forEach(pp => {
if (checkCollision(pp, e)) {
e.hp -= 10;
if (e.hp <= 0) {
e.delete = true;
currentLevel.explosions.push(new Explosion(e.x, e.y));
}
pp.delete = true;
}
});
currentLevel.playerSpecial.forEach(sp => {
if (checkCollision(sp, e)) {
e.delete = true;
currentLevel.explosions.push(new Explosion(e.x, e.y));
if (sp.specialType === "missile") sp.delete = true;
}
});
// collision detection with player and shield
if (currentLevel.player.shieldOn) {
if (checkCollision(currentLevel.player.shield, e)) {
e.delete = true;
currentLevel.explosions.push(new Explosion(e.x, e.y));
}
}
else if (checkCollision(currentLevel.player, e)) {
if (!currentLevel.player.hit) {
currentLevel.player.hit = true;
currentLevel.explosions.push(new Explosion(e.x, e.y));
e.delete = true;
currentLevel.playerDead();
}
}
e.update(deltaTime);
if (e.delete) this.supportEnemies.splice(i, 1);
});
// forward movement
this.speedX = 0.105 * deltaTime;
if (this.x > this.xbreak && !this.retreat) this.x -= this.speedX;
// Retreat when both the torpedoes are fired & dragonfly swarm
if (this.torpedoes.length === 0) {
if (this.retreatTimer > this.retreatInterval && !this.retreat) {
this.retreat = true;
this.retreatTimer = 0;
}
if (this.retreat && this.x <= 840) {
this.x += 2; // retreat back out of the screen
} else if (!this.retreatFire && this.retreat) {
this.supportEnemies.push(new Dragonfly(40, 1040, 60, false, 4, 0, "linear", 0, 0));
this.supportEnemies.push(new Dragonfly(40, 1150, 60, false, 4, 0, "linear", 0, 0));
this.supportEnemies.push(new Dragonfly(40, 1040, 120, false, 4, 0, "linear", 0, 0));
this.supportEnemies.push(new Dragonfly(40, 1150, 120, false, 4, 0, "linear", 0, 0));
this.supportEnemies.push(new Dragonfly(40, 1040, 180, false, 4, 0, "linear", 0, 0));
this.supportEnemies.push(new Dragonfly(40, 1150, 180, false, 4, 0, "linear", 0, 0));
this.supportEnemies.push(new Dragonfly(40, 1040, 240, false, 4, 0, "linear", 0, 0));
this.supportEnemies.push(new Dragonfly(40, 1150, 240, false, 4, 0, "linear", 0, 0));
this.retreatFire = true;
}
if (this.retreat && this.retreatTimer > this.retreatInterval) {
this.retreat = false;
this.retreatFire = false;
this.retreatTimer = 0;
}
this.retreatTimer += deltaTime;
}
//update hitbox
this.hitbox.forEach(hb => hb.update(this.x, this.y));
this.draw();
}
}
// UI class for displaying lives, spAtk and score;
class UI {
constructor() {
this.image = mainSprites;
this.frameX = 6; // grid x on spritesheet
this.heartY = 3; // grid y on spritesheet
this.missileY = 0;
this.laserY = 5;
this.wallY = 7;
if (isLevelDark) {
this.heartY++;
this.missileY++;
this.laserY++;
this.wallY++;
}
this.spriteSize = 150;
this.width = 50;
this.height = 40;
}
draw() {
// drawing hearts for lives
for (let i = 0; i < lives; i++) {
bgCtx.drawImage(this.image, this.frameX * this.spriteSize, this.heartY * this.spriteSize, this.width, this.height, i * 55 + 5, 5, this.width, this.height);
}
// drawing special Attack and the counts remining;
switch (specialAtttack) {
case "missile": bgCtx.drawImage(this.image, this.frameX * this.spriteSize, this.missileY * this.spriteSize, this.width, this.height, 400, 10, this.width, this.height);
break;
case "laser": bgCtx.drawImage(this.image, this.frameX * this.spriteSize, this.laserY * this.spriteSize, this.width, this.height, 400, 5, this.width, this.height);
break;
case "wall": bgCtx.drawImage(this.image, this.frameX * this.spriteSize, this.wallY * this.spriteSize, this.width, this.height, 400, 5, this.width, this.height);
break;
}
bgCtx.save();
if (isLevelDark) bgCtx.fillStyle = "#aad69c";
else bgCtx.fillStyle = "#282828";
bgCtx.font = "bold 52px Silkscreen";
bgCtx.fillText(specialCount.toString().padStart(2, "0"), 460, 45);
//score
bgCtx.fillText(playerScore.toString().padStart(5, "0"), 600, 45);
bgCtx.restore();
}
}
// Star Particle effect in the home screen
class Particle {
constructor() {
this.initialX = Math.floor(Math.random() * 440) + 200;
this.initialY = Math.floor(Math.random() * 280) + 100;
this.x = this.initialX;
this.y = this.initialY;
this.speedX = Math.abs(420 - this.x) * 0.0025;
this.speedY = Math.random() * 0.3;
if(this.initialX <= bgCanvas.width * 0.5) this.speedX *= -1;
if(this.initialY <= bgCanvas.height * 0.5) this.speedY *= -1;
this.radius = Math.random() * 2;
this.color = "white";
this.opacity = 0.001;
this.increase = Math.random() * 0.01 + 0.001;
}
update() {
this.x += this.speedX;
this.y += this.speedY;
this.opacity += this.increase;
if(this.x < 0 || this. x > 840 || this.y < 0 || this.y > 480){
this.x = this.initialX;
this.y = this.initialY;
this.opacity = 0.001;
}
this.draw();
}
draw() {
bgCtx.save();
bgCtx.globalAlpha = this.opacity;
bgCtx.beginPath();
bgCtx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
bgCtx.fillStyle = this.color;
bgCtx.fill();
bgCtx.closePath();
bgCtx.restore();
}
}
const particles = [];
for(let i = 0; i< 100; i++){
particles.push(new Particle());
}
// Background classes
class Background {
constructor(level, frameX) {
this.level = level;
this.frameX = frameX;
this.speed = 0;
this.hitbox = [];
}
update(deltaTime) {
if (!this.level.bgStop) {
this.speed = 0.105 * deltaTime; // 0.105 comes from 840px in 8000ms.
this.x -= this.speed;
}
this.hitbox.forEach(hb => hb.update(this.x, this.y));
if (this.x + this.width < 0) this.delete = true;
this.draw();
}
draw() {
bgCtx.drawImage(this.image, this.frameX * this.spriteSize, this.spriteY, this.width, this.height, this.x, this.y, this.width, this.height);
}
}
class Background2 extends Background {
constructor(level, frameX, index) {
super(level, frameX);
this.image = bgSprites1;
this.spriteY = 0; // y position in px on sprite sheet
this.spriteSize = 400;
this.width = 320;
switch (frameX) {
case 0: this.height = 160;
break;
case 1: this.height = 80;
break;
case 2: this.height = 60;
break;
}
this.x = this.width * index;
this.y = bgCanvas.height - this.height;
this.delete = false; // marked for deletion
}
}
class Background3 extends Background {
constructor(level, frameX, index) {
super(level, frameX);
this.image = bgSprites1;
this.spriteY = 200; //y position in px on sprite sheet
this.spriteSize = 400;
this.width = 320;
this.x = this.width * index;
this.hitbox = [];
switch (frameX) {
case 0: this.height = 70;
this.y = bgCanvas.height - this.height;
this.hitbox.push(new Hitbox(this.x, this.y, this.width, this.height - 10, 0, 10, false));
break;
case 1: this.height = 80;
this.y = bgCanvas.height - this.height;
this.hitbox.push(new Hitbox(this.x, this.y, this.width, this.height - 20, 0, 20, false));
break;
case 2: this.height = 160;
this.y = bgCanvas.height - this.height;
this.hitbox.push(new Hitbox(this.x, this.y, 60, 60, 0, 100, false));
this.hitbox.push(new Hitbox(this.x, this.y, 30, 130, 60, 30, false));
this.hitbox.push(new Hitbox(this.x, this.y, 100, 150, 90, 10, false));
this.hitbox.push(new Hitbox(this.x, this.y, 60, 120, 230, 40, false));
break;
}
this.delete = false; // marked for deletion
}
}
class Background4 extends Background {
constructor(level, frameX, index) {
super(level, frameX);
this.image = bgSprites1;
this.spriteY = 400; //y position in px on sprite sheet
this.spriteSize = 400;
this.width = 320;
this.x = this.width * index;
switch (frameX) {
case 0: this.height = 70;
this.y = bgCanvas.height - this.height;
this.hitbox.push(new Hitbox(this.x, this.y, this.width, this.height - 20, 0, 20, false));
break;
case 1: this.height = 80;
this.y = bgCanvas.height - this.height;
this.hitbox.push(new Hitbox(this.x, this.y, this.width, this.height - 20, 0, 20, false));
break;
case 2: this.height = 160;
this.y = bgCanvas.height - this.height;
this.hitbox.push(new Hitbox(this.x, this.y, this.width, 60, 0, 100, false));
this.hitbox.push(new Hitbox(this.x, this.y, 60, 150, 100, 10, false));
}
this.delete = false; // marked for deletion
}
}
class Background5 extends Background {
constructor(level, frameX, index) {
super(level, frameX);
this.image = bgSprites1;
this.spriteY = 600; //y position in px on sprite sheet
this.spriteSize = 400;
this.width = 320;
this.x = this.width * index;
this.y = 50;
switch (frameX) {
case 0: this.height = 170;
break;
case 1: this.height = 80;
break;
case 2: this.height = 50;
break;
}
this.hitbox.push(new Hitbox(this.x, this.y, this.width - 40, this.height - 5, 20, 0, false));
this.delete = false; // marked for deletion
}
}
class Background7 extends Background {
constructor(level, frameX, index) {
super(level, frameX);
this.image = bgSprites2;
this.spriteY = 0; //y position in px on sprite sheet
this.spriteSize = 400;
this.width = 2800;
this.height = 160;
this.x = this.width * index;
this.y = bgCanvas.height - this.height;
this.delete = false; // marked for deletion
this.hitbox = [
new Hitbox(this.x, 430, this.width, 50, 0, 110, false),
new Hitbox(this.x, this.y, 60, 150, 1880, 10, false),
new Hitbox(this.x, this.y, 60, 150, 2200, 10, false)
];
}
}
class Background8 extends Background {
constructor(level, frameX, index) {
super(level, frameX);
this.image = bgSprites2;
this.spriteY = 200; //y position in px on sprite sheet
this.spriteSize = 400;
this.width = 2800;
this.height = 160;
this.x = this.width * index;
this.y = bgCanvas.height - this.height;
this.delete = false; // marked for deletion
this.hitbox = [
new Hitbox(this.x, 430, this.width, 50, 0, 110, false),
new Hitbox(this.x, this.y, 110, 100, 670, 60, false),
new Hitbox(this.x, this.y, 100, 150, 780, 10, false),
new Hitbox(this.x, this.y, 110, 100, 990, 60, false),
new Hitbox(this.x, this.y, 100, 150, 1100, 10, false),
new Hitbox(this.x, this.y, 110, 100, 1950, 60, false),
new Hitbox(this.x, this.y, 100, 150, 2060, 10, false)
];
}
}
class Level {
constructor(level, isDark) {
this.active = true;
this.number = level; // level number
isLevelDark = isDark;
if (isLevelDark) bgCanvas.style.background = "#282828";
else bgCanvas.style.background = "#aad69c";
this.player = new Player(this);
this.input = new InputHandler(this);
this.ui = new UI();
this.sourceEnemyArray = [];
this.enemies = [];
this.playerProjectiles = [];
this.playerSpecial = []; // special weapons
this.enemyProjectiles = [];
this.background = [];
this.explosions = [];
this.i = 0; // index for global level enemy array
this.levelTime = 0;
this.flag = true; // prevents index overflow of enemy array
this.bgStop = false; // stops background
this.levelComplete = false;
// create background and assigns enemy array of the current level
switch (this.number) {
case 1: this.sourceEnemyArray = enemiesLvl1;
break;
case 2: this.sourceEnemyArray = enemiesLvl2;
bgArray2.forEach((frame, index) => this.background.push(new Background2(this, frame, index)));
break;
case 3: this.sourceEnemyArray = enemiesLvl3;
bgArray3.forEach((frame, index) => this.background.push(new Background3(this, frame, index)));
break;
case 4: this.sourceEnemyArray = enemiesLvl4;
bgArray4.forEach((frame, index) => this.background.push(new Background4(this, frame, index)));
break;
case 5: this.sourceEnemyArray = enemiesLvl5;
bgArray5.forEach((frame, index) => this.background.push(new Background5(this, frame, index)));
break;
case 6: this.sourceEnemyArray = enemiesLvl6;
bgArray6.forEach((frame, index) => this.background.push(new Background5(this, frame, index))); // lvl 5 & 6 have same bg
break;
case 7: this.sourceEnemyArray = enemiesLvl7;
bgArray7.forEach((frame, index) => this.background.push(new Background7(this, frame, index)));
break;
case 8: this.sourceEnemyArray = enemiesLvl8;
bgArray8.forEach((frame, index) => this.background.push(new Background8(this, frame, index)));
break;
}
}
update(deltaTime) {
// ui draw
this.ui.draw();
// player update
if (!this.player.delete) this.player.update(deltaTime);
// player projectiles
this.playerProjectiles.forEach(projectile => projectile.update());
this.playerProjectiles = this.playerProjectiles.filter(projectile => !projectile.delete);
// player special attack
this.playerSpecial.forEach(special => special.update(deltaTime));
this.playerSpecial = this.playerSpecial.filter(special => !special.delete);
// pushing enemies from source array into the level enemies array
if (this.flag && this.levelTime > this.sourceEnemyArray[this.i].time) {
this.swarm(this.sourceEnemyArray[this.i].object);
this.i++;
if (this.i >= this.sourceEnemyArray.length) {
this.flag = false;
this.bgStop = true;
}
}
//updating all the enemies, and thier collision detection
this.enemies.forEach(enemy => {
enemy.update(deltaTime);
// interaction with player projectile;
this.playerProjectiles.forEach(pp => {
if (enemy.isBoss) { // for boss
enemy.hitbox.forEach(hb => {
if (checkCollision(hb, pp)) {
pp.delete = true;
if (!hb.immune) {
enemy.hp -= 5;
playerScore += 5;
if (enemy.hp <= 0) {
playerScore += enemy.score;
for (let i = 0; i < 3; i++) { //add 3 explosions
this.explosions.push(new Explosion(Math.random() * enemy.width + enemy.x, Math.random() * enemy.height + enemy.y));
}
this.levelComplete = true;
enemy.delete = true;
}
}
}
});
}
else if (checkCollision(pp, enemy)) { // for others
if (enemy.isPowerUp) pp.delete = true;
else {
pp.delete = true;
enemy.hp -= 10;
if (enemy.hp <= 0) {
this.explosions.push(new Explosion(enemy.x, enemy.y));
playerScore += enemy.score;
enemy.delete = true;
}
}
}
});
//interaction with player special attack
this.playerSpecial.forEach(sp => {
if (enemy.isBoss) { // for boss
enemy.hitbox.forEach(hb => {
if (checkCollision(hb, sp)) {
if (sp.specialType === "missile") sp.delete = true;
if (!hb.immune) {
if (!sp.hit) {
enemy.hp -= sp.damage;
sp.hit = true;
playerScore += sp.damage;
if (enemy.hp <= 0) {
playerScore += enemy.score;
for (let i = 0; i < 3; i++) { //add 3 explosions
this.explosions.push(new Explosion(Math.random() * enemy.width + enemy.x, Math.random() * enemy.height + enemy.y));
}
this.levelComplete = true;
enemy.delete = true;
}
}
}
}
});
}
else if (!enemy.isPowerUp) { // everyone except powerup
if (checkCollision(sp, enemy)) {
if (!enemy.hit || !sp.hit) {
enemy.hp -= sp.damage;
enemy.hit = true;
sp.hit = true;
if (enemy.hp <= 0) {
this.explosions.push(new Explosion(enemy.x, enemy.y));
playerScore += enemy.score;
enemy.delete = true;
}
}
if (sp.specialType === "missile") sp.delete = true;
}
}
});
//interaction with player and its shield
if (enemy.isPowerUp) {
if (checkCollision(enemy, this.player.hitbox)) {
enemy.delete = true;
if (enemy.powerup === "life") {
if (lives === maxLives) specialCount++;
else lives++;
}
else if (specialAtttack === enemy.powerup) {
if (specialAtttack === "wall") specialCount++;
else specialCount += 3;
}
else {
specialAtttack = enemy.powerup;
if (specialAtttack == "wall") specialCount = 1;
else specialCount = 3;
}
}
}
else if (enemy.isBoss) {
enemy.hitbox.forEach(hb => {
if (this.player.shieldOn) { //when shieldOn check with shield
if (checkCollision(hb, this.player.shield)) {
enemy.hp -= 1;
playerScore += 1;
if (enemy.hp <= 0) {
playerScore += enemy.score;
for (let i = 0; i < 3; i++) { //add 3 explosions
this.explosions.push(new Explosion(Math.random() * enemy.width + enemy.x, Math.random() * enemy.height + enemy.y));
}
this.levelComplete = true;
enemy.delete = true;
}
}
}
else if (checkCollision(hb, this.player.hitbox)) {
if (!this.player.hit) {
this.player.hit = true;
this.explosions.push(new Explosion(this.player.x, this.player.y));
this.playerDead();
}
}
});
}
else {
if (this.player.shieldOn) { //other enemies, when shield is on
if (checkCollision(this.player.shield, enemy)) {
playerScore += enemy.score;
this.explosions.push(new Explosion(enemy.x, enemy.y));
enemy.delete = true;
}
}
// other enemies when shield is off
else if (checkCollision(enemy, this.player.hitbox)) {
if (!this.player.hit) {
this.player.hit = true;
this.explosions.push(new Explosion(this.player.x, this.player.y));
this.explosions.push(new Explosion(enemy.x, enemy.y));
enemy.delete = true;
this.playerDead();
}
}
}
});
this.enemies = this.enemies.filter(enemy => !enemy.delete);
//enemy projectiles
this.enemyProjectiles.forEach(projectile => {
projectile.update();
// interaction with player projectile
this.playerProjectiles.forEach(pp => {
if (checkCollision(projectile, pp)) {
this.explosions.push(new Explosion(pp.x, pp.y));
pp.delete = true;
projectile.delete = true;
}
});
// interaction with player special attack
this.playerSpecial.forEach(sp => {
if (checkCollision(projectile, sp)) {
projectile.delete = true;
}
});
// interaction with player
if (!this.player.shieldOn && checkCollision(this.player.hitbox, projectile)) {
if (!this.player.hit && this.active) {
this.player.hit = true;
projectile.delete = true;
this.explosions.push(new Explosion(this.player.x, this.player.y));
this.playerDead();
}
}
//interaction with player shield
if (this.player.shieldOn) {
if (checkCollision(this.player.shield, projectile)) {
projectile.delete = true;
}
}
});
this.enemyProjectiles = this.enemyProjectiles.filter(projectile => !projectile.delete);
// explosions update
this.explosions.forEach(explosion => explosion.update());
this.explosions = this.explosions.filter(explosion => !explosion.delete);
// background and collision detection with player and projectiles
this.background.forEach(bg => {
bg.update(deltaTime);
bg.hitbox.forEach(hb => {
if (checkCollision(this.player.hitbox, hb)) {
if (!this.player.hit) {
this.player.hit = true;
this.explosions.push(new Explosion(this.player.x, this.player.y));
this.playerDead();
}
}
this.playerProjectiles.forEach(pp => {
if (checkCollision(pp, hb)) {
pp.delete = true;
}
})
})
});
this.background = this.background.filter(bg => !bg.delete);
//level complete;
if (this.levelComplete) {
this.active = false;
if (this.number != 5 && this.number != 6) {
if (this.player.y <= 55) this.player.x += 5;
else this.player.y -= 2;
}
else {
if (this.player.y + this.player.height >= 400) this.player.x += 5;
else this.player.y += 2;
}
//player crosses canvas width and next level
if (this.player.x >= 840) {
if (this.number === 8) {
gameOver = true;
}
else if (this.number != 4 && this.number != 5) {
nextLevel(this.number + 1, false);
}
else {
nextLevel(this.number + 1, true);
}
}
}
this.levelTime += deltaTime;
}
swarm(array) {
array.forEach(object => {
this.enemies.push(object);
})
}
playerDead() {
this.player.delete = true;
lives--;
if (lives > 0) setTimeout(() => { this.player = new Player(); }, 500);
else setTimeout(() => {
gameOver = true;
}, 500);
}
}
// all level enemies source array
const enemiesLvl1 = [
{
time: 2000,
object: [
new Meteor(10, 840, 80, false, 3.5, 0, "linear", 0, 0),
new Meteor(10, 1040, 80, false, 3.5, 0, "linear", 0, 0),
new Meteor(10, 1240, 80, false, 3.5, 0, "linear", 0, 0)
]
},
{
time: 5000,
object: [
new Meteor(10, 840, 200, false, 3.5, 0, "linear", 0, 0),
new Meteor(10, 1040, 200, false, 3.5, 0, "linear", 0, 0)
]
},
{
time: 8000,
object: [
new Meteor(10, 840, 200, false, 2, 0, "linear", 0, 0),
new Meteor(10, 1040, 200, false, 2, 0, "linear", 0, 0)
]
},
{
time: 12000,
object: [
new Meteor(10, 840, 300, false, 3, 0, "linear", 0, 0),
new Meteor(10, 1040, 300, false, 3, 0, "linear", 0, 0),
new Meteor(10, 1240, 300, false, 3, 0, "linear", 0, 0)
]
},
{
time: 15000,
object: [
new Meteor(25, 840, 80, false, 3, 0, "linear", 0, 0),
new PowerUp(950, 70, 3, 0, "linear", 0, 0),
new Meteor(25, 1120, 80, false, 3, 0, "linear", 0, 0)
]
},
{
time: 21000,
object: [
new Triship(15, 840, 50, false, 2, 1.5, "zigzag", 300, 620),
new Triship(15, 960, 200, false, 2, 0.03, "wave", 120, 200),
new Triship(15, 1080, 50, false, 2, 1.5, "zigzag", 300, 620),
new Triship(15, 1200, 200, false, 2, 0.03, "wave", 120, 200),
new Triship(15, 1320, 50, false, 2, 1.5, "zigzag", 300, 620),
new Triship(15, 1440, 200, false, 2, 0.03, "wave", 120, 200),
new Triship(15, 1560, 50, false, 2, 1.5, "zigzag", 300, 620),
new Triship(15, 1680, 200, false, 2, 0.03, "wave", 120, 200)
]
},
{
time: 35500,
object: [new Squid(10, 840, 280, false, 3, 0, "linear", 0, 0)]
},
{
time: 38000,
object: [
new Squid(10, 840, 70, false, 3, 0, "linear", 0, 0),
new Squid(10, 1000, 70, false, 3, 0, "linear", 0, 0)
]
},
{
time: 41000,
object: [new Squid(10, 840, 200, true, 2, 0.03, "wave", 120, 200)]
},
{
time: 42500,
object: [new Squid(10, 840, 200, true, 2, 0.03, "wave", 120, 200)]
},
{
time: 44000,
object: [new Squid(10, 840, 200, true, 2, 0.03, "wave", 120, 200)]
},
{
time: 45500,
object: [new Squid(10, 840, 200, true, 2, 0.03, "wave", 120, 200)]
},
{
time: 49000,
object: [
new Squid(10, 840, 50, true, 3, 2, "zigzag", 300, 620),
new Squid(10, 1040, 50, true, 3, 2, "zigzag", 300, 620),
new Squid(10, 1240, 50, true, 3, 2, "zigzag", 300, 620),
new Squid(10, 1440, 50, true, 3, 2, "zigzag", 300, 620)
]
},
{
time: 57000,
object: [new PowerUp(840, 280, 3, 0, "linear", 0, 0)]
},
{
time: 61000,
object: [
new Shuttle(15, 840, 50, false, 3, 0, "linear", 0, 0),
new Shuttle(15, 1000, 110, false, 2, 0, "linear", 0, 0),
new Shuttle(15, 1100, 60, false, 2, 0, "linear", 0, 0),
new Shuttle(15, 1300, 330, false, 3, 0, "linear", 0, 0)
]
},
{
time: 68000,
object: [new Boss1()] //boss
}
];
isLevelDark = false;//solves bug, otherwise need to declare level before
const enemiesLvl2 = [
{
time: 2000,
object: [
new Triship(15, 840, 50, false, 2, 1, "zigzag", 300, 780),
new Triship(15, 840, 300, false, 2, -1, "zigzag", 300, 780),
new Triship(15, 960, 50, false, 2, 1, "zigzag", 300, 780),
new Triship(15, 960, 300, true, 2, -1, "zigzag", 300, 780),
new Triship(15, 1080, 50, true, 2, 1, "zigzag", 300, 780),
new Triship(15, 1080, 300, false, 2, -1, "zigzag", 300, 780)
]
},
{
time: 8000,
object: [
new Saucer(20, 840, 100, false, 2, 0.03, "wave", 50, 100),
new Saucer(20, 840, 340, false, 2, 0.03, "wave", 50, 340)
]
},
{
time: 9000,
object: [
new Saucer(20, 840, 100, false, 2, 0.03, "wave", 50, 100),
new Saucer(20, 840, 340, true, 2, 0.03, "wave", 50, 340),
new PowerUp(840, 220, 3, 0, "linear", 0, 0)
]
},
{
time: 10000,
object: [
new Saucer(20, 840, 100, true, 2, 0.03, "wave", 50, 100),
new Saucer(20, 840, 340, false, 2, 0.03, "wave", 50, 340)
]
},
{
time: 13000,
object: [
new Squid(15, 840, 50, true, 2, 1, "zigzag", 250, 780),
new Squid(15, 1200, 130, true, 3, 0.07, "wave", 40, 130),
new Squid(15, 1200, 350, true, 2, 0.05, "wave", 40, 350),
new Squid(15, 1800, 240, true, 3, 0, "linear", 0, 0),
new Squid(15, 2000, 120, true, 3, 0, "linear", 0, 0)
]
},
{
time: 21000,
object: [
new Squid(15, 840, 240, false, 3, 0, "linear", 0, 0),
new Squid(15, 940, 170, false, 3, 0, "linear", 0, 0),
new Squid(15, 940, 310, false, 3, 0, "linear", 0, 0),
new Squid(15, 1040, 100, false, 3, 0, "linear", 0, 0),
new Squid(15, 1040, 380, false, 3, 0, "linear", 0, 0)
]
},
{
time: 24000,
object: [
new Squid(15, 840, 240, false, 3, 0, "linear", 0, 0),
new Squid(15, 940, 170, false, 3, 0, "linear", 0, 0),
new Squid(15, 940, 310, false, 3, 0, "linear", 0, 0),
new Squid(15, 1040, 100, false, 3, 0, "linear", 0, 0),
new Squid(15, 1040, 380, false, 3, 0, "linear", 0, 0)
]
},
{
time: 28000,
object: [
new Shuttle(25, 840, 250, false, 4, 0, "linear", 0, 0),
new Shuttle(25, 960, 250, false, 4, 0, "linear", 0, 0),
new Shuttle(25, 1080, 250, false, 4, 0, "linear", 0, 0),
new Shuttle(25, 1200, 250, false, 4, 0, "linear", 0, 0),
new Shuttle(25, 1320, 250, false, 4, 0, "linear", 0, 0)
]
},
{
time: 30000,
object: [
new Shuttle(25, 840, 70, false, 3, 0, "linear", 0, 0),
new Shuttle(25, 1040, 70, false, 3, 0, "linear", 0, 0),
new Shuttle(25, 1240, 250, false, 3, 0, "linear", 0, 0),
new Shuttle(25, 1440, 220, false, 3, 0, "linear", 0, 0),
new Shuttle(25, 1640, 150, false, 3, 0, "linear", 0, 0),
new Shuttle(25, 1840, 220, false, 3, 0, "linear", 0, 0)
]
},
{
time: 38000,
object: [new Boss2()] //boss
}
];
const enemiesLvl3 = [
{
time: 2000,
object: [
new Tadpole(15, 840, 50, true, 2, 1, "zigzag", 250, 750),
new Tadpole(15, 1020, 50, true, 2, 1, "zigzag", 250, 750),
new Tadpole(15, 1200, 50, true, 2, 1, "zigzag", 250, 750),
new Tadpole(15, 1380, 50, true, 2, 1, "zigzag", 250, 750),
new Tadpole(15, 1560, 50, true, 2, 1, "zigzag", 250, 750),
new Tadpole(15, 1740, 50, true, 2, 1, "zigzag", 250, 750),
new Tadpole(15, 1920, 50, true, 2, 1, "zigzag", 250, 750),
new Tadpole(15, 2100, 50, true, 2, 1, "zigzag", 250, 750),
new Tadpole(15, 2280, 50, true, 2, 1, "zigzag", 250, 750)
]
},
{
time: 16000,
object: [
new Saucer(15, 840, 280, false, 4, 0, "linear", 0, 0),
new Saucer(15, 1040, 280, false, 4, 0, "linear", 0, 0),
new Saucer(15, 1240, 280, false, 4, 0, "linear", 0, 0),
new PowerUp(1340, 280, 4, 0, "linear", 0, 0)
]
},
{
time: 22000,
object: [
new Squid(20, 840, 180, false, 4, 0, "linear", 0, 0),
new Squid(20, 1000, 120, false, 4, 0, "linear", 0, 0),
new Squid(20, 1000, 240, false, 4, 0, "linear", 0, 0),
new Squid(20, 1160, 60, false, 4, 0, "linear", 0, 0),
new Squid(20, 1160, 300, false, 4, 0, "linear", 0, 0)
]
},
{
time: 26000,
object: [
new Squid(20, 840, 180, false, 4, 0, "linear", 0, 0),
new Squid(20, 1000, 120, false, 4, 0, "linear", 0, 0),
new Squid(20, 1000, 240, false, 4, 0, "linear", 0, 0),
new Squid(20, 1160, 60, false, 4, 0, "linear", 0, 0),
new Squid(20, 1160, 300, false, 4, 0, "linear", 0, 0)
]
},
{
time: 30000,
object: [new Marble1(20, 840, 110, true, 3, 0.04, "wave", 50, 110)]
},
{
time: 30300,
object: [new Marble2(20, 840, 110, true, 3, 0.04, "wave", 50, 110)]
},
{
time: 30600,
object: [
new Marble3(20, 840, 110, true, 3, 0.04, "wave", 50, 110),
new Marble1(20, 840, 270, true, 3, 0.04, "wave", 50, 270)
]
},
{
time: 30900,
object: [new Marble2(20, 840, 270, true, 3, 0.04, "wave", 50, 270)]
},
{
time: 31200,
object: [new Marble3(20, 840, 270, true, 3, 0.04, "wave", 50, 270)]
},
{
time: 33000,
object: [new Kraken(150, 840, 150, true, 3, 2, "mini1", 0, 480)]
},
{
time: 51000,
object: [new Boss3()] //boss
}
];
const enemiesLvl4 = [
{
time: 2000,
object: [
new Beetle(15, 840, 50, true, 3, 1, "zigzag", 700, 800),
new Beetle(15, 990, 50, true, 3, 1, "zigzag", 700, 800),
new Beetle(15, 1140, 50, false, 3, 1, "zigzag", 700, 800),
new Beetle(15, 1290, 50, true, 3, 1, "zigzag", 700, 800),
new Beetle(15, 1440, 50, false, 3, 1, "zigzag", 700, 800)
]
},
{
time: 6000,
object: [
new Beetle(15, 840, 50, true, 3, 2, "zigzag", 400, 700),
new Beetle(15, 990, 50, false, 3, 2, "zigzag", 400, 700),
new Beetle(15, 1140, 50, true, 3, 2, "zigzag", 400, 700),
new Beetle(15, 1290, 50, false, 3, 2, "zigzag", 400, 700),
new Beetle(15, 1440, 50, true, 3, 2, "zigzag", 400, 700)
]
},
{
time: 10000,
object: [
new Beetle(15, 840, 270, true, 3, -2, "zigzag", 370, 700),
new Beetle(15, 990, 270, true, 3, -2, "zigzag", 370, 700),
new Beetle(15, 1140, 270, false, 3, -2, "zigzag", 370, 700),
new Beetle(15, 1290, 270, false, 3, -2, "zigzag", 370, 700),
new Beetle(15, 1440, 270, true, 3, -2, "zigzag", 370, 700)
]
},
{
time: 14500,
object: [new Shuttle(15, 840, 250, false, 3, 0, "linear", 0, 0)]
},
{
time: 17500,
object: [
new Shuttle(15, 840, 120, false, 3, 0, "linear", 0, 0),
new Shuttle(15, 980, 120, false, 3, 0, "linear", 0, 0),
new Shuttle(15, 1120, 120, false, 3, 0, "linear", 0, 0)
]
},
{
time: 20500,
object: [new Shuttle(25, 840, 250, false, 3, 0, "linear", 0, 0)]
},
{
time: 24000,
object: [
new Shuttle(15, 840, 180, false, 4, 0, "linear", 0, 0),
new Shuttle(15, 980, 180, false, 4, 0, "linear", 0, 0),
new Shuttle(15, 1120, 180, false, 4, 0, "linear", 0, 0)
]
},
{
time: 29300,
object: [new PowerUp(840, 180, 1.788, 0.07, "wave", 80, 180)]
},
{
time: 36000,
object: [
new Rock(80, 840, 130, false, 2, 0, "linear", 0, 0),
new Rock(80, 1040, 180, false, 2, 0, "linear", 0, 0),
new Rock(80, 1240, 240, false, 2, 0, "linear", 0, 0),
new Rock(80, 1440, 90, false, 2, 0, "linear", 0, 0),
new Rock(80, 1640, 250, false, 2, 0, "linear", 0, 0)
]
},
{
time: 50500,
object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)]
},
{
time: 51000,
object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)]
},
{
time: 51500,
object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)]
},
{
time: 52000,
object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)]
},
{
time: 52500,
object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)]
},
{
time: 53000,
object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)]
},
{
time: 53500,
object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)]
},
{
time: 54000,
object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)]
},
{
time: 54500,
object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 90, 190)]
},
{
time: 58000,
object: [
new Triship(20, 840, 50, true, 3, 3, "zigzag", 600, 800),
new Triship(20, 940, 50, true, 3, 3, "zigzag", 600, 800),
new Triship(20, 1160, 50, true, 3, 3, "zigzag", 600, 800),
new Triship(20, 1260, 50, true, 3, 3, "zigzag", 600, 800),
new Triship(20, 1480, 50, true, 3, 3, "zigzag", 600, 800),
new Triship(20, 1580, 50, true, 3, 3, "zigzag", 600, 800)
]
},
{
time: 66500,
object: [new Boss4()] //boss
}
];
isLevelDark = true;
const enemiesLvl5 = [
{
time: 2000,
object: [
new Marble3(15, 840, 440, true, 2.8, -2, "zigzag", 300, 600),
new Marble3(15, 1000, 440, true, 2.8, -2, "zigzag", 300, 600),
new Marble3(15, 1160, 440, true, 2.8, -2, "zigzag", 300, 600),
new Marble3(15, 1320, 440, true, 2.8, -2, "zigzag", 300, 600),
new Marble3(15, 1480, 440, true, 2.8, -2, "zigzag", 300, 600)
]
},
{
time: 4000,
object: [
new Marble3(15, 840, 410, false, 3.5, 0, "linear", 0, 0),
new Marble3(15, 1000, 410, false, 3.5, 0, "linear", 0, 0),
new Marble3(15, 1160, 410, false, 3.5, 0, "linear", 0, 0),
new Marble3(15, 1320, 410, false, 3.5, 0, "linear", 0, 0),
new Marble3(15, 1480, 410, false, 3.5, 0, "linear", 0, 0)
]
},
{
time: 9500,
object: [new Kraken(100, 840, 390, true, 2, -3, "zigzag", 500, 600)]
},
{
time: 16000,
object: [new PowerUp(840, 300, 2, 0.07, "wave", 100, 300)]
},
{
time: 21000,
object: [
new Tadpole(15, 840, 320, true, 3, 0.07, "wave", 110, 320),
new Tadpole(15, 940, 320, true, 2, 0.05, "wave", 110, 320),
new Tadpole(15, 1040, 320, false, 2, 0.08, "wave", 110, 320),
new Tadpole(15, 1140, 320, false, 3, 0.03, "wave", 110, 320),
new Tadpole(15, 1240, 320, true, 3, 0.09, "wave", 110, 320),
new Tadpole(15, 1340, 320, false, 2, 0.04, "wave", 110, 320),
new Tadpole(15, 1440, 320, false, 3, 0.06, "wave", 110, 320),
new Tadpole(15, 1540, 320, true, 3, 0.03, "wave", 110, 320),
new Tadpole(15, 1640, 320, false, 3, 0.07, "wave", 110, 320)
]
},
{
time: 30000,
object: [
new Squid(25, 840, 320, false, 4, 0, "linear", 0, 0),
new Squid(25, 980, 230, false, 4, 0, "linear", 0, 0),
new Squid(25, 980, 410, false, 4, 0, "linear", 0, 0)
]
},
{
time: 32000,
object: [
new Squid(25, 840, 320, false, 4, 0, "linear", 0, 0),
new Squid(25, 980, 230, false, 4, 0, "linear", 0, 0),
new Squid(25, 980, 410, false, 4, 0, "linear", 0, 0)
]
},
{
time: 34000,
object: [
new Triship(15, 840, 210, false, 2, 1, "zigzag", 225, 600),
new Triship(15, 840, 410, false, 2, -1, "zigzag", 225, 600),
new Triship(15, 1000, 210, false, 2, 1, "zigzag", 225, 600),
new Triship(15, 1000, 410, true, 2, -1, "zigzag", 225, 600),
new Triship(15, 1160, 210, true, 2, 1, "zigzag", 225, 600),
new Triship(15, 1160, 410, false, 2, -1, "zigzag", 225, 600),
new Triship(15, 1320, 210, false, 2, 1, "zigzag", 225, 600),
new Triship(15, 1320, 410, true, 2, -1, "zigzag", 225, 600),
new Triship(15, 1480, 210, true, 2, 1, "zigzag", 225, 600),
new Triship(15, 1480, 410, false, 2, -1, "zigzag", 225, 600)
]
},
{
time: 46000,
object: [
new Squid(25, 840, 160, true, 3, 2, "zigzag", 400, 760),
new Squid(25, 980, 160, true, 3, 2, "zigzag", 400, 760),
new Squid(25, 1120, 160, true, 3, 2, "zigzag", 400, 760),
new Squid(25, 1260, 160, true, 3, 2, "zigzag", 400, 760),
new Squid(25, 1400, 160, true, 3, 2, "zigzag", 400, 760)
]
},
{
time: 53000,
object: [
new Squid(15, 840, 430, true, 3, -2, "zigzag", 350, 650),
new Squid(15, 980, 430, true, 3, -2, "zigzag", 350, 650),
new Squid(15, 1120, 430, true, 3, -2, "zigzag", 350, 650),
new Squid(15, 1260, 430, true, 3, -2, "zigzag", 350, 650),
new Squid(15, 1400, 430, true, 3, -2, "zigzag", 350, 650)
]
},
{
time: 56000,
object: [new Squid(150, 840, 180, true, 1.8, -2, "mini2", 300, 450)]
},
{
time: 75200,
object: [new Boss5()] //boss
}
];
const enemiesLvl6 = [
{
time: 2000,
object: [
new Triship(15, 840, 350, false, 6, 0, "linear", 0, 0),
new Triship(15, 980, 300, false, 6, 0, "linear", 0, 0),
new Triship(15, 1120, 250, false, 6, 0, "linear", 0, 0),
new Triship(15, 1260, 200, false, 6, 0, "linear", 0, 0),
new Triship(15, 1400, 150, false, 6, 0, "linear", 0, 0)
]
},
{
time: 4000,
object: [
new Squid(15, 840, 220, true, 4, 2, "zigzag", 300, 700),
new Squid(15, 960, 220, true, 4, 2, "zigzag", 300, 700),
new Squid(15, 1080, 220, true, 4, 2, "zigzag", 300, 700),
new Squid(15, 1200, 220, true, 4, 2, "zigzag", 300, 700),
new Squid(15, 1320, 220, true, 4, 2, "zigzag", 300, 700)
]
},
{
time: 7500,
object: [
new Triship(15, 840, 410, false, 5, 0, "linear", 0, 0),
new Triship(15, 980, 360, false, 5, 0, "linear", 0, 0),
new Triship(15, 1120, 310, false, 5, 0, "linear", 0, 0),
new Triship(15, 1260, 260, false, 5, 0, "linear", 0, 0),
new Triship(15, 1400, 210, false, 5, 0, "linear", 0, 0)
]
},
{
time: 10000,
object: [new PowerUp(840, 240, 4, 0, "linear", 0, 0)]
},
{
time: 12000,
object: [new Beetle(15, 840, 350, true, 4, 0.04, "wave", 50, 350)]
},
{
time: 12500,
object: [new Beetle(15, 840, 350, true, 4, 0.04, "wave", 50, 350)]
},
{
time: 13000,
object: [new Beetle(15, 840, 350, true, 4, 0.04, "wave", 50, 350)]
},
{
time: 13500,
object: [new Beetle(15, 840, 350, true, 4, 0.04, "wave", 50, 350)]
},
{
time: 15000,
object: [
new Saucer(20, 840, 210, true, 4, 0.05, "linear", 50, 100),
new Saucer(20, 840, 380, true, 4, 0.06, "wave", 50, 380),
new PowerUp(840, 270, 4, 0, "linear", 0, 0)
]
},
{
time: 15500,
object: [
new Saucer(20, 840, 210, true, 4, 0.04, "linear", 50, 100),
new Saucer(20, 840, 380, true, 4, 0.06, "wave", 50, 380)
]
},
{
time: 16000,
object: [
new Saucer(20, 840, 210, true, 4, 0.04, "linear", 50, 100),
new Saucer(20, 840, 380, true, 4, 0.06, "wave", 50, 380)
]
},
{
time: 20000,
object: [
new Triship(15, 840, 210, false, 4, 4, "zigzag", 400, 600),
new Triship(15, 840, 410, false, 4, -4, "zigzag", 400, 600),
new Triship(15, 1000, 210, false, 4, 4, "zigzag", 400, 600),
new Triship(15, 1000, 410, true, 4, -4, "zigzag", 400, 600),
new Triship(15, 1160, 210, true, 4, 4, "zigzag", 400, 600),
new Triship(15, 1160, 410, false, 4, -4, "zigzag", 400, 600)
]
},
{
time: 22000,
object: [new Beetle(15, 840, 420, true, 3, 0, "linear", 0, 0)]
},
{
time: 25000,
object: [new Marble2(15, 840, 320, true, 4, 0.06, "wave", 110, 320)]
},
{
time: 25400,
object: [new Marble2(15, 840, 320, true, 4, 0.06, "wave", 110, 320)]
},
{
time: 25800,
object: [new Marble2(15, 840, 320, true, 4, 0.06, "wave", 110, 320)]
},
{
time: 26200,
object: [new Marble2(15, 840, 320, true, 4, 0.06, "wave", 110, 320)]
},
{
time: 26600,
object: [new Marble2(15, 840, 320, true, 4, 0.06, "wave", 110, 320)]
},
{
time: 27000,
object: [new Marble2(15, 840, 320, true, 4, 0.06, "wave", 110, 320)]
},
{
time: 28000,
object: [new Flipper(25, 840, 320, true, 3, 0.06, "wave", 100, 320)]
},
{
time: 28700,
object: [new Flipper(25, 840, 320, true, 3, 0.06, "wave", 100, 320)]
},
{
time: 29400,
object: [new Flipper(25, 840, 320, true, 3, 0.06, "wave", 100, 320)]
},
{
time: 30100,
object: [new Flipper(25, 840, 320, true, 3, 0.06, "wave", 100, 320)]
},
{
time: 32300,
object: [
new Dragonfly(15, 840, 210, true, 3.5, 3, "zigzag", 350, 600),
new Dragonfly(15, 840, 430, true, 3.5, -3, "zigzag", 350, 600),
new Dragonfly(15, 1000, 210, true, 3.5, 3, "zigzag", 350, 600),
new Dragonfly(15, 1000, 430, true, 3.5, -3, "zigzag", 350, 600),
new Dragonfly(15, 1160, 210, true, 3.5, 3, "zigzag", 350, 600),
new Dragonfly(15, 1160, 430, true, 3.5, -3, "zigzag", 350, 600)
]
},
{
time: 37000,
object: [new PowerUp(840, 200, 4, 0, "linear", 0, 0)]
},
{
time: 40000,
object: [new Beetle(15, 840, 200, true, 3, 0.07, "wave", 50, 200)]
},
{
time: 43000,
object: [
new Squid(15, 840, 210, true, 4, 2, "zigzag", 300, 700),
new Squid(15, 1140, 210, true, 4, 2, "zigzag", 300, 700)
]
},
{
time: 48000,
object: [new Flipper(10, 840, 300, true, 4, 0.06, "wave", 120, 300)]
},
{
time: 48400,
object: [new Flipper(10, 840, 300, true, 4, 0.06, "wave", 120, 300)]
},
{
time: 48800,
object: [new Flipper(10, 840, 300, true, 4, 0.06, "wave", 120, 300)]
},
{
time: 49200,
object: [new Flipper(10, 840, 300, true, 4, 0.06, "wave", 120, 300)]
},
{
time: 49600,
object: [new Flipper(10, 840, 300, true, 4, 0.06, "wave", 120, 300)]
},
{
time: 51000,
object: [
new Dragonfly(15, 840, 210, true, 3.5, 3, "zigzag", 350, 600),
new Dragonfly(15, 840, 430, true, 3.5, -3, "zigzag", 350, 600),
new Dragonfly(15, 1000, 210, false, 3.5, 3, "zigzag", 350, 600),
new Dragonfly(15, 1000, 430, false, 3.5, -3, "zigzag", 350, 600),
new Dragonfly(15, 1160, 210, true, 3.5, 3, "zigzag", 350, 600),
new Dragonfly(15, 1160, 430, false, 3.5, -3, "zigzag", 350, 600),
new Dragonfly(15, 1320, 210, true, 3.5, 3, "zigzag", 350, 600),
new Dragonfly(15, 1320, 430, false, 3.5, -3, "zigzag", 350, 600)
]
},
{
time: 57000,
object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)]
},
{
time: 57500,
object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)]
},
{
time: 58000,
object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)]
},
{
time: 58500,
object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)]
},
{
time: 59000,
object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)]
},
{
time: 59500,
object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)]
},
{
time: 60000,
object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)]
},
{
time: 60500,
object: [new Squid(20, 840, 320, true, 5, 0.05, "wave", 110, 320)]
},
{
time: 61000,
object: [new Boss6()] //boss
},
{
time: 65000,
object: [] // bg stop trigger
}
];
isLevelDark = false;
const enemiesLvl7 = [
{
time: 2000,
object: [
new Kraken(25, 840, 100, true, 3, 0.05, "wave", 40, 100),
new Kraken(25, 840, 320, true, 5, 0.06, "wave", 40, 320)
]
},
{
time: 2500,
object: [
new Kraken(25, 900, 100, true, 3, 0.05, "wave", 40, 100),
new Kraken(25, 840, 320, true, 5, 0.06, "wave", 40, 320)
]
},
{
time: 6000,
object: [
new Kraken(25, 840, 150, true, 5, 0.06, "wave", 40, 150),
new Kraken(25, 840, 320, true, 3, 0.05, "wave", 40, 320)
]
},
{
time: 6500,
object: [
new Kraken(25, 840, 150, true, 5, 0.06, "wave", 40, 150),
new Kraken(25, 900, 320, true, 3, 0.05, "wave", 40, 320)
]
},
{
time: 9000,
object: [
new Kraken(25, 840, 100, true, 5, 0.06, "wave", 40, 100),
new Kraken(25, 840, 300, true, 3, -3, "zigzag", 400, 650),
new Kraken(25, 980, 300, true, 3, -3, "zigzag", 400, 650)
]
},
{
time: 9500,
object: [new Kraken(25, 840, 100, true, 5, 0.06, "wave", 40, 100)]
},
{
time: 13000,
object: [
new Beetle(15, 840, 180, true, 6, 0, "linear", 0, 0),
new Beetle(15, 980, 180, true, 6, 0, "linear", 0, 0),
new Beetle(15, 1120, 180, true, 6, 0, "linear", 0, 0),
new Beetle(15, 1260, 180, true, 6, 0, "linear", 0, 0)
]
},
{
time: 15000,
object: [
new Beetle(15, 840, 310, true, 6, 0, "linear", 0, 0),
new Beetle(15, 980, 310, true, 6, 0, "linear", 0, 0),
new Beetle(15, 1120, 310, true, 6, 0, "linear", 0, 0),
new Beetle(15, 1260, 310, true, 6, 0, "linear", 0, 0)
]
},
{
time: 16000,
object: [
new Beetle(15, 840, 50, true, 5, 4, "zigzag", 300, 600),
new Beetle(15, 980, 50, true, 5, 4, "zigzag", 300, 600),
new Beetle(15, 1120, 50, true, 5, 4, "zigzag", 300, 600),
new Beetle(15, 1260, 50, true, 5, 4, "zigzag", 300, 600),
new Beetle(15, 1400, 50, true, 5, 4, "zigzag", 300, 600)
]
},
{
time: 18500,
object: [
new Beetle(15, 840, 180, true, 5, 0, "linear", 0, 0),
new Beetle(15, 980, 180, true, 5, 0, "linear", 0, 0),
new Beetle(15, 1120, 180, true, 5, 0, "linear", 0, 0),
new Beetle(15, 1260, 180, true, 5, 0, "linear", 0, 0)
]
},
{
time: 20500,
object: [
new Triship(25, 840, 50, true, 2, 1.5, "zigzag", 450, 800),
new Triship(25, 980, 50, true, 2, 1.5, "zigzag", 450, 800),
new Triship(25, 1120, 50, true, 2, 1.5, "zigzag", 450, 800),
new Triship(25, 1260, 50, true, 2, 1.5, "zigzag", 450, 800),
new Triship(25, 1400, 50, true, 2, 1.5, "zigzag", 450, 800),
new Triship(25, 1540, 50, true, 2, 1.5, "zigzag", 450, 800)
]
},
{
time: 22000,
object: [
new Triship(25, 840, 300, true, 4, -4, "zigzag", 350, 600),
new Triship(25, 980, 300, true, 4, -4, "zigzag", 350, 600),
new Triship(25, 1120, 300, true, 4, -4, "zigzag", 350, 600),
new Triship(25, 1260, 300, true, 4, -4, "zigzag", 350, 600),
new Triship(25, 1400, 300, true, 4, -4, "zigzag", 350, 600),
new Triship(25, 1540, 300, true, 4, -4, "zigzag", 350, 600)
]
},
{
time: 30500,
object: [
new Shuttle(25, 1120, 50, false, 4, 0, "linear", 0, 0),
new Shuttle(25, 980, 120, false, 4, 0, "linear", 0, 0),
new Shuttle(25, 840, 190, false, 4, 0, "linear", 0, 0),
new Shuttle(25, 980, 260, false, 4, 0, "linear", 0, 0),
new Shuttle(25, 1120, 330, false, 4, 0, "linear", 0, 0)
]
},
{
time: 35000,
object: [
new Shuttle(25, 1120, 50, false, 4, 0, "linear", 0, 0),
new Shuttle(25, 980, 120, false, 4, 0, "linear", 0, 0),
new Shuttle(25, 840, 190, false, 4, 0, "linear", 0, 0),
new Shuttle(25, 980, 260, false, 4, 0, "linear", 0, 0),
new Shuttle(25, 1120, 330, false, 4, 0, "linear", 0, 0)
]
},
{
time: 40000,
object: [
new Beetle(15, 840, 100, true, 4, 0.06, "wave", 50, 100),
new Beetle(25, 840, 170, true, 4, 0.06, "wave", 50, 170)
]
},
{
time: 40700,
object: [
new Beetle(15, 840, 100, true, 4, 0.06, "wave", 50, 100),
new Beetle(25, 840, 170, true, 4, 0.06, "wave", 50, 170)
]
},
{
time: 41400,
object: [
new Beetle(15, 840, 100, true, 4, 0.06, "wave", 50, 100),
new Beetle(25, 840, 170, true, 4, 0.06, "wave", 50, 170)
]
},
{
time: 42100,
object: [
new Beetle(15, 840, 100, true, 4, 0.06, "wave", 50, 100),
new Beetle(25, 840, 170, true, 4, 0.06, "wave", 50, 170)
]
},
{
time: 42800,
object: [
new Beetle(15, 840, 100, true, 4, 0.06, "wave", 50, 100),
new Beetle(25, 840, 170, true, 4, 0.06, "wave", 50, 170)
]
},
{
time: 44000,
object: [
new Beetle(15, 840, 170, true, 4, 0.06, "wave", 50, 170),
new Beetle(25, 840, 240, true, 4, 0.06, "wave", 50, 240)
]
},
{
time: 44500,
object: [
new Beetle(15, 840, 170, true, 4, 0.06, "wave", 50, 170),
new Beetle(25, 840, 240, true, 4, 0.06, "wave", 50, 240)
]
},
{
time: 45000,
object: [
new Beetle(15, 840, 170, true, 4, 0.06, "wave", 50, 170),
new Beetle(25, 840, 240, true, 4, 0.06, "wave", 50, 240)
]
},
{
time: 45500,
object: [
new Beetle(15, 840, 170, true, 4, 0.06, "wave", 50, 170),
new Beetle(25, 840, 240, true, 4, 0.06, "wave", 50, 240)
]
},
{
time: 46000,
object: [
new Beetle(15, 840, 170, true, 4, 0.06, "wave", 50, 170),
new Beetle(25, 840, 240, true, 4, 0.06, "wave", 50, 240)
]
},
{
time: 52000,
object: [new Boss7()] //boss
}
];
const enemiesLvl8 = [
{
time: 2000,
object: [
new Squid(70, 840, 70, false, 3, 0, "linear", 0, 0),
new Squid(70, 840, 170, false, 3, 0, "linear", 0, 0),
new Squid(70, 840, 270, false, 3, 0, "linear", 0, 0)
]
},
{
time: 4000,
object: [
new Dragonfly(25, 840, 50, true, 2, 2, "zigzag", 350, 600),
new Dragonfly(25, 940, 200, true, 2, 0.04, "wave", 130, 180),
new Dragonfly(25, 1040, 50, true, 2, 2, "zigzag", 350, 600),
new Dragonfly(25, 1140, 200, true, 2, 0.06, "wave", 130, 180),
new Dragonfly(25, 1240, 50, true, 2, 2, "zigzag", 350, 600),
new Dragonfly(25, 1340, 200, true, 2, 0.03, "wave", 130, 180),
new Dragonfly(25, 1440, 50, true, 2, 2, "zigzag", 350, 600),
new Dragonfly(25, 1540, 200, true, 2, 0.05, "wave", 130, 180)
]
},
{
time: 12050,
object: [new Boss8()]
},
{
time: 15700,
object: []
// bg stop trigger component so blank object
}
];
function checkCollision(rect1, rect2) {
return (rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.height + rect1.y > rect2.y);
}
let currentLevel = new Level(1, true);
let lastTime = 0; // previous time stamp
const logo = document.getElementById("logo");
const playButton = document.getElementById("play");
const exitButton = document.getElementById("exit");
const pauseButton = document.getElementById("pause-button");
let isMainScreen = true; // game doesn't start in main screen
let newGame = false;
function nextLevel(num, isDark) {
currentLevel = new Level(num, isDark);
}
function gameWin() {
bgCtx.clearRect(0, 0, bgCanvas.width, bgCanvas.height);
mainCtx.clearRect(0, 0, mainCanvas.width, mainCanvas.height);
particles.forEach(particle => particle.update());
mainCtx.save();
mainCtx.font = "bold 60px Silkscreen"
bgCanvas.style.background = "#282828";
mainCtx.textAlign = "center";
mainCtx.fillStyle = "#aad69c";
mainCtx.fillText("You Win!", 420, 150);
mainCtx.fillText("High Score: " + playerScore.toString().padStart(5, "0"), 420, 250);
mainCtx.restore();
exitButton.style.display = "block";
pauseButton.style.visibility = "hidden";
}
function gameLose() {
bgCtx.clearRect(0, 0, bgCanvas.width, bgCanvas.height);
mainCtx.clearRect(0, 0, mainCanvas.width, mainCanvas.height);
particles.forEach(particle => particle.update());
mainCtx.save();
bgCanvas.style.background = "#282828";
mainCtx.fillStyle = "#aad69c";
mainCtx.font = "bold 60px Silkscreen"
mainCtx.textAlign = "center";
mainCtx.fillText("Game Over!", 420, 120);
mainCtx.fillText("Score: " + playerScore.toString().padStart(5, "0"), 420, 220);
mainCtx.font = "bold 50px Silkscreen"
mainCtx.fillText("Better Luck Next Time", 420, 320);
mainCtx.restore();
exitButton.style.display = "block";
pauseButton.style.visibility = "hidden";
}
function gameStart() {
playButton.style.display = "none";
pauseButton.style.visibility = "visible";
isMainScreen = false;
}
function mainScreen() {
mainCtx.clearRect(0, 0, mainCanvas.width, mainCanvas.height);
bgCtx.clearRect(0, 0, bgCanvas.width, bgCanvas.height);
particles.forEach(particle => particle.update());
bgCanvas.style.background = "#282828";
mainCtx.drawImage(logo, 0, 0, 1190, 430, 100, 100, 640, 245);
}
function animate(timestamp) {
let deltaTime = timestamp - lastTime;
lastTime = timestamp;
if (isMainScreen) mainScreen();
else if (!gamePause && !gameOver) {
mainCtx.clearRect(0, 0, mainCanvas.width, mainCanvas.height);
bgCtx.clearRect(0, 0, bgCanvas.width, bgCanvas.height);
currentLevel.update(deltaTime);
}
else {
mainCtx.save();
mainCtx.fillStyle = "red";
mainCtx.textAlign = "center";
mainCtx.font = "bold 60px Silkscreen"
mainCtx.fillText("|| Game Paused", 420, 260);
mainCtx.restore();
}
if (gameOver) {
lives > 0 ? gameWin() : gameLose();
}
requestAnimationFrame(animate);
}
animate(0);
playButton.style.top = `${bgCanvas.getBoundingClientRect().height - 70}`+"px";
exitButton.style.top = `${bgCanvas.getBoundingClientRect().height - 70}`+"px";
exitButton.addEventListener("click", () => {
document.location.reload(); // !!! reloading this because game not loading properly second time, tried everything, still unknown bug
});
pauseButton.addEventListener("click", () => {
gamePause = !gamePause;
if (gamePause) pauseButton.innerHTML = "Resume";
else pauseButton.innerHTML = "Pause";
pauseButton.blur();
});
playButton.addEventListener("click", gameStart);
exitButton.addEventListener("click", gameStart);
window.addEventListener("resize", ()=>{
playButton.style.top = `${bgCanvas.getBoundingClientRect().height - 70}`+"px";
exitButton.style.top = `${bgCanvas.getBoundingClientRect().height - 70}`+"px";
})