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:

//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