Midterm Project – Space Shooter

Below is the game I have created, a retro styled classic: Space Shooter!

and below is the code for this game:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//Declaring all variables to be used
let bullets = [];
let enemies = [];
let stars = [];
let powerups = [];
let gamestate;
let posterImage;
let fighter;
let player;
let laser;
let enemy;
let score = 0;
let title_screen_audio;
let playing_screen_audio;
let end_screen_audio;
let laser_sound_effect;
let enemySpawnRate =1;
let timeElapsed = 0;
function preload() {
posterImage = loadImage('SpaceShooterBackground.png'); //Loading main menu
fighter = loadImage('NicePng_spaceship-png_138961.png'); //Loading spaceship
laser = loadImage('LaserBullet.png'); //Loading laser bullet
enemy = loadImage('invader.png'); //Loading enemies
font = loadFont('GameOverFont.ttf'); //Loading Game Over Screen Font
title_screen_audio = loadSound("SkyFire (Title Screen).mp3");
playing_screen_audio = loadSound("Battle in the Stars.mp3"); //Loading menu music
end_screen_audio = loadSound("gameEndMusic.mp3"); //Load Game Over Screen Music
laser_sound_effect = loadSound("lasershot.wav"); //Load laser sound effect
}
function setup() {
createCanvas(400, 600);
gamestate = 'menu'; //Setting gamestate menu
player = new Spaceship(67, 61); //Initializing Sapceship class as the player
for (let i = 0; i < 100; i++) {
stars[i] = new Star(random(width), random(height), random(1, 5)); //Randomly generating stars
}
}
function draw() {
if (gamestate == 'menu') {
image(posterImage, 0, 0, width, height); //Displaying menu screen if gamestate is menu
if (!title_screen_audio.isPlaying())
{
title_screen_audio.play(); //Plays menu music
}
}
if (gamestate == 'playing') {
title_screen_audio.stop(); //Stops menu music when gamestate is playing
if (!playing_screen_audio.isPlaying())
{
playing_screen_audio.play(); //Plays battle music
}
background(1, 22, 64);
player.show();
for (let star of stars) {
star.show(); //Displaying stars
}
// Add enemies randomly
if (frameCount % (60 / enemySpawnRate) == 0) {
let enemy = new Enemy(random(width-50), -50, random(1, 4));
enemies.push(enemy);
}
for (let i = 0; i < bullets.length; i++) {
bullets[i].update(); //Adding velocity to bullets
bullets[i].show(); //Displaying bullets
for (let j = 0; j < enemies.length; j++) {
let enemyHitbox = enemies[j].getHitbox(); //Initializing enemies with hitbox
if (bullets[i].hits(enemyHitbox)) {
bullets.splice(i, 1); //Remove bullets when it hits an enemy
enemies[j].hits(); //Registers hit to enemy
score += 1; //Incremements score on hit
enemies.splice(j, 1); // remove the enemy object from the array
break;
}
}
}
for (let i = 0; i < enemies.length; i++) {
enemies[i].update(); //Makes enemies fall
enemies[i].show(); //Displays enemies
}
let anyEnemyReachedBottom = false; // flag to indicate if any enemy has reached the bottom
for (let i = 0; i < enemies.length; i++) {
if (enemies[i].reachedBottomFlag) {
anyEnemyReachedBottom = true; //Turns true when enemy reaches bottom
break;
}
}
if (anyEnemyReachedBottom) {
gamestate = 'gameover'; //Sets gamestate to gameover once enemy reaches the bottom
}
textSize(20);
strokeWeight(1);
textFont(font);
fill(255);
text("Score: " + score, 10, 30); //Displays score at top left
}
if (gamestate == 'gameover') {
playing_screen_audio.stop(); //Stops battle music
if (!end_screen_audio.isPlaying())
{
end_screen_audio.play(); //Plays defeat music
}
background(1, 22, 64);
for (let star of stars) {
star.show(); //Displays stars
}
textSize(30);
strokeWeight(1);
fill(255);
textFont(font);
text("Game Over", width / 2 - 80, height / 2 - 20);
text("Score: " + score, width / 2 - 65, height / 2 + 20);
text("Press Space to retry!", width / 2 - 150, height / 2 + 60);
}
timeElapsed += deltaTime / 1000;
if (timeElapsed >= 40) { // increase spawn rate every 40 seconds
enemySpawnRate++;
timeElapsed = 0;
}
}
function mouseClicked() {
if (gamestate == 'menu') {
gamestate = 'playing'; //Changes gameststate on mouseclick
}
if (gamestate == 'playing') {
let bullet = new Bullet(mouseX + 3, height - 20, 10);
bullets.push(bullet); //Fires bullet on every click
laser_sound_effect.play(); //Plays laser sound on every click
}
}
function keyPressed(){
if (key == ' ' && gamestate == 'gameover'){
score = 0;
bullets = [];
enemies = [];
restartGame();
end_screen_audio.stop(); //Restarts game by pressing space on game over screen
}
}
function restartGame() {
gamestate = 'playing';
enemySpawnRate = 1;
}
class Bullet {
//Setting variables
constructor(x, y, velocity) {
this.x = x;
this.y = y;
this.velocity = velocity;
this.size = 20;
}
update() {
this.y -= this.velocity; //Fires bullet upward
}
show() {
image(laser, this.x - this.size/2, this.y - this.size/2, this.size, this.size); //Laser image
}
hits(hitbox) {
let d = dist(this.x, this.y, hitbox.x + hitbox.width/2, hitbox.y + hitbox.height/2);
if (d < (this.size + hitbox.width)/2) {
return true; //Hitbox registration
} else {
return false;
}
}
}
class Spaceship {
constructor(x,y) {
this.x = x;
this.y = y; // Setting variables
}
show(){
//Prevents fighter from going out of bounds
if (mouseX - 30 < 0)
{
image(fighter,0,height-50,this.x,this.y);
}
else if (mouseX - 30 > width)
{
image(fighter,width - this.x,height-50,this.x,this.y);
}
else
{
image(fighter,mouseX-30,height-50,this.x,this.y);
}
}
}
//Creates stars in background
class Star {
constructor(x, y, size) {
this.x = x;
this.y = y;
this.size = size;
}
show() {
stroke(255);
strokeWeight(this.size);
point(this.x, this.y);
}
}
class Enemy {
constructor(x, y, velocity) {
this.x = x;
this.y = y;
this.velocity = velocity;
this.size = 30;
this.reachedBottomFlag = false; // flag to indicate if this enemy has reached the bottom
}
update() {
this.y += this.velocity;
if (this.y >= height) {
this.reachedBottomFlag = true; //Detects if enemy has reached bottom
}
}
show() {
image(enemy, this.x, this.y, this.size, this.size); //Creating enemies images
}
hits() {
this.velocity = 0; //Sets velocity to 0 on hit
}
getHitbox() {
return {
x: this.x,
y: this.y,
width: this.size,
height: this.size //Creates hitbox of enemies
}
}
}
//Declaring all variables to be used let bullets = []; let enemies = []; let stars = []; let powerups = []; let gamestate; let posterImage; let fighter; let player; let laser; let enemy; let score = 0; let title_screen_audio; let playing_screen_audio; let end_screen_audio; let laser_sound_effect; let enemySpawnRate =1; let timeElapsed = 0; function preload() { posterImage = loadImage('SpaceShooterBackground.png'); //Loading main menu fighter = loadImage('NicePng_spaceship-png_138961.png'); //Loading spaceship laser = loadImage('LaserBullet.png'); //Loading laser bullet enemy = loadImage('invader.png'); //Loading enemies font = loadFont('GameOverFont.ttf'); //Loading Game Over Screen Font title_screen_audio = loadSound("SkyFire (Title Screen).mp3"); playing_screen_audio = loadSound("Battle in the Stars.mp3"); //Loading menu music end_screen_audio = loadSound("gameEndMusic.mp3"); //Load Game Over Screen Music laser_sound_effect = loadSound("lasershot.wav"); //Load laser sound effect } function setup() { createCanvas(400, 600); gamestate = 'menu'; //Setting gamestate menu player = new Spaceship(67, 61); //Initializing Sapceship class as the player for (let i = 0; i < 100; i++) { stars[i] = new Star(random(width), random(height), random(1, 5)); //Randomly generating stars } } function draw() { if (gamestate == 'menu') { image(posterImage, 0, 0, width, height); //Displaying menu screen if gamestate is menu if (!title_screen_audio.isPlaying()) { title_screen_audio.play(); //Plays menu music } } if (gamestate == 'playing') { title_screen_audio.stop(); //Stops menu music when gamestate is playing if (!playing_screen_audio.isPlaying()) { playing_screen_audio.play(); //Plays battle music } background(1, 22, 64); player.show(); for (let star of stars) { star.show(); //Displaying stars } // Add enemies randomly if (frameCount % (60 / enemySpawnRate) == 0) { let enemy = new Enemy(random(width-50), -50, random(1, 4)); enemies.push(enemy); } for (let i = 0; i < bullets.length; i++) { bullets[i].update(); //Adding velocity to bullets bullets[i].show(); //Displaying bullets for (let j = 0; j < enemies.length; j++) { let enemyHitbox = enemies[j].getHitbox(); //Initializing enemies with hitbox if (bullets[i].hits(enemyHitbox)) { bullets.splice(i, 1); //Remove bullets when it hits an enemy enemies[j].hits(); //Registers hit to enemy score += 1; //Incremements score on hit enemies.splice(j, 1); // remove the enemy object from the array break; } } } for (let i = 0; i < enemies.length; i++) { enemies[i].update(); //Makes enemies fall enemies[i].show(); //Displays enemies } let anyEnemyReachedBottom = false; // flag to indicate if any enemy has reached the bottom for (let i = 0; i < enemies.length; i++) { if (enemies[i].reachedBottomFlag) { anyEnemyReachedBottom = true; //Turns true when enemy reaches bottom break; } } if (anyEnemyReachedBottom) { gamestate = 'gameover'; //Sets gamestate to gameover once enemy reaches the bottom } textSize(20); strokeWeight(1); textFont(font); fill(255); text("Score: " + score, 10, 30); //Displays score at top left } if (gamestate == 'gameover') { playing_screen_audio.stop(); //Stops battle music if (!end_screen_audio.isPlaying()) { end_screen_audio.play(); //Plays defeat music } background(1, 22, 64); for (let star of stars) { star.show(); //Displays stars } textSize(30); strokeWeight(1); fill(255); textFont(font); text("Game Over", width / 2 - 80, height / 2 - 20); text("Score: " + score, width / 2 - 65, height / 2 + 20); text("Press Space to retry!", width / 2 - 150, height / 2 + 60); } timeElapsed += deltaTime / 1000; if (timeElapsed >= 40) { // increase spawn rate every 40 seconds enemySpawnRate++; timeElapsed = 0; } } function mouseClicked() { if (gamestate == 'menu') { gamestate = 'playing'; //Changes gameststate on mouseclick } if (gamestate == 'playing') { let bullet = new Bullet(mouseX + 3, height - 20, 10); bullets.push(bullet); //Fires bullet on every click laser_sound_effect.play(); //Plays laser sound on every click } } function keyPressed(){ if (key == ' ' && gamestate == 'gameover'){ score = 0; bullets = []; enemies = []; restartGame(); end_screen_audio.stop(); //Restarts game by pressing space on game over screen } } function restartGame() { gamestate = 'playing'; enemySpawnRate = 1; } class Bullet { //Setting variables constructor(x, y, velocity) { this.x = x; this.y = y; this.velocity = velocity; this.size = 20; } update() { this.y -= this.velocity; //Fires bullet upward } show() { image(laser, this.x - this.size/2, this.y - this.size/2, this.size, this.size); //Laser image } hits(hitbox) { let d = dist(this.x, this.y, hitbox.x + hitbox.width/2, hitbox.y + hitbox.height/2); if (d < (this.size + hitbox.width)/2) { return true; //Hitbox registration } else { return false; } } } class Spaceship { constructor(x,y) { this.x = x; this.y = y; // Setting variables } show(){ //Prevents fighter from going out of bounds if (mouseX - 30 < 0) { image(fighter,0,height-50,this.x,this.y); } else if (mouseX - 30 > width) { image(fighter,width - this.x,height-50,this.x,this.y); } else { image(fighter,mouseX-30,height-50,this.x,this.y); } } } //Creates stars in background class Star { constructor(x, y, size) { this.x = x; this.y = y; this.size = size; } show() { stroke(255); strokeWeight(this.size); point(this.x, this.y); } } class Enemy { constructor(x, y, velocity) { this.x = x; this.y = y; this.velocity = velocity; this.size = 30; this.reachedBottomFlag = false; // flag to indicate if this enemy has reached the bottom } update() { this.y += this.velocity; if (this.y >= height) { this.reachedBottomFlag = true; //Detects if enemy has reached bottom } } show() { image(enemy, this.x, this.y, this.size, this.size); //Creating enemies images } hits() { this.velocity = 0; //Sets velocity to 0 on hit } getHitbox() { return { x: this.x, y: this.y, width: this.size, height: this.size //Creates hitbox of enemies } } }
//Declaring all variables to be used
let bullets = [];
let enemies = [];
let stars = [];
let powerups = [];
let gamestate;
let posterImage;
let fighter;
let player;
let laser;
let enemy;
let score = 0;
let title_screen_audio;
let playing_screen_audio;
let end_screen_audio;
let laser_sound_effect;
let enemySpawnRate =1;
let timeElapsed = 0;


function preload() {
  posterImage = loadImage('SpaceShooterBackground.png'); //Loading main menu
  fighter = loadImage('NicePng_spaceship-png_138961.png'); //Loading spaceship
  laser = loadImage('LaserBullet.png'); //Loading laser bullet
  enemy = loadImage('invader.png'); //Loading enemies
  font = loadFont('GameOverFont.ttf'); //Loading Game Over Screen Font
  title_screen_audio = loadSound("SkyFire (Title Screen).mp3");
  playing_screen_audio = loadSound("Battle in the Stars.mp3"); //Loading menu music
  end_screen_audio = loadSound("gameEndMusic.mp3");  //Load Game Over Screen Music
  laser_sound_effect = loadSound("lasershot.wav"); //Load laser sound effect
}

function setup() {
  createCanvas(400, 600);
  gamestate = 'menu'; //Setting gamestate menu
  player = new Spaceship(67, 61); //Initializing Sapceship class as the player

  for (let i = 0; i < 100; i++) {
    stars[i] = new Star(random(width), random(height), random(1, 5)); //Randomly generating stars
  }
}

function draw() {
  if (gamestate == 'menu') {
    image(posterImage, 0, 0, width, height); //Displaying menu screen if gamestate is menu
    if (!title_screen_audio.isPlaying())
      {
        title_screen_audio.play(); //Plays menu music
      }
  }
  if (gamestate == 'playing') {
    title_screen_audio.stop(); //Stops menu music when gamestate is playing
    if (!playing_screen_audio.isPlaying())
      {
        playing_screen_audio.play(); //Plays battle music
      }
    background(1, 22, 64);
    player.show();

    for (let star of stars) {
      star.show(); //Displaying stars
    }

    // Add enemies randomly
    if (frameCount % (60 / enemySpawnRate) == 0) {
      let enemy = new Enemy(random(width-50), -50, random(1, 4));
      enemies.push(enemy);
    }

    for (let i = 0; i < bullets.length; i++) {
      bullets[i].update(); //Adding velocity to bullets
      bullets[i].show(); //Displaying bullets
      for (let j = 0; j < enemies.length; j++) {
        let enemyHitbox = enemies[j].getHitbox(); //Initializing enemies with hitbox
        if (bullets[i].hits(enemyHitbox)) {
          bullets.splice(i, 1); //Remove bullets when it hits an enemy
          enemies[j].hits(); //Registers hit to enemy
          score += 1; //Incremements score on hit
          enemies.splice(j, 1); // remove the enemy object from the array
          break;
        }
      }
    }

    for (let i = 0; i < enemies.length; i++) {
      enemies[i].update(); //Makes enemies fall
      enemies[i].show(); //Displays enemies
    }

    let anyEnemyReachedBottom = false; // flag to indicate if any enemy has     reached the bottom
    for (let i = 0; i < enemies.length; i++) {
      if (enemies[i].reachedBottomFlag) {
        anyEnemyReachedBottom = true; //Turns true when enemy reaches bottom
        break;
      }
    }

    if (anyEnemyReachedBottom) {
      gamestate = 'gameover'; //Sets gamestate to gameover once enemy reaches the bottom
    }

    textSize(20);
    strokeWeight(1);
    textFont(font);
    fill(255);
    text("Score: " + score, 10, 30); //Displays score at top left
  }

  if (gamestate == 'gameover') {
    playing_screen_audio.stop(); //Stops battle music
    if (!end_screen_audio.isPlaying())
      {
        end_screen_audio.play(); //Plays defeat music
      }
    background(1, 22, 64);
    for (let star of stars) {
      star.show(); //Displays stars
    }
    textSize(30);
    strokeWeight(1);
    fill(255);
    textFont(font);
    text("Game Over", width / 2 - 80, height / 2 - 20);
    text("Score: " + score, width / 2 - 65, height / 2 + 20);
    text("Press Space to retry!", width / 2 - 150, height / 2 + 60);
  }
  
  timeElapsed += deltaTime / 1000;
  if (timeElapsed >= 40) { // increase spawn rate every 40 seconds
    enemySpawnRate++;
    timeElapsed = 0;
  }
  
}
function mouseClicked() {
  if (gamestate == 'menu') {
    gamestate = 'playing'; //Changes gameststate on mouseclick
  }
  if (gamestate == 'playing') {
    let bullet = new Bullet(mouseX + 3, height - 20, 10);
    bullets.push(bullet); //Fires bullet on every click
    laser_sound_effect.play(); //Plays laser sound on every click
  }
}

function keyPressed(){
  if (key == ' ' && gamestate == 'gameover'){
    score = 0;
    bullets = [];
    enemies = [];
    restartGame();
    end_screen_audio.stop(); //Restarts game by pressing space on game over screen
  }
}

function restartGame() {
  gamestate = 'playing';
  enemySpawnRate = 1;
}

class Bullet {
  //Setting variables
  constructor(x, y, velocity) {
    this.x = x;
    this.y = y;
    this.velocity = velocity;
    this.size = 20; 
  }

  update() {
    this.y -= this.velocity; //Fires bullet upward
  }

  show() {
    image(laser, this.x - this.size/2, this.y - this.size/2, this.size, this.size); //Laser image
  }

  hits(hitbox) {
    let d = dist(this.x, this.y, hitbox.x + hitbox.width/2, hitbox.y + hitbox.height/2);
    if (d < (this.size + hitbox.width)/2) {
      return true; //Hitbox registration
    } else {
      return false;
    }
  }
}

class Spaceship {
  constructor(x,y) {
    this.x = x;
    this.y = y; // Setting variables
  }
  show(){
    //Prevents fighter from going out of bounds
    if (mouseX - 30 < 0)
      {
        image(fighter,0,height-50,this.x,this.y);
      }
    else if (mouseX - 30 > width)
      {
        image(fighter,width - this.x,height-50,this.x,this.y);
      }
    else
      {
        image(fighter,mouseX-30,height-50,this.x,this.y);
      }
    
    
  }
}
//Creates stars in background
class Star {
  constructor(x, y, size) {
    this.x = x;
    this.y = y;
    this.size = size;
  }

  show() {
    stroke(255);
    strokeWeight(this.size);
    point(this.x, this.y);
  }
}

class Enemy {
  constructor(x, y, velocity) {
    this.x = x;
    this.y = y;
    this.velocity = velocity;
    this.size = 30;
    this.reachedBottomFlag = false; // flag to indicate if this enemy has reached the bottom
  }

  update() {
    this.y += this.velocity;
    if (this.y >= height) {
      this.reachedBottomFlag = true; //Detects if enemy has reached bottom
    }
  }

  show() {
    image(enemy, this.x, this.y, this.size, this.size); //Creating enemies images
  }

  hits() {
    this.velocity = 0; //Sets velocity to 0 on hit
  }

  getHitbox() {
    return {
      x: this.x,
      y: this.y,
      width: this.size,
      height: this.size //Creates hitbox of enemies
    }
  }
} 

I was inspired by the many retro classic games. These games bring a long a sense of nostalgia that no other games bring. Since I am an avid space enjoyer and all things sci-fi, I decided to combine these 2 concepts into the game Space Shooter. The story is you are a space fighter defending the human race from these purple invaders! Your mission is to prevent these invaders to breaking through into your back line.

Behind the scenes, I used Canva in order to create the opening menu of this game. I then created separate classes for the player’s spaceship, enemies, bullets, and stars. I tied the spaceship position and laser firing position to the mouseX coordinate while invaders fall between a random set of x values. I essentially created a hitbox for the enemies so the laser can register whether it hits an enemy or not. I also made it so that it tracks how long the game goes on and after every 30 seconds, the spawn rate of the enemies increasing making the game get progressively difficult. Just for some extra personalization, I made it so the stars in the background are randomly generated so there is always something that is a tiny bit new in every playthrough.

I am especially proud of making the hitbox method in order to deal with collision. I initially struggled with collision but after researching and messing around, I managed to finally solve it and in quite the efficient way I like to think. I’m also proud of the laser bullet and invader sprites. I managed to find a good vector sprite for the spaceship but importing different sized bullets and invaders could be messy. So, I made them on my own using online software allowing me to personalize them into whatever size or shape I need. This further simplified my work and going forward and gave it a bit more of my own touch and creativity into this project.

I still feel as there are many more improvements that can be made into this. For starters, I could add other enemy types where they require multiple hits in order to be destroyed. I can also add powerups where the game could be slowed for example. I was also told about adding a pause screen which I initially thought wouldn’t fit as it is a retro game where pause menus didn’t exist but still could be added as a quality-of-life improvement.

Leave a Reply