Sitemap / Advertise

Introduction

This particular game is an iteration of the splendid Space Invaders, but surreptitiously including the most-acknowledged spaceship in the Universe - Millennium Falcon.


Tags

Share

JavaScript Game: Space Invaders with Millennium Falcon

Advertisement:


read_later

Read Later



read_later

Read Later

Introduction

This particular game is an iteration of the splendid Space Invaders, but surreptitiously including the most-acknowledged spaceship in the Universe - Millennium Falcon.

Tags

Share





Advertisement

Advertisement





Introduction

I am new to developing and designing a game using JavaScript. In this project series named JavaScript Game, I will share games I develop from basic to advanced. This particular game is an iteration of the splendid Space Invaders, but surreptitiously including the most-acknowledged spaceship in the Universe - Millennium Falcon.

It is also a competitive game: when you exceed the highest scores defined as ranks, your username will be registered as the new highest rank scorer in the database.

Click 'Preview' to Play.

For more information about the code files, please visit the Source Code section below.

Step 1: Features

- Move the Millennium Falcon using arrow keys or the mobile keyboard to fire bullets to shoot Tie Fighters.

project-image
Figure - 35.1

- Pause or resume the game using the escape key or the P mobile key.

project-image
Figure - 35.2

- There are three different levels in this game:

- In Level 1, each Tie Fighter getting hit by a bullet increases the sum of the score by 200 points. Every 1/15 seconds, Tie Fighters are randomly generated by the game.

project-image
Figure - 35.3

- In Level 2, each Tie Fighter getting hit by a bullet increases the sum of the score by 300 points. Every 2/15 seconds, Tie Fighters are randomly generated by the game.

project-image
Figure - 35.4

- In Level 3, each Tie Fighter getting hit by a bullet increases the sum of the score by 500 points. Every 3/15 seconds, Tie Fighters are randomly generated by the game. Also, the Death Star emerges: you need to hit the Death Star ten times with bullets to destroy it.

project-image
Figure - 35.5

- When the Millennium Falcon got hit from a Tie Fighter, the game is over. Also, in Level 3, if the Millennium Falcon crushes to the Death Star, the game is over.

- Press Enter or click the P mobile button to restart the game.

project-image
Figure - 35.6

- Note: You have to sign in to join to the scoreboard on TheAmplituhedron.

project-image
Figure - 35.7

- If you hit a new highest record defined as rank, you will be the new highest rank scorer in the scoreboard :)

project-image
Figure - 35.8


project-image
Figure - 35.9

Step 2: Index

- Import the Game class.

- Get the canvas element.

- Create the 2d context to draw.

- Define the game width and the game height.

- Define the game object.

- Get the highest scores from the score.php file as the response by making a get request in jQuery / AJAX.

- Create the loop function to update and draw the game using requestAnimationFrame(loop);.

project-image
Figure - 35.10

Step 3: Game

- Import Ship, InputHandler, Fighters, and Collision classes.

- Define game states.

- Create and define game objects. And, attach the class itself to the created game objects.

- Update each game object if the game state is not equal to paused or gameover.

- Print the game score and the current game level.

- Proceed after drawing each object unless there is not a change in the game state.

- Define pause() and gameOver() functions.

- In the gameOver() function, send the new score to the score.php file and get notification messages after interpretation as the response.

project-image
Figure - 35.11


project-image
Figure - 35.12

Step 4: Ship

- Get the information including the game width and the game height from the Game class.

- Define the Millennium Falcon's properties - width, height, speed, accelerate, position, bullet properties, score, and level.

- Define the bullets array and the bulletNumber variable.

- Define moveRight(), moveLeft(), moveUp(), moveDown(), and stop() functions.

- In the fire() function, add a new bullet to the bullets array with the current position coordinates by using the push() function and increase the bulletNumber value.

- Draw the background of the canvas.

- Draw the Millennium Falcon.

- Draw each bullet in the bullets array if it is not empty.

- Update the Millennium Falcon position.

- Update each bullet position y-axis in the bullets array.

- If a particular bullet is outside of the canvas, remove that bullet in the bullets array using splice() and indexOf() functions.

project-image
Figure - 35.13


project-image
Figure - 35.14

Step 5: Fighters

- Get the information including the game width and the game height from the Game class.

- Define the fighters' properties - width, height, and speed.

- Define the level multidimensional array to provide each level with a different Tie Fighter fleet.

- Define the fighters array and the line variable.

- Define the Death Star's properties - position, width, height, speed, and hit.

- Draw each Tie Fighter in the fighters array.

- If the current level is equal to 3, draw the Death Star.

- Create new Tie Fighters depending on the current level and the line variable using the push() function.

- Update each Tie Fighter in the fighters array.

- If a particular Tie Fighter is outside of the canvas, remove that Tie Fighter in the fighters array using splice() and indexOf() functions.

project-image
Figure - 35.15


project-image
Figure - 35.16

Step 6: Collision

- Get objects and variables from the Game class.

- In the detect_bullet_collision(bullets, fighters) function, detect whether a particular bullet hits to a particular Tie Fighter or not.

- If it is true; remove that array elements using splice() and indexOf() functions, increase the score depending on the current level.

- Depending on the score, instigate further levels.

- In the detect_star_collision(bullets, star) function, detect whether a particular bullet hits to the Death Star.

- If it is true, increase the hit value and remove that bullet.

- In the detect_ship_collision(ship, fighters), detect whether a particular Tie Fighter hits to the Millennium Falcon.

- If it is true, initiate the gameOver() function.

- Update the mentioned functions for each related array element.

project-image
Figure - 35.17


project-image
Figure - 35.18

Step 7: InputHandler

- Get objects and variables from the Game class.

- If the right arrow is pressed or the mobile right key is clicked, execute the moveRight() function.

- If the left arrow is pressed or the mobile left key is clicked, execute the moveLeft() function.

- If the up arrow is pressed or the mobile up key is clicked, execute the moveUp() function.

- If the down arrow is pressed or the mobile down key is clicked, execute the moveDown() function.

- To avoid the page navigation while pressing arrows, use event.preventDefault();

- If the escape key is pressed or the mobile P key is clicked, pause or resume the game.

- If the Enter key is pressed or the mobile P key is clicked, restart the game in the GAMEOVER state.

- If the space key is pressed or the mobile space is clicked, fire a bullet.

- On keyup, stop the ship by executing the stop() function.

project-image
Figure - 35.19


project-image
Figure - 35.20

Step 8: Table

- Create a database.

- Subsequently, create a database table named `spaceinvaders`.

project-image
Figure - 35.21

- Insert the default rank and user name variables to the table.

project-image
Figure - 35.22

- Check the table after inserting variables.

project-image
Figure - 35.23

Step 9: Score

- Define the database information to connect to the database table.

- Select ranks from the database table.

- If the scores variable is set, print all the rank and username values.

- If the new score variable is set, add each rank to the scores array using the array_push() function.

- If the new score is higher than any ranks, update the database and print a congratulations notification message as the response.

- Otherwise, print a try again notification message as the response.

project-image
Figure - 35.24


project-image
Figure - 35.25

Source Code

Please sign in to download source code files and the game, including index.css, index.html, and assets, in a zipped folder to play and try the game on your computer.

Zip Folder

Download


Index

Download



import Game from './game.js';

let canvas = document.getElementById("game");
let ctx = canvas.getContext("2d");

const GAME_WIDTH = 300;
const GAME_HEIGHT = 500;

let game = new Game(GAME_WIDTH, GAME_HEIGHT);

// GET SCORES

$.ajax({		 
    url: 'score.php?scores=true', 
	type: 'GET',
	async:true,
	success: (result) => {if(result) $(".scoreBoard").append(result); }
});

function loop(timestamp){
	// Define if needed.
	let deltaTime;
	
	ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
    game.update(deltaTime);
	game.draw(ctx);
	
	requestAnimationFrame(loop);
}

requestAnimationFrame(loop);


Game

Download



import Ship from './ship.js';
import InputHandler from './inputhandler.js';
import Fighters from './fighters.js';
import Collision from './collision.js';

export default class Game{
	constructor(gameWidth, gameHeight){
		this.gameWidth = gameWidth;
		this.gameHeight = gameHeight;
		
		this.STATE = {
			PAUSED: 0,
			RUNNING: 1,
			GAMEOVER: 2
		};
		
	    this.ship = new Ship(this);
        this.fighters = new Fighters(this);
		this.collision = new Collision(this);
		new InputHandler(this);
		this.objects = [this.ship, this.fighters, this.collision];

	}
	
	update(deltaTime){
		if(this.gameState == this.STATE.PAUSED || this.gameState == this.STATE.GAMEOVER) return;
		this.objects.forEach((object) => object.update(deltaTime));
	}
	
	draw(ctx){
		this.objects.forEach((object) => object.draw(ctx));
		
		ctx.font = "bold 20px Times";
		ctx.fillStyle = "#eb2e00";
		ctx.fillText("SCORE: " + this.ship.properties.score, 10, 25);
		ctx.fillText("LEVEL: " + this.ship.properties.LEVEL, 10, this.gameHeight - 25);
		
		if(this.gameState == this.STATE.PAUSED){
			ctx.fillStyle = "rgba(78, 88, 81, 0.6)";
			ctx.fillRect(0, 0, this.gameWidth, this.gameHeight);
			ctx.font = "bold 30px Times";
			ctx.fillStyle = "white";
			ctx.textAlign = "center";
			ctx.fillText("PAUSED", this.gameWidth / 2, this.gameHeight / 3);
			ctx.textAlign = "left";
		}
		
		if(this.gameState == this.STATE.GAMEOVER){
			ctx.fillStyle = "#1F2020";
			ctx.fillRect(0, 0, this.gameWidth, this.gameHeight);
			ctx.font = "bold 30px Times";
			ctx.fillStyle = "white";
			ctx.textAlign = "center";
			ctx.fillText("GAME OVER !!!", this.gameWidth / 2, this.gameHeight / 2);
		}

	}
	
	pause(){
		if(this.gameState == this.STATE.PAUSED){
			this.gameState = this.STATE.RUNNING
		}else{
			this.gameState = this.STATE.PAUSED;
		}
	}
	
	gameOver(){
		this.gameState = this.STATE.GAMEOVER;
		
		// SEND NEW SCORE TO THE DATABASE
		$.ajax({
			url: 'score.php?new_score=' + this.ship.properties.score,
			type: 'GET',
			async:true,
			success: (result) => { alert(result); }
		});
	}
}


Ship

Download



export default class Ship{
	constructor(game){
		this.game = game;
		
		this.gameWidth = game.gameWidth;
		this.gameHeight = game.gameHeight;
		
		this.properties = {
			width: 35,
			height: 45,
			speed: {x: 0, y: 0},
			accelerate: 10,
			position: {x : this.gameWidth / 2, y: this.gameHeight / 2},
			bullet: {width: 3, height: 15, speed: 10},
			score: 0,
			LEVEL: 1
		};
		
		this.bullets = [];
		this.bulletNumber = 0;
		
	}
	
	moveRight(){
		this.properties.speed.x = -this.properties.accelerate;
		this.properties.speed.y = 0;
	}
	
	moveLeft(){
		this.properties.speed.x = this.properties.accelerate;
		this.properties.speed.y = 0;
	}
	
	moveUp(){
		this.properties.speed.x = 0;
		this.properties.speed.y = -this.properties.accelerate;
	}
	
	moveDown(){
		this.properties.speed.x = 0;
		this.properties.speed.y = this.properties.accelerate;
	}
	
	stop(){
		this.properties.speed.x = 0;
		this.properties.speed.y = 0;
	}
	
	fire(){
		this.bullets.push({x: this.properties.position.x + (this.properties.width / 2 - this.properties.bullet.width / 2), y: this.properties.position.y - this.properties.height / 2});
		this.bulletNumber++;
	}
	
	draw(ctx){
		ctx.drawImage(document.getElementById("background"), 0, 0, this.gameWidth, this.gameHeight);
		
		ctx.drawImage(document.getElementById("millennium_falcon"), this.properties.position.x, this.properties.position.y, this.properties.width, this.properties.height);
		
		if(this.bulletNumber > 0){
			this.bullets.forEach((object) => { 
			    ctx.fillStyle = "cyan"; 
				ctx.fillRect(object.x, object.y, this.properties.bullet.width, this.properties.bullet.height); 
			});
		}

	}
	
	update(deltaTime){
		this.properties.position.x += this.properties.speed.x;
		this.properties.position.y += this.properties.speed.y;
		
	    if(this.properties.position.x > this.gameWidth - this.properties.width) this.properties.position.x = this.gameWidth - this.properties.width;
		if(this.properties.position.x < 0) this.properties.position.x = 0;
	    if(this.properties.position.y > this.gameHeight - this.properties.height) this.properties.position.y = this.gameHeight - this.properties.height;
	    if(this.properties.position.y < 0) this.properties.position.y = 0;
		
		if(this.bulletNumber > 0){
			this.bullets.forEach((object) => {
				object.y -= this.properties.bullet.speed;
				if(object.y < 0) this.bullets.splice(this.bullets.indexOf(object), 1);
			});
		}
	}
}


Fighters

Download



export default class Fighters{
	constructor(game){
		this.game = game;
		
		this.gameWidth = game.gameWidth;
		this.gameHeight = game.gameHeight;
		
		this.properties = {
			width: 25,
			height: 25,
			speed: 5
		};
		
		this.level = [
		  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
		  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
		  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1]
		];
		
		this.fighters = [];
		
		this.line = -1;
		
		this.death_star = {position: {x: 0, y: -300}, width: this.gameWidth, height: 300, speed: 3, hit: 0};

	}
	
	draw(ctx){
		this.fighters.forEach((object) => {
			ctx.drawImage(document.getElementById("tie_fighter"), object.x, object.y, this.properties.width, this.properties.height);
			
		});
		
		if(this.game.ship.properties.LEVEL == 3 && this.death_star.hit < 10){
		    ctx.drawImage(document.getElementById("death_star"), this.death_star.position.x, this.death_star.position.y, this.death_star.width, this.death_star.height);
		}

	}
		
	update(deltaTime){
		this.line++;
		if(this.line > this.level[0].length) this.line = 0;
		
		if(this.game.ship.properties.LEVEL == 1){ if(this.level[0][this.line] == 1) this.fighters.push({x: Math.floor(Math.random() * 10 * 26), y: 0}); }
		if(this.game.ship.properties.LEVEL == 2){ if(this.level[1][this.line] == 1) this.fighters.push({x: Math.floor(Math.random() * 10 * 26), y: 0}); }
		if(this.game.ship.properties.LEVEL == 3){
			if(this.level[2][this.line] == 1) this.fighters.push({x: Math.floor(Math.random() * 10 * 26), y: 0});
			if(this.death_star.position.y != 0) this.death_star.position.y += this.death_star.speed;
		}
			

		this.fighters.forEach((object) => {
			object.y += this.properties.speed;
			if(object.y > this.gameHeight) this.fighters.splice(this.fighters.indexOf(object), 1);
		});
		
	}
}


Collision

Download



export default class Collision{
	constructor(game){
        // GET ALL INFO FROM THE GAME CLASS
		this.game = game;
	}
	
	detect_bullet_collision(bullets, fighters){
		let bottomOfFighter = fighters.y + this.game.fighters.properties.height;
        let leftOfFighter = fighters.x;
        let rightOfFighter = fighters.x + this.game.fighters.properties.width;
        let topOfFighter = fighters.y;
        
        let bottomOfBullet = bullets.y + this.game.ship.properties.bullet.height;
        let leftOfBullet = bullets.x;		
        let rightOfBullet = bullets.x + this.game.ship.properties.bullet.width;	
        let topOfBullet = bullets.y;
		
		if(leftOfBullet < rightOfFighter && rightOfBullet > leftOfFighter && topOfBullet < bottomOfFighter && bottomOfBullet > topOfFighter){
			this.game.ship.bullets.splice(this.game.ship.bullets.indexOf(bullets), 1);
			this.game.fighters.fighters.splice(this.game.fighters.fighters.indexOf(fighters), 1);
			
			if(this.game.ship.properties.LEVEL == 1) this.game.ship.properties.score += 200;
			if(this.game.ship.properties.LEVEL == 2) this.game.ship.properties.score += 300;
			if(this.game.ship.properties.LEVEL == 3) this.game.ship.properties.score += 500;
			
			if(this.game.ship.properties.score > 5000) this.game.ship.properties.LEVEL = 2;
			if(this.game.ship.properties.score > 10000) this.game.ship.properties.LEVEL = 3;
		}
        
	}
	
	detect_star_collision(bullets, star){
		if(bullets.y <= star.height / 2 && star.hit < 10){
			star.hit++;
			this.game.ship.bullets.splice(this.game.ship.bullets.indexOf(bullets), 1);
		}
		
		if(this.game.ship.properties.position.y <= star.height / 2 && star.hit < 10){
			this.game.gameOver();
		}
	}
	
	detect_ship_collision(ship, fighters){
		let bottomOfFighter = fighters.y + this.game.fighters.properties.height;
        let leftOfFighter = fighters.x;
        let rightOfFighter = fighters.x + this.game.fighters.properties.width;
        let topOfFighter = fighters.y;
        
        let bottomOfShip = ship.position.y + ship.height;
        let leftOfShip = ship.position.x;		
        let rightOfShip = ship.position.x + ship.width;	
        let topOfShip = ship.position.y;
		
		if(leftOfShip < rightOfFighter && rightOfShip > leftOfFighter && topOfShip < bottomOfFighter && bottomOfShip > topOfFighter){
			this.game.gameOver();
		}
	}
	
	draw(ctx){
		
	}
	
	update(deltaTime){
		if(this.game.ship.bulletNumber > 0){
			this.game.ship.bullets.forEach((bullet) => {
				this.game.fighters.fighters.forEach((fighter) => { this.detect_bullet_collision(bullet, fighter); });
			});
		}
		
		this.game.fighters.fighters.forEach((fighter) => {
			this.detect_ship_collision(this.game.ship.properties, fighter);
		});
		
		if(this.game.ship.properties.LEVEL == 3){
			this.game.ship.bullets.forEach((bullet) => {
				this.detect_star_collision(bullet, this.game.fighters.death_star);
			});
		}

	}
}


InputHandler

Download



export default class InputHandler{
	constructor(game){
	    // GET ALL INFO FROM THE GAME CLASS
		this.game = game;
		
		document.addEventListener("keydown", (event) => {
			let key = event.keyCode || event.which;
			switch(key){
				case 37:
				this.game.ship.moveRight();
				break;
				case 38:
				event.preventDefault(); this.game.ship.moveUp();
				break;
				case 39:
				this.game.ship.moveLeft();
				break;
				case 40:
				event.preventDefault(); this.game.ship.moveDown();
				break;
				case 32:
				event.preventDefault(); this.game.ship.fire();
				break;
				case 27:
				if(this.game.gameState != this.game.STATE.GAMEOVER) this.game.pause();
				break;
				case 13:
				if(this.game.gameState == this.game.STATE.GAMEOVER) location.reload();
				break;
				
			}
		});
		
		document.addEventListener("keyup", (event) => {
			let key = event.keyCode || event.which;
			switch(key){
				case 37:
				this.game.ship.stop();
				break;
				case 38:
				this.game.ship.stop();
				break;
				case 39:
				this.game.ship.stop();
				break;
				case 40:
				this.game.ship.stop();
				break;
			}
		});
		
		// MOBILE KEYBOARD
		
		document.getElementById("RIGHT").addEventListener("click", () => {
			this.game.ship.moveRight();
		});
		
		document.getElementById("UP").addEventListener("click", () => {
			this.game.ship.moveUp();
		});
		
		document.getElementById("LEFT").addEventListener("click", () => {
			this.game.ship.moveLeft();
		});
		
		document.getElementById("DOWN").addEventListener("click", () => {
			this.game.ship.moveDown();
		});
		
		document.getElementById("SPACE").addEventListener("click", () => {
			this.game.ship.fire();
		});
		
		document.getElementById("PAUSE").addEventListener("click", () => {
			if(this.game.gameState != this.game.STATE.GAMEOVER){this.game.pause();}
			else{location.reload();}
		});
		
	}
}


Table

Download



-- CREATE TABLE
CREATE TABLE `spaceinvaders`(
      rank int(11) PRIMARY KEY NOT NULL,
      username varchar(255) NOT NULL,
      score varchar(255) NOT NULL
);

-- INSERT DEFAULT RANK VARIABLES TO THE TABLE
INSERT INTO `spaceinvaders`(`rank`, `username`, `score`) VALUES (1, 'Player_1', '50000');
INSERT INTO `spaceinvaders`(`rank`, `username`, `score`) VALUES (2, 'Player_2', '40000');
INSERT INTO `spaceinvaders`(`rank`, `username`, `score`) VALUES (3, 'Player_3', '30000');
INSERT INTO `spaceinvaders`(`rank`, `username`, `score`) VALUES (4, 'Player_4', '20000');
INSERT INTO `spaceinvaders`(`rank`, `username`, `score`) VALUES (5, 'Player_5', '10000');


Score

Download



<?php

$DBServerName = "[SERVER_NAME]";
$DBUserName = "[USER_NAME]";
$DBPassword = "[PASSWORD]";
$DBName = "[DATABASE_NAME]";

$conn = mysqli_connect($DBServerName, $DBUserName, $DBPassword, $DBName);

// CREATE A TABLE ON YOUR SERVER, INCLUDING USERNAME, SCORE, AND RANK VARIABLES
$sql = "SELECT * FROM `[TABLE_NAME]`";
$result = mysqli_query($conn, $sql);

if(isset($_GET['scores'])){
	while($row = mysqli_fetch_assoc($result)){
		echo '<p>'.$row["username"].': '.$row["score"].'</p>';
	}
	exit();
}

if(isset($_GET['new_score'])){
	  $new_score = $_GET['new_score'];
	  $username = "[USER_NAME]";
	  $scores = [];
	  while($row = mysqli_fetch_assoc($result)){
	  	  array_push($scores, $row['score']);
	  }
	
	  if($new_score > $scores[0]){
		  $sql = "UPDATE `[TABLE_NAME]` SET `username` = '$username', `score` = '$new_score' WHERE rank=1";
		  mysqli_query($conn, $sql);
		  echo 'Congratulations RANK_1 exceeded :)';
		  exit();
	  }else if($new_score > $scores[1]){
		  $sql = "UPDATE `[TABLE_NAME]` SET `username` = '$username', `score` = '$new_score' WHERE rank=2";
		  mysqli_query($conn, $sql);
		  echo 'Congratulations RANK_2 exceeded :)';
		  exit();
	  }else if($new_score > $scores[2]){
		  $sql = "UPDATE `[TABLE_NAME]` SET `username` = '$username', `score` = '$new_score' WHERE rank=3";
		  mysqli_query($conn, $sql);
		  echo 'Congratulations RANK_3 exceeded :)';
		  exit();
	  }else if($new_score > $scores[3]){
		  $sql = "UPDATE `[TABLE_NAME]` SET `username` = '$username', `score` = '$new_score' WHERE rank=4";
		  mysqli_query($conn, $sql);
		  echo 'Congratulations RANK_4 exceeded :)';
		  exit();
	  }else if($new_score > $scores[4]){
		  $sql = "UPDATE `[TABLE_NAME]` SET `username` = '$username', `score` = '$new_score' WHERE rank=5";
		  mysqli_query($conn, $sql);
		  echo 'Congratulations RANK_5 exceeded :)';
		  exit();
	  }else{
		  echo 'Try again to hit a new record :)';
		  exit();
	  }
  }

 ?>