SUPERPOWERS TUTORIAL #4

SUPER ASTEROIDS and SUPER SPACEWAR, Chapter 8

Writing Collision, Life and Score systems.

To give a substance to all our actors and give the main gameplay to our game we need to set the collision system without which the actors would never interact to each other.

Collision system

The Game.checkCollisionMissile function

We add a new function that we will call each time we need to check a collision with a missile list, we put it in the Module Game of the Global script.

namespace Game{
[...] 
  export function checkCollisionMissile(actor: Sup.Actor, actorList: Sup.Actor[], amplitude: number){
    /* 
    We get the distance between actor and all the actors of the actorList
    If the distance if inferior to the amplitude box around the Actor, there is a collision
    */

    // Get the position 1 of the actor we check collision with actorList
    let position1: Sup.Math.Vector2 = actor.getLocalPosition().toVector2();
    // Loop through the actors of listToCheck and check if ....
    for(let i = 0; i < actorList.length; i++){
      // Get the position 2 of the current actor of the loop inside actorList
      let position2: Sup.Math.Vector2 = actorList[i].getLocalPosition().toVector2();
      // Get the distance between position 1 and position 2
      let distance: number = Math.round(position1.distanceTo(position2)*100)/100;
      // If the distance is inferior to the amplitude (collision radius), then it is a collision
      if (distance < amplitude) {
        // The current actor of the actorList is destroyed
        actorList[i].destroy();
        // Return true to the behavior which check for collision
        return true;
      }
    }
    // Return false to the behavior which check for collision
    return false;
  }
}
[...]
The Game.checkCollisionAsteroid function

We add a new function that we will call each time we need to check a collision with an asteroid list (different amplitude for each asteroid)

namespace Game{
[...]   
  export function checkCollisionAsteroids(ship: Sup.Actor, shipAmplitude: number) {
    /*
      Get the distance between the ship and the asteroids, compare the distance with the biggest amplitude
    */
    // Initialize a variable asteroid which will take all the asteroids of the list as value
    let asteroid: Sup.Actor;
    // Get the current position of the ship
    let shipPosition: Sup.Math.Vector2 = ship.getLocalPosition().toVector2();
    // Loop through all the asteroids in game
    for(asteroid of Asteroids.list){
      // Get the amplitude of the current asteroid
      let asteroidAmplitude: number = asteroid.getBehavior(AsteroidBehavior).amplitude;
      // Get the position of the current asteroid
      let asteroidPosition: Sup.Math.Vector2 = asteroid.getLocalPosition().toVector2();
      // Convert to distance between ship and current asteroid positions
      let distance: number = Math.round(shipPosition.distanceTo(asteroidPosition)*100)/100;
      // Check if distance is less than the biggest amplitude 
      if (distance < shipAmplitude || distance < asteroidAmplitude) {
        // Destroy the current asteroid
        asteroid.getBehavior(AsteroidBehavior).die();
        // Return true, the ship is destroyed too
        return true;
      }
    }
    return false;
  }
}
[...]
The Game.checkCollisionShip function

We add a new function that we will call each time we need to check a collision with an other ship (alien or other player)

namespace Game{
[...]   
  // Check collision between actor1 and actor2
  export function checkCollisionShip(actor1: Sup.Actor, actor1Amplitude: number) {
    // Initialize variable for the actors
    let actor2: Sup.Actor;
    let actor1Position: Sup.Math.Vector2;
    let actor2Position: Sup.Math.Vector2;
    let actor2Amplitude: number;
    let actor2Alive: boolean;
    // If the actor1 is the Ship1, then we check the Ship2 as actor2
    if (actor1.getName() === "Ship1"){
      actor2 = Sup.getActor("Ship2");
    }
    // Else, if the actor1 is Ship2 or Alien Ship, we check the Ship1 as actor2
    else {
      actor2 = Sup.getActor("Ship1");
    }
    // Get the positions of both actors
    actor2Position = actor2.getLocalPosition().toVector2();
    actor1Position = actor1.getLocalPosition().toVector2();
    // Get amplitud of actor 2
    actor2Amplitude = Ships.amplitude;
    // Get the life status of the actor 2
    actor2Alive = actor2.getBehavior(ShipBehavior).alive;
    // Get the distance between the two actors
    let distance: number = Math.round(actor1Position.distanceTo(actor2Position)*100)/100;
    // To avoid to collide when ship is blinking with invulnerability, return false the collisionChecking if actor 2 not alive
    if(!actor2Alive){
      return false;
    }
    // Check if distance if less than the biggest amplitude (or collision radius)
    if (distance < actor1Amplitude || distance < actor2Amplitude) {
        // Destroy the current asteroid
        actor2.getBehavior(ShipBehavior).die();
        // Return true, both actor are destroyed
        return true;
      }
    // If condition not met, return false
    return false
  }
}
[...]
Collision alien ship with player missile

Inside the update loop of the Alien script, we call a Game.checkCollisionMissile with the ship missiles of the player.

[...]  
  update() {
  [...]
    // If there is player ship1 missiles, call a collision checking for this actor with the list of the player Ship missiles
    if(Ships.missiles[0].length > 0){
      // If the collision checking return true
      if (Game.checkCollisionMissile(this.actor, Ships.missiles[0], this.amplitude)) {
        // Decrease Alien lifes by one
        Alien.lifes--
        // Flag true to check and update the life HUD in Game Script
        Game.checkLifeHUD = true;
      }
    }
  }
[...]
Collision asteroid with player missile

Inside the update loop of the Asteroid script, we call a Game.checkCollisionMissile with the ship missiles of the player. If the collision is true the asteroid is destroyed and two new asteroids spawn.

[...]
  update() {
  [...]
    // If there is player ship1 missiles, call a collision checking for this actor with the list of the player Ship missiles
    if(Ships.missiles[0].length > 0){
      // If the collision checking return true
      if (Game.checkCollisionMissile(this.actor, Ships.missiles[0], this.amplitude)) {
        // Destroy this asteroid
        this.die();
        // If the asteroid is not small
        if (this.sizeClass !== "small"){
          // Spawn two new asteroid of the class below
          this.spawnChildren();
        }
      }
    }
  }
[...]
Collision player ship with asteroids

Inside the Ship script, if the game is Super Asteroids we now check for collision betwen the ship and the alien missiles and all the asteroids in the update loop.

If the game is Super Spacewar we check collision with the missile of other ship or a collision with the other ship.

[...]
  update() {
  [...]
    // If game is Super Spacewar, check collision with other missiles and other ship
    if (Game.nameIndex === 0) { 
      // Check collision between the player ship and the asteroids
      if (Game.checkCollisionAsteroids(this.actor, this.amplitude)){
        this.die();
      }
    }
[...]
Collision player ship with alien missile

Still in the Ship script, in the same block that check collision in the case the game is Asteroids (index 0).

[...]
  update() {
  [...]
    if (Game.nameIndex === 0) {
    [...]
      // Check collision between the player ship and the alien missiles
      if (Game.checkCollisionMissile(this.actor, Alien.missiles, this.amplitude)){
        this.die();
      }
    }
[...]
Collision player ship with alien ship

In the Alien script, we add a call of the Game.checkCollisionShip function.

[...]
  update() {
  [...]
    // Check collision between Alien ship and Player ship
    if (Game.checkCollisionShip(this.actor, this.amplitude)){
      // Destroy the alien ship by setting its lifes to 0
      Alien.lifes = 0;
    }
    }
[...]
Collision player ship with other player missiles

In the Ship script we add an other block of checking in the case the game is Spacewar (index 1)

[...]    
    // If game is Super Spacewar, check collision with other missiles and other ship
    if (Game.nameIndex === 1) {
      // If the ship is 1 check collision with ship 2 missile
      if (this.index === 0){
        if (Game.checkCollisionMissile(this.actor, Ships.missiles[1], this.amplitude)){
          // if there is collision, add point to the ship 2 and destroy ship 1
          Game.addPoints(false, Game.points.ship);
          this.die();
        }
      }
      // Else the ship is 2 check collision with ship 1 missile
      else {
        if (Game.checkCollisionMissile(this.actor, Ships.missiles[0], this.amplitude)){
          // if there is collision, add point to the ship 1 and destroy ship 2
          Game.addPoints(true, Game.points.ship);
          this.die();
        }
      }
    }
[...]

Note : To do test, we need to comment the Game.addPoints function.

Collision player ship with other player ship
[...]
    if (Game.nameIndex === 1) {
        [...]
      // Check collision between this ship and the other ship
      if (Game.checkCollisionShip(this.actor, this.amplitude)) {
            this.die();
      }
    }
[...]

The collision work now fine, we need now to update the Game HUD when something happen in the game.

Score system

Global script : Game.addPoints function

We can now create the function that addPoints to the player score, to make the difference between the score of ship 1 and ship 2, we simply use a boolean parameter, when it is true, we add points to ship 1 else, we add them to ship 2.

namespace Game{
  [...]
  // Add points to the ship score
  export function addPoints(ship1: boolean, points: number){
    // If ship1 is true, add points to ship1
    if(ship1){
      Sup.getActor("Ship1").getBehavior(ShipBehavior).score += points;
    }
    // If ship1 is false, add points to ship2
    else {
      Sup.getActor("Ship2").getBehavior(ShipBehavior).score += points;
    }
  }
}
[...]

Note : to make the function work in all our game, we need to uncomment every place we have a Game.addPoints function in ou game. In the Ship script, the asteroid script and the alien script.

Global script : Game.updateHUDScore

Inside the Global script, we add a function updateHUDScore update in the Game module.

namespace Game{
  [...]
  // Update the score HUD with the score for ship 1 or optionally for ship2
  export function updateHUDScore(Score1:number, Score2?:number) {
    // Change the score text displayed on HUD with the new score
    Sup.getActor("HUD").getChild("UIShip1").getChild("Score").textRenderer.setText(Score1);
    // If there is a score 2, do the same for the HUD of ship 1 and ship 2
    if(Score2){
        Sup.getActor("HUD").getChild("UIShip1").getChild("Score").textRenderer.setText(Score1);
        Sup.getActor("HUD").getChild("UIShip2").getChild("Score").textRenderer.setText(Score2);
    }
    // Set flag to false
    Game.checkScoreHUD = false;
  }
}
[...]
Game script : checkHUDScore

Now, in the Game script we simply check the flag checkHUDScore to see when to call the Game.updateHUDScore function.

[...]
  update() {  
    // Check if score need to be updated with the HUD
    if (Game.checkScoreHUD) {
      // If the game is spacewar, we update the two ship scores
      if (Game.nameIndex == 1) {
        let player1Score = Sup.getActor("Ship1").getBehavior(ShipBehavior).score;
        let player2Score = Sup.getActor("Ship2").getBehavior(ShipBehavior).score;
        Game.updateHUDScore(player1Score, player2Score);
      }
      // Else the game is asteroids, we update the ship 1 score
      else {
        let playerScore = Sup.getActor("Ship1").getBehavior(ShipBehavior).score;
        Game.updateHUDScore(playerScore);
      }
    }
  }
[...]

Life system

Global script : Game.updateHUDLife

Inside the Global script, we add a function updateHUDLife update in the Game module.

namespace Game{
  [...]  
  // Update the life HUD
  export function updateHUDLife(life1:number, life2:number){
    /*
    Explanation of life 
    */
    // If the game is Asteroids
    if (Game.nameIndex === 0){
      // Update the player Ship life
      for (let i = 0; i < Ships.startLife; i++) {
        let heart = Sup.getActor("HUD").getChild("UIShip1").getChild("Life").getChild(i.toString());
        heart.spriteRenderer.setAnimation(hearts[life1][i]);
      }

      // Update the alien life
      for (let i = 0; i < Alien.startLife; i++) {
        let heart = Sup.getActor("HUD").getChild("UIAlien").getChild(i.toString());
        heart.spriteRenderer.setAnimation(hearts[life2][i]);
      }      
    }

    if (Game.nameIndex === 1){
      // player1 life
      for (let i = 0; i < Ships.startLife; i++) {
        let heart = Sup.getActor("HUD").getChild("UIShip1").getChild("Life").getChild(i.toString());
        heart.spriteRenderer.setAnimation(hearts[life1][i]);
      }

      // player2 life
      for (let i = 0; i < Ships.startLife; i++) {
        let heart = Sup.getActor("HUD").getChild("UIShip2").getChild("Life").getChild(i.toString());
        heart.spriteRenderer.setAnimation(hearts[life2][i]);
      }
    }
    // Set false to flag checkLifeHUD
    Game.checkLifeHUD = false;
  }
}
[...]
Game script : checkHUDlife

Now, in the Game script we simply check the flag checkHUDLife to see when to call the Game.updateHUDLife function.

[...]
  update() {  
    // Check if lifes need to be updated with the HUD
    if (Game.checkLifeHUD) {
      if (Game.nameIndex === 0) {
        let playerLife = Sup.getActor("Ship1").getBehavior(ShipBehavior).lifes;
        let alienLife = Alien.lifes;
        Game.updateHUDLife(playerLife, alienLife);
      }
      if (Game.nameIndex === 1) {
        let player1Life = Sup.getActor("Ship1").getBehavior(ShipBehavior).lifes;
        let player2Life = Sup.getActor("Ship2").getBehavior(ShipBehavior).lifes;
        Game.updateHUDLife(player1Life, player2Life);
      }
    }
  }
[...]

The HUD working fine, we need to work the menu screen inside our game mechanic to be also able to conclude the game when the ship have no more life or the timer is out with a game over screen. Let's finish our game with the next chapter.

<-- back to chapter 7 -- go to chapter 9 -->