How to code Jet Fighter (1975) – 4

Since starting this series I have been looking forward to coding up Jet Fighter as it is where I got a lot of inspiration for my game currently running at spaceheir.com. The idea was to create a mashup of Asteroids and Jet Fighter. Taking the Player vs environment aspects of Asteroids; shooting asteroids to gain levels. And the player vs player combat system of Jet Fighter.

What is Jet Fighter?

The game is beautifully simple, Player 1 controls a black jet and player 2 controls a white jet. The black jet shoots black bullets, the white jet shoots white bullets. If you hit the opposing player you gain a point represented by the score corresponding to your colour!

If you go off the end of the map you appear on the other side, same goes for the bullets.

The Jet

The first thing we need to code up is the Jet. Here are some things we need to consider.

  • The Jet moves the direction it is facing.
  • The jet shoots in the direction it is facing.
  • If the jet goes off the map, it will appear the other side

The Images

I’ve created both the black and white jets in photoshop. Go ahead and copy these for your implementation!

Black Jet
White jet (Good luck finding it)

Drawing the jets

Let’s spawn the jets in a random location on the map, by creating a Jet class.

class Jet {
  constructor(image) {
    this.x = random(width);
    this.y = random(height);
    this.image = image;
  }
  
  
  draw() {
    image(this.image, this.x, this.y);
  }
}

Let’s load the images in p5.js preload function, as this executes before setup and draw.

let blackJet;
let whiteJet;

let blackJetImage;
let whiteJetImage;

function preload() {
  blackJetImage = loadImage("black-jet.png");
  whiteJetImage = loadImage("white-jet.png");
}

function setup() {
  createCanvas(400, 400);
  blackJet = new Jet(blackJetImage);
  whiteJet = new Jet(whiteJetImage);
}

function draw() {
  background(130);
  
  
  blackJet.draw();
  whiteJet.draw();
}

Go the way we’re facing!

Let’s make the jets go forwards, like this.

I’ve added some comments to highlight what’s been added. Oh yeah, don’t forget to call update on the jets in sketch.js.

class Jet {
  constructor(image) {
    this.x = random(width);
    this.y = random(height);
    this.image = image;
    
    this.angle = 0;
    this.speed = 0.7;
  }
  
  // added an update function
  update() {
    this.goTheWayWereFacing();
    this.constrainToMap();
  }
  
  // make them go forwards!
  goTheWayWereFacing() {
    this.x += this.speed * sin(this.angle);
    this.y += this.speed * cos(this.angle);
  }
  
  // lets keep on the map now you buggers
  constrainToMap() {
    if (this.x < -this.image.width) {
    	this.x = width;
    } else if (this.x > width) {
    	this.x = 0;
    } 
    
    if (this.y > height) {
    	this.y = 0;
    } else if (this.y < -this.image.height) {
    	this.y = height;
    }
  }
  
  
  draw() {
    // the push/pop is to prevent the rotation from happening to other stuff!
    push();
    translate(this.x, this.y);
    imageMode(CENTER);
    // the plus HALF_PI bit is because I drew the images upside down, sorry!
    rotate(this.angle + HALF_PI);
    
    image(this.image, 0, 0);
    pop();
  }
}

Steering!

I’ve coded it so the black jet uses the arrow keys to move and the white jet uses a and d.

The rotation stuff is pretty much identical to the previous tutorial for how to code Gran Trak 10. Don’t worry if you haven’t seen it the concept is pretty simple If the player clicks a key we then set the rotateAmount which is added to the angle each frame. If the player lets go of the key we set the
rotateAmount to 0, so it doesn’t rotate.

Jet class

class Jet {
  constructor(image) {
    this.x = random(width);
    this.y = random(height);
    this.image = image;
    
    this.angle = 0;
    this.speed = 0.7;
    
    // added this new rotate amount variable
    this.rotateAmount = 0;
  }
  

  update() {
    this.goTheWayWereFacing();
    this.constrainToMap();
    
    // increment the angle each frame
    this.angle += this.rotateAmount;
  }

Sketch

// set ROTATE_AMOUNT at the top of the file, I've set it to 0.05
// have a play around with the numbers, find a rotate amount that you like

function keyPressed() {
  if (keyCode === RIGHT_ARROW) {
    blackJet.rotateAmount = ROTATE_AMOUNT;
  } else if (keyCode === LEFT_ARROW) {
    blackJet.rotateAmount = -ROTATE_AMOUNT;
  } else if (keyCode === 68) {
    // d
    whiteJet.rotateAmount = ROTATE_AMOUNT;
  } else if (keyCode === 65) { 
  	// a
    whiteJet.rotateAmount = -ROTATE_AMOUNT;
  }
}

function keyReleased() {
  if (keyCode === RIGHT_ARROW || keyCode === LEFT_ARROW) {
    blackJet.rotateAmount = 0;
  } else if (keyCode === 65 || keyCode === 68) {
    whiteJet.rotateAmount = 0;
  }
}

Bullets!

Bullets require a lot of functionality that the Jets do, they need to go in the direction that the ship was facing when it was shot. They also need to be constrained to the map.

Bullet class

Created a new Bullet class, this takes the x and y position of the jet at the point the bullet was shot, as well as the angle, and a new isWhite flag which is used to determine the colour of the bullet.

class Bullet {
  constructor(x, y, angle, isWhite) {
    this.x = x;
    this.y = y;
    this.angle = angle;
    this.isWhite = isWhite;
    this.speed = 2;
    this.r = 1.5;
  }
  
  
  update() {
    this.x += this.speed * cos(this.angle);
    this.y += this.speed * sin(this.angle);
    this.constrainToMap();
  }
  
  
  constrainToMap() {
    if (this.x < -this.r) {
    	this.x = width;
    } else if (this.x > width) {
    	this.x = 0;
    } 
    
    if (this.y > height) {
    	this.y = 0;
    } else if (this.y < -this.r) {
    	this.y = height;
    }
  }
  
  
  draw() {
    if (this.isWhite) {
      push();
      noStroke();
      fill(255);
      ellipse(this.x, this.y, this.r*2, this.r*2);	
      pop();
    } else  {
      push();
      fill(0);
      ellipse(this.x, this.y, 3, 3);	
      pop();
    }
  }

}

Add a new bullets array to the Jet class. Once again, I’ve added comments so you can quickly see what has been added.

class Jet {
	// added an is white flag, so we know which bullets to shoot!
  constructor(image, isWhite) {
    this.x = random(width);
    this.y = random(height);
    this.image = image;
    
    this.angle = 0;
    this.speed = 0.7;
    
    
    this.rotateAmount = 0;
    
    this.bullets = [];
    this.isWhite = isWhite;
  }
  

  update() {
    this.goTheWayWereFacing();
    this.constrainToMap();
    
    // increment the angle each frame
    this.angle += this.rotateAmount;
  }
  
  goTheWayWereFacing() {
    this.x += this.speed * cos(this.angle);
    this.y += this.speed * sin(this.angle);
  }
  

  constrainToMap() {
    if (this.x < -this.image.width) {
    	this.x = width;
    } else if (this.x > width) {
    	this.x = 0;
    } 
    
    if (this.y > height) {
    	this.y = 0;
    } else if (this.y < -this.image.height) {
    	this.y = height;
    }
  }
  
  
  // when the player shoots add to the bullet array
  shoot() {
    let bullet = new Bullet(this.x, this.y, this.angle, this.isWhite);
    this.bullets.push(bullet);
  }
  
  draw() {
    push();
    translate(this.x, this.y);
    imageMode(CENTER);
    rotate(this.angle + HALF_PI);
    image(this.image, 0, 0);
    pop();
    
    // call to draw and update the buggers
    this.drawBullets();
  }
  
  // draw and update the buggers
  drawBullets() {
    for (let bullet of this.bullets) {
      bullet.update();
      bullet.draw();
    }
  
  }
  
}

And then just update your sketch so you pass that new isWhite flag in, so true if it is white and false if it isn’t.

Make the bullets de-spawn!

We don’t want every bullet ever shot to be in circulation forever as our game would look really silly and probably blow up after a while. Let’s make the bullets de-spawn after some time of being alive, when they de-spawn we then remove them from the bullets array.

So in the bullets class let’s add a new field, we will call it timeAlive, which we will increment every frame.

   constructor(x, y, angle, isWhite) {
    this.x = x;
    this.y = y;
    this.angle = angle;
    this.isWhite = isWhite;
    this.speed = 2;
    this.r = 1.5;
    console.log(this.angle);
    // this bugger was added
    this.timeAlive = 0;
  }
  
  
  update() {
    this.x += this.speed * cos(this.angle);
    this.y += this.speed * sin(this.angle);
    
    this.constrainToMap();
    
    // increment the time alive
    this.timeAlive++;
  }
  

Then let’s check each bullet to see if it has been alive for longer than a certain period of time, if it has remove it. This is in our Jet class where we draw and update the bullets.

Notice how we loop through the bullets backwards, see what happens if you loop through it forwards!

  // draw and update the buggers
  drawBullets() {
    
    for (let i = this.bullets.length - 1; i >= 0; i--) {
      this.bullets[i].update();
      this.bullets[i].draw(); 
      
      if (this.bullets[i].timeAlive > 200) {
      	this.bullets.splice(i, 1);
      }
    }
  	
  }

Scoring system

It’s no game without a scoring system, so let’s add one!

Hitting an enemy

Each frame, we need to loop through all of the bullets and check against their target jet if they have hit them. So we need to check all of the white bullets against the black jet and all the black bullets against the white jet.

To do that, let’s pass the bullets array of the other jet into the update function.

Sketch.js


function draw() {
  background(130);
  
  // add the enemy bullets into the update function
  blackJet.update(whiteJet.bullets);
  whiteJet.update(blackJet.bullets);
  
  blackJet.draw();
  whiteJet.draw(); 
}

In the Jet class we need to loop through the bullets, if the bullet has hit the current ship we need to remove that bullet from the array.

  update(enemyBullets) {
    this.goTheWayWereFacing();
    this.constrainToMap();
    

    this.angle += this.rotateAmount;

    // new function to check if player has been hit
    this.processBeingHitByBullet(enemyBullets);
  }
  
  processBeingHitByBullet(enemyBullets) {
  
    // I use 10 as the radius for the ship, you can tweak it
    for (let i = enemyBullets.length - 1; i >= 0; i--) {
    	if (dist(this.x, this.y, enemyBullets[i].x, enemyBullets[i].y) < (10 + enemyBullets[i].r)){ 
        enemyBullets.splice(i, 1);
      }
    }
  }

Displaying scores!

As is a tradition with these tutorials I leave a little bit of development left to do so you can get stuck in and write some code. This one may require you to restructure how we’ve just implemented the bullets hitting the player.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s