SUPERPOWERS TUTORIAL #4

SUPER ASTEROIDS and SUPER SPACEWAR, Chapter 12

Complete Game Source Reference

Game structure

  • Global (script)
  • Game (folder)
    • Scene (scene)
    • Game (script)
  • Menu (folder)
    • Scene (scene)
    • Menu (script)
    • Screens (folder)
      • Main (folder)
        • Sprite (sprite)
      • Asteroids (folder)
        • Sprite (sprite)
      • Spacewar (folder)
        • Sprite (sprite)
      • GameOver (folder)
        • Sprite (sprite)
    • Button (folder)
      • Sprite (sprite)
  • Background (folder)
    • Background (script)
    • Sprite (sprite)
  • Ship (folder)
    • Ship (script)
    • Missile (folder)
      • Missile (script)
    • Boost (folder)
      • Sprite (sprite)
    • 0 (folder)
      • Model (3D model)
      • Prefab (scene)
      • Life (folder)
        • Sprite (sprite)
      • Missile (folder)
        • Prefab (scene)
        • Sprite (sprite)
      • Explosion (folder)
        • Sprite (sprite)
    • 1 (folder)
      • Model (3D model)
      • Prefab (scene)
      • Life (folder)
        • Sprite (sprite)
      • Missile (folder)
        • Prefab (scene)
        • Sprite (sprite)
      • Explosion (folder)
        • Sprite (sprite)
  • Alien (folder)
    • Alien (script)
    • Model (3D model)
    • Prefab (scene)
    • Missile (folder)
      • Missile (script)
      • Prefab (scene)
      • Sprite (sprite)
    • Life (folder)
      • Sprite (sprite)
    • Explosion (folder)
      • Sprite (sprite)
  • Asteroid (folder)
    • Asteroid (script)
    • Model (3D model)
    • Prefab (scene)
    • Explosion (folder)
      • Sprite (sprite)
  • Sounds
    • music
    • shipShot
    • alienShot
    • explosion
    • shotContact
  • Font

Source assets

You can see the repository of this assets here.

  • alien
    • alien.blend
    • alien.obj
    • alienExplosion.png
    • alienLife.png
    • alienMissile.png
    • alienTexture.png
  • asteroid
    • asteroid.blend
    • asteroid.obj
    • asteroidExplosion.png
    • asteroidTexture.png
  • castle
    • // will be updated
  • menu
    • asteroids.png
    • button.png
    • gameover.png
    • main.png
    • spacewar.png
  • scripts
    • Alien.ts
    • Asteroids.ts
    • Background.ts
    • Game.ts
    • Global.ts
    • Menu.ts
    • Missile.ts (Alien)
    • Missile.ts (Ship)
    • Ship.ts
  • ships
    • boost.png
    • ship.blend
    • ship.obj
    • ship1explosion.png
    • ship1life.png
    • ship1missile.png
    • ship1texture.png
    • ship2explosion.png
    • ship2life.png
    • ship2missile.png
    • ship2texture.png
  • sounds
    • alienShot.mp3
    • explosion.mp3
    • music.mp3
    • shipShot.mp3
    • shotContact.mp3
  • background.png
  • font.png

Source code

Global.ts
// SUPER ASTEROIDS and SUPER SPACEWAR
// Tutorial #4 Game Development with Superpowers

// Main game datas and functions
namespace Game{

  // Game timer max value
  export const timeMax: number = 7200; //7200 2 minutes

  // The game index of the current played game, 0 for Asteroids, 1 for Spacewar
  export let nameIndex: number = 0; // Temporary allocation

  // The screen limits in width and height + outside border
  export let bounds : {
    width:number,
    height:number
  }

  // set an invisible border of 2 x 1.5 units (24 pixels wide) around the screen
  let border: number = 3;

  // Game points won when target shot
  export enum points {
    asteroidBig = 10,
    asteroidMedium = 15,
    asteroidSmall = 20,
    alien = 50,
    ship = 100 ,
    death = -50,
     }

  // Life hearts positions
  export const hearts = [
    ["empty", "empty", "empty", "empty", "empty"],
    ["full", "empty", "empty", "empty", "empty"],
    ["full", "full", "empty", "empty", "empty"],
    ["full", "full", "full", "empty", "empty"],
    ["full", "full", "full", "full", "empty"],
    ["full", "full", "full", "full", "full"]
  ]

  // Flags to check HUD changes
  export var checkLifeHUD: boolean;
  export var checkScoreHUD: boolean;

  // Start the game
  export function start(){
    // Load Game Scene
    Sup.loadScene("Game/Scene");

    // Set new asteroids list
    Asteroids.list = [];
    // Set asteroids number to 0
    Asteroids.currentCount = 0;

    // Set new Ships.missiles list
    Ships.missiles = [[], []];
    // Set new Alien.missiles list
    Alien.missiles = [];


    // get the Camera actor the the game Scene
    let screen = Sup.getActor("Camera").camera;
    // We get the game screen bounds and add the invisible border
    bounds = {
      width: screen.getOrthographicScale() + border,
      height: screen.getOrthographicScale() + border
    }

    // If the game is Asteroids, load the Ship 1 only
    if (nameIndex === 0) {
      // Set ship 1 in game
      setShip(Ships.index.ship1);
      // Set visible false the HUD display for ship 2
      Sup.getActor("HUD").getChild("UIShip2").setVisible(false);
      // Set visible true the HUD display for alien
      Sup.getActor("HUD").getChild("UIAlien").setVisible(true);
      // Set Timer HUD visible false
      Sup.getActor("HUD").getChild("Timer").setVisible(true);
    }
    // If the game is Spacewar, load two ships
    if (nameIndex === 1) {
      // Set ship 1 in game
      setShip(Ships.index.ship1);
      // Set ship 2 in game
      setShip(Ships.index.ship2);
      // Set visible true the HUD display for ship 2
      Sup.getActor("HUD").getChild("UIShip2").setVisible(true);
      // Set visible false the HUD display for alien
      Sup.getActor("HUD").getChild("UIAlien").setVisible(false);
      // Set Timer HUD visible false
      Sup.getActor("HUD").getChild("Timer").setVisible(false);
    }
  }

  function setShip(shipIndex:number){
    // Initialize a new variable of type Sup.Actor
    let Ship: Sup.Actor;
    // Add the ship 1 or 2 to game scene depending the index
    if (shipIndex === 0) {
      // Create Ship and set the ship variable with the Ship1 actor
      Ship = Sup.appendScene("Ship/0/Prefab")[0];
    }
    else {
      // Create Ship and set the ship variable with the Ship2 actor
      Ship = Sup.appendScene("Ship/1/Prefab")[0];
    }
    // Set behavior variables
    Ship.getBehavior(ShipBehavior).index = shipIndex;
    // Set spawn position of the ship accordingly to the game an ship index
    // If game is Asteroids, set position to center
    if (nameIndex === 0) {
      Ship.setLocalPosition(Ships.spawns[0]);
    }
    // If game is Spacewar and Ship is 1, set position to corner down left
    else if (shipIndex === 0) {
      Ship.setLocalPosition(Ships.spawns[1]);
    }
    // Else it is Ship 2, set position to corner up right
    else {
      Ship.setLocalPosition(Ships.spawns[2]);
    }
  }

  // Create an alien in the Game scene              
  export function spawnAlien(){
    // Load the alien prefab and get the Alien actor (index 0) as a new alien variable
    let alien = Sup.appendScene("Alien/Prefab")[0];

    // Choose a random position somewhere at the vertical edges of the game screen
    // Create the three axis positions x, y, z
    let x: number = 0, y: number = 0, z: number = 0;

    // Choose randomly a position on one of the vertical edges
    x = Sup.Math.Random.sample([-bounds.width / 2, bounds.width / 2]);
    // Get the height without the invisible border
    let height = bounds.height - border
    // Choose randomly a position on y axis
    y = Sup.Math.Random.integer(-height / 2 , height / 2);
    // Get alien default Z position
    z = Alien.zPosition;

    // Set the randomly chosen position to the Alien actor
    alien.setLocalPosition(x, y, z);
  }

  // Create an asteroid in the Game scene
  export function spawnAsteroid(){
    // Load the asteroid prefab and get the Asteroid actor (index 0) as a new asteroid variable
    let asteroid = Sup.appendScene("Asteroid/Prefab")[0];

    // Choose a random position somewhere at the edges of the game screen
    // Create the three axis positions x, y, z
    let x: number = 0, y: number = 0, z: number = 0;

    // Choose randomly if the asteroids come from the horizontal or vertical edges
    if(Sup.Math.Random.sample([0, 1]) === 0){
      x = Sup.Math.Random.sample([-bounds.width / 2, bounds.width / 2]);
      y = Sup.Math.Random.integer(-bounds.height / 2, bounds.height / 2);
    }
    else {
      x = Sup.Math.Random.integer(-bounds.height / 2, bounds.height / 2);
      y = Sup.Math.Random.sample([-bounds.width / 2, bounds.width / 2]);
    }
    // Choose randomly the z position on the scene in the range of Asteroids.zPoistions
    z = Sup.Math.Random.integer(Asteroids.zPositions.min, Asteroids.zPositions.max);

    // Set the randomly chosen position to the Asteroid actor
    asteroid.setLocalPosition(x, y, z);

    // Report the asteroid size
    asteroid.getBehavior(AsteroidBehavior).sizeClass = "big";
  }

  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;
  }

  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;
  }

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

  // Update HUD timer in the Game scene
  export function updateTimer(time: number){
    // Sup.log("one second (60 frames) of", time, "frame left."); // Debug log
    // We convert frames in minutes and seconds
    let minutes = Math.floor((time / 60) / 60);
    let seconds = Math.floor((time / 60) % 60);
    // We get The Timer actor from the Game scene and we update the text Renderer with the new time
    let timer = Sup.getActor("HUD").getChild("Timer");
    // For the last 10 seconds we need to add a 0 to keep 2 numbers in the timer
    if (seconds < 10) {
      timer.textRenderer.setText(minutes + ":0" + seconds);
    }
    else {
      timer.textRenderer.setText(minutes + ":" + seconds);
    }
  }

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

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

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

  // Load the gameOver screen and display score
  export function gameOver(winner: string){
    // Initialize variables
    let score1: number;
    let score2: number;
    // Store the ship1 score before to leave the scene
    score1 = Sup.getActor("Ship1").getBehavior(ShipBehavior).score;
    // If the game is spacewar, stock also the Ship2 score
    if (nameIndex === 1) {
      score2 = Sup.getActor("Ship2").getBehavior(ShipBehavior).score;
    }
    // Close game scene and load menu scene
    Sup.loadScene("Menu/Scene");
    // Set game over Screen
    Sup.getActor("Menu").getBehavior(MenuBehavior).setScreen(Menu.screens.gameover);
    // Get in a variable gameOverScreen the gameover screen actor
    let gameOverScreen = Sup.getActor("Screens").getChild(Menu.screens.gameover.toString());
    // Set the Sprite from the actor to display the winner frame
    gameOverScreen.spriteRenderer.setAnimation(winner);
    // Display Score
    // Set title visible false
    Sup.getActor("Title").setVisible(false);
    // Get the Score actor in a variable
    let score: Sup.Actor = Sup.getActor("Score");
    // Set the Score actor visible
    score.setVisible(true);
    // Set the score text to the current score of ship1
    score.getChild("Ship1").textRenderer.setText("Ship1:"+score1);
    // If the game is spacewar
    if (nameIndex === 1) {
      // Set visible true the score of Ship2
      score.getChild("Ship2").setVisible(true);
      // Set the score text to the current score of ship2
      score.getChild("Ship2").textRenderer.setText("Ship2:"+score2);

    }
    else {
      // Set visible false the ship2 score
      score.getChild("Ship2").setVisible(false);
    }
  }
}

// Menu datas
namespace Menu{

  // Different menu screen index
  export const screens = {
      main : "Main",
      asteroids : "Asteroids",
      spacewar : "Spacewar",
      gameover : "GameOver",
     }

  // Game names index
  export enum names {
        Asteroids = 0,
        Spacewar = 1,
       }
}

// Ship datas
namespace Ships{
  // Default ship size
  export const size: number = 0.5;
  // Default ship collision radius
  export const amplitude: number = 1.5;
  // Starting ship score
  export const startScore: number = 0;
  // Starting ship life
  export const startLife: number = 3;

  // Starting spawn positions
  export const spawns: Sup.Math.Vector3[] = [
    new Sup.Math.Vector3(0, 0, 14), // ship1 for asteroids game
    new Sup.Math.Vector3(-4, -12, 2), // ship1 for spacewar game
    new Sup.Math.Vector3(4, 12, 4) // ship1 for spacewar game
  ] 

  // ship index
  export enum index {
    ship1 = 0,
    ship2 = 1
    };

  // Starting time before next shoot
  export const shootingTimer: number = 30;
  // Starting time before respawn
  export const respawnTimer: number = 180;
  // Starting time before vulnerability
  export const invincibleTimer: number = 200;

  // Linear speed
  export const linearAcceleration: number = 0.005;
  // Linear slowing down
  export const linearDamping: number = 0.97;

  // Rotation speed
  export const angularAcceleration: number = 0.02;
  // Rotation slowing down
  export const angularDamping: number = 0.75;

  // Commands for each ship by index
  export const commands = [
    {left:"LEFT", right:"RIGHT", forward:"UP", shoot:"CONTROL", boost:"SHIFT"}, // commands[0]
    {left:"A", right:"D", forward:"W", shoot:"SPACE", boost:"C"} // commands[1]
  ];

  // Missiles list for each ship by index
  export let missiles: Sup.Actor[][];
  // Starting missile life before destruction (frames)
  export const missileLife: number = 60;
  // Missile speed (unit/frame)
  export const missileSpeed: number = 0.30;
}


// Alien datas
namespace Alien{
  // Flag for alien alive
  export let alive: boolean = true;

  // Starting alien life
  export const startLife: number = 5;
  // Current alien life
  export let lifes: number;

  // Alien z position
  export const zPosition: number = 12;

  // Different alien sizes
  export const sizes: number[] = [1.7, 1.3, 1];
  // Different collision amplitude related to size
  export const amplitudes: number[] = [2.5, 2.2, 2];

  // Linear and rotation speed of alien ship
  export let linearSpeed: number = 0.05;
  export let rotationSpeed: number = 0.01;

  // Starting time before alien ship respawn
  export const respawnTime: number = 300;
  // Current time befer alien ship respawn
  export let spawnTimer: number = 0;

  // Starting time before alien ship shoot again
  export const shootTime: number = 200;

  // Alien missile list
  export let missiles: Sup.Actor[];
  // Alien missile speed (unit/frame)
  export const missileSpeed: number = 0.05;
}

// Asteroids datas
namespace Asteroids{
  // List of all current asteroids
  export let list: Sup.Actor[];

  // Differents size of asteroids
  export const sizes = {"big":1.5, "medium":1, "small":0.5};
  // Different collision amplitude related to size
  export const amplitudes = {"big":2.5, "medium":2, "small":1}

  // Range for Z positions of asteroids
  export enum zPositions {min = -28, max = 10};

  // Starting asteroids number
  export const startCount: number = 5;
  // Current asteroids number
  export let currentCount: number;
  // Maximum asteroids number
  export const maxCount: number = 10;

  // Range of linear and rotation speed of asteroids
  export enum linearSpeed {min = -0.05, max = 0.05};
  export enum rotationSpeed {min = -0.01, max = 0.01};

  // Starting time before asteroids spawning
  export const respawnTime: number = 180;
  // Current time before asteroids spawning
  export let spawnTimer: number = 0;
}
Game.ts
class GameBehavior extends Sup.Behavior {
  timer: number;

  start() {    
    // When GameBehavior awake, if game is Asteroids then spawn asteroids and alien
    if(Game.nameIndex === 0){
      // Give to this game instance, the starting timer
      this.timer = Game.timeMax;
      // And update the HUD timer
      Game.updateTimer(this.timer);

      // Spawn an Alien ship
      Game.spawnAlien();

      // Spawn as much asteroids than we have set as a Starting number
      for(let i = 0; i < Asteroids.startCount; i++){
        // Spawn an asteroid
        Game.spawnAsteroid();
      }
    }
  }

  update() {
    // If the game is Asteroids then spawn alien and asteroids
    if(Game.nameIndex === 0){
      //If alien destroyed, spawn a new alien after a certain time
      if(!Alien.alive){
        // If spawnTimer not finished, decrease by one
        if(Alien.spawnTimer > 0){
          Alien.spawnTimer--;
        // If spawnTimer finished, spawn an alien ship
        }
        else {
          Game.spawnAlien();
        }
      }

      // Spawn a new asteroid after a certain time until the overall number reach maximum
      // If asteroid current count is less than the max number
      if (Asteroids.currentCount < Asteroids.maxCount) {
        // If spawnTimer is not finished, decrease by one
        if (Asteroids.spawnTimer > 0) {
          Asteroids.spawnTimer--;
        }
        // If spawnTimer is finished, create a big asteroid
        else {
          Game.spawnAsteroid();
          // Reset timer for next asteroid
          Asteroids.spawnTimer = Asteroids.respawnTime; 
        }
      }

      // Timer and game over screen decrease to each frame
      if (this.timer > 0) {
        this.timer--
        // If 60 frames passed (which mean one second when converted), update
        if(this.timer % 60 === 0) {
          Game.updateTimer(this.timer);
        }
      // If timer at 0, then the game is finished.
      }
      else {
        // The game is over, return ship1 score
        Game.gameOver("ship1");
      }
    }

    // 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);
      }
    }

    // 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);
      }
    }

    // Restart game when key (R) is pressed, call Game.start function which reload the game scene
    if (Sup.Input.wasKeyJustPressed("R")) {
      Game.start();
    }

    // Leave Game when key (ESCAPE) is pressed, load the menu scene
    if (Sup.Input.wasKeyJustPressed("ESCAPE")) {
      Sup.loadScene("Menu/Scene");
    }
  }
}
Sup.registerBehavior(GameBehavior);
Alien.ts
class AlienBehavior extends Sup.Behavior {
  // Ship size of this Actor
  size: number;
  // Ship amplitude of this Actor
  amplitude: number;
  // Ship position
  position: Sup.Math.Vector2;
  // Ship linear movement
  velocity : Sup.Math.Vector2;
  // Timer before shooting
  shootCooldown: number;
  // Timer before death
  deathTimer: number;

  awake() {
    // Set Alien.alive flag to true
    Alien.alive = true;
    // Get a random index for alien ship size and amplitude
    let randomShip: number = Sup.Math.Random.integer(0, 2);
    // Get the default size related to the random index
    this.size = Alien.sizes[randomShip];
    // Get the default collision amplitude related to the random index
    this.amplitude = Alien.amplitudes[randomShip];
    // Set the size to the actor
    this.actor.setLocalScale(this.size);
    // Set the value of Alien.startLife to the current Alien.lifes
    Alien.lifes = Alien.startLife;
    // Set the shootCooldown timer to the Alien.shootTime value
    this.shootCooldown = Alien.shootTime;
    // Set the Game.checkLifeHUD value to true 
    Game.checkLifeHUD = true;
  }

  start() {
    // Set position to the current actor position
    this.position = this.actor.getLocalPosition().toVector2();
    // Set x axis linear movement toward the opposite edge of spawn
    if (this.position.x === Game.bounds.width / 2) {
      this.velocity = new Sup.Math.Vector2(-Alien.linearSpeed, 0);
    }
    else {
      this.velocity = new Sup.Math.Vector2(Alien.linearSpeed, 0);
    }
  }

  shoot() {
    // Create a new alien missile and set the Missile actor to a variable
    let missile = Sup.appendScene("Alien/Missile/Prefab")[0];
    // Set the alien ship position to the missile actor position
    missile.setLocalPosition(this.position);
    // Reset value of Timer to default value
    this.shootCooldown = Alien.shootTime;
  }

  update() {
    // Death timer
    // Decrease death timer before destruction
    if(this.deathTimer > 0){
      this.deathTimer--;
      if(this.deathTimer === 0){
        this.actor.destroy();
      }
      return;
    }

    // Death setting
    // Set death if alien don't have lifes anymore
    if(Alien.lifes === 0){
      Sup.Audio.playSound('Sounds/explosion');
      // Reset angles to default for the explosion sprite
      this.actor.setEulerAngles(0,0,0);
      // Set visible off the alien ship model
      this.actor.getChild("Model").setVisible(false);
      // Set the sprite animation explode to play once without looping
      this.actor.getChild('Destruction').spriteRenderer.setAnimation("explode", false);
      // Give 30 frames before actor destruction (half a second)
      this.deathTimer = 30;
      // Add point to the player for alien death
      Game.addPoints(true, Game.points.alien);
      // Flag the game to check and update the HUD score
      Game.checkScoreHUD = true;
    }

    // Keep moving
    // Add the linear movement to the position variable
    this.position.add(this.velocity);
    // Set the new position to the alien actor
    this.actor.setLocalPosition(this.position);

    // Keep rotating
    // Rotate actor using rotateLocalEulerY with the default Alien.rotationSpeed value
    this.actor.rotateLocalEulerY(Alien.rotationSpeed);

    // Keep shooting
    // If the timer is not finished, decrease value of one
    if (this.shootCooldown > 0) {
      this.shootCooldown--;
    }
    // If the timer is finished, shoot missile
    else {
      Sup.Audio.playSound('Sounds/alienShot');
      this.shoot();
    }

    // Stay on the screen
    // If position is superior to the max of the x bound then switch position
    if (this.position.x > Game.bounds.width / 2) {
      this.position.x = -Game.bounds.width / 2;
    }
    // If position is inferior to the min of the x bound then switch position
    if (this.position.x < -Game.bounds.width / 2) {
      this.position.x = Game.bounds.width / 2;
    }

    // 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)) {
        Sup.Audio.playSound('Sounds/shotContact');
        // Decrease Alien lifes by one
        Alien.lifes--
        // Flag true to check and update the life HUD in Game Script
        Game.checkLifeHUD = true;
      }
    }
    // 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;
    }
  }

  onDestroy() {
    // Set Alien.alive flag to false
    Alien.alive = false;
    // Give to Alien.spawnTimer the value of Alien.respawnTime
    Alien.spawnTimer = Alien.respawnTime;
  }
}
Sup.registerBehavior(AlienBehavior);
Missile.ts (Alien)
class AlienMissileBehavior extends Sup.Behavior {
  // Current position
  position: Sup.Math.Vector2;
  // Current velocity
  velocity: Sup.Math.Vector2;
  // Target position
  target: Sup.Math.Vector2;
  // Missile trajectory angle
  angle: number;

  start() {
    // Set current position from the actor position
    this.position = this.actor.getLocalPosition().toVector2();
    // Get player ship position to define as target for this missile
    this.target = Sup.getActor("Ship1").getLocalPosition().toVector2();
    // Get angle trajectory between this actor position and the target position
    this.angle = this.position.angleTo(this.target);
    // Create velocity with the Alien.missileSpeed value
    this.velocity = new Sup.Math.Vector2(Alien.missileSpeed, 0);
    // Convert velocity with the angle trajectory
    this.velocity.rotate(this.angle);
    // Add the current actor the Alien.missiles list
    Alien.missiles.push(this.actor);
  }

  update() {
    // While the missile has no reached the target position, keep moving
    // Add current velocity to current position
    this.position.add(this.velocity);
    // Update current position to missile actor
    this.actor.setLocalPosition(this.position);
    // Get the distance between current position and target position
    let distance = this.target.distanceTo(this.position);
    // When the distance to target is nearly reached, the missile explode
    if (distance < 0.5) {
      this.actor.getChild("Sprite").spriteRenderer.setAnimation("explode", false);
      // When the distance is close to 0, the missile actor is destroyed
      if (distance < 0.1) {
        this.actor.destroy();
      }
    }
  }

  onDestroy() {
    // Remove this actor from the Alien.missiles list
    Alien.missiles.splice(Alien.missiles.indexOf(this.actor), 1);
  }
}
Sup.registerBehavior(AlienMissileBehavior);

##### Asteroid.ts

class AsteroidBehavior extends Sup.Behavior {
  // The size of the asteroid
  size: number;
  // The amplitude of this asteroid Actor
  amplitude: number;
  // The class of size this asteroid belong
  sizeClass: string;
  // The position of the asteroid
  position: Sup.Math.Vector3;
  // The movement of the asteroid
  velocity: Sup.Math.Vector3;
  // The movement of the asteroid on x axis
  linearSpeedX: number;
  // The movement of the asteroid on y axis
  linearSpeedY: number;
  // The rotation speed of the asteroid
  rotationSpeed: number;
  // Timer before destruction
  deathTimer: number;

   awake() {
    // Increase asteroids count by one
    Asteroids.currentCount++;
    // Add this actor to the Asteroids list
    Asteroids.list.push(this.actor);
  }

  start() {
    // Get the position of the actor set randomly when spawned
    this.position = this.actor.getLocalPosition();
    // Get random value for linear speed on axis X and axis Y
    this.linearSpeedX = Sup.Math.Random.float(Asteroids.linearSpeed.min, Asteroids.linearSpeed.max);
    this.linearSpeedY = Sup.Math.Random.float(Asteroids.linearSpeed.min, Asteroids.linearSpeed.max);
    // Set asteroid velocity with the linearSpeed values
    this.velocity = new Sup.Math.Vector3(this.linearSpeedX, this.linearSpeedY, 0);
    // Get random value for linear speed on axis X and axis Y
    this.rotationSpeed = Sup.Math.Random.float(Asteroids.rotationSpeed.min, Asteroids.rotationSpeed.max);
    // Set the asteroid size related to the classSize of the asteroid
    this.size = Asteroids.sizes[this.sizeClass];
    // Set the asteroid collision amplitude related to the classSize of the asteroid
    this.amplitude = Asteroids.amplitudes[this.sizeClass];
    // Set the size to the actor model
    this.actor.getChild("Model").setLocalScale(this.size);
    // Set the size to the destruction sprite
    this.actor.getChild("Destruction").setLocalScale(this.size * 2);
  }

  die() {
    Sup.Audio.playSound('Sounds/explosion');
    // Reset angles to default for the explosion sprite
    this.actor.setEulerAngles(0,0,0);
    // Set visible off the asteroid model
    this.actor.getChild("Model").setVisible(false);
    // Set the sprite animation explode to play once without looping
    this.actor.getChild('Destruction').spriteRenderer.setAnimation("explode", false);
    // Give 30 frames before actor destruction (half a second)
    this.deathTimer = 30;
    // We give the points to ship1 related to the classSize of this asteroid
    if(this.sizeClass === "big") {
        Game.addPoints(true, Game.points.asteroidBig);
      }
      else if (this.sizeClass === "medium") {
        Game.addPoints(true, Game.points.asteroidMedium);
      }
      else {
        Game.addPoints(true, Game.points.asteroidSmall);
      }
    // Flag the game to check and update the HUD score
    Game.checkScoreHUD = true;
  }

  spawnChildren() {
    // Create two now asteroid and set the Actor asteroid to each variable
    let asteroid1 = Sup.appendScene("Asteroid/Prefab")[0];
    let asteroid2 = Sup.appendScene("Asteroid/Prefab")[0];
    // Set the position of the new asteroids to the same position than the asteroid parent (this one)
    asteroid1.setPosition(this.position);
    asteroid2.setPosition(this.position);
    // If the sizeClass was big, then both asteroid are medium
    if (this.sizeClass === "big") {
      asteroid1.getBehavior(AsteroidBehavior).sizeClass = "medium";
      asteroid2.getBehavior(AsteroidBehavior).sizeClass = "medium";

    }
    // If the sizeClass was medium, then both asteroid are small
    if (this.sizeClass === "medium") {
      asteroid1.getBehavior(AsteroidBehavior).sizeClass = "small";
      asteroid2.getBehavior(AsteroidBehavior).sizeClass = "small";
    }
  }

  update() {
    // Death timer
    if(this.deathTimer > 0){
      this.deathTimer--;
        if(this.deathTimer === 0){
          this.actor.destroy();
        }
      return;
      }

    // Keep moving
    this.position.add(this.velocity);
    this.actor.setLocalPosition(this.position);

    // Keep rotating
    this.actor.rotateEulerAngles(this.rotationSpeed, this.rotationSpeed, this.rotationSpeed);

    // Stay on the screen
    // Check position of asteroid on x axis
    if (this.position.x > Game.bounds.width / 2) {
      this.position.x = -Game.bounds.width / 2
    }

    if (this.position.x < -Game.bounds.width / 2) {
      this.position.x = Game.bounds.width / 2
    }
    // Check position of asteroid on y axis
    if (this.position.y > Game.bounds.height / 2) {
      this.position.y = -Game.bounds.height / 2
    } 

    if (this.position.y < -Game.bounds.height / 2) {
      this.position.y = Game.bounds.height / 2
    }

    // 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();
        }
      }
    }
  }

  onDestroy() {
    // Decrease asteroids count by one
    Asteroids.currentCount--;
    // Remove this actor from the Asteroids list
    Asteroids.list.splice(Asteroids.list.indexOf(this.actor), 1);
  }
}
Sup.registerBehavior(AsteroidBehavior);
Ship.ts
class ShipBehavior extends Sup.Behavior {
  // Ship index, 0 is ship 1, 1 is ship 2
  index: number;
  // Ship radius collision
  amplitude: number;
  // Ship current life
  lifes: number;
  // Ship current status
  alive: boolean;
  // Ship current score
  score: number;
  // Spawn position
  spawnPosition: Sup.Math.Vector2;
  // Current position
  position: Sup.Math.Vector2;
  // Current movement speed
  linearVelocity = new Sup.Math.Vector2();
  // Current rotation speed
  angularVelocity: number;
  // Angle position
  angle: number;
  // Timer before shooting
  shootCooldown: number;
  // Timer before respawn
  spawnCooldown: number;
  // Timer before vulnerability
  invincibilityCooldown: number;

  awake() {
    // Starting life to 3
    this.lifes = Ships.startLife;
    // Set true to alive status
    this.alive = true;
    // Starting score to 0
    this.score = Ships.startScore;
    // Starting speed movement on x and y axis to 0
    this.linearVelocity.set(0, 0);
    // Starting speed rotation to 0
    this.angularVelocity = 0;
  }

  start() {
    // Set the ship default size to half size
    this.actor.setLocalScale(Ships.size);
    // Set the ship default amplitude related to size
    this.amplitude = Ships.amplitude;
    // Get the starting position to become the spawnPosition of this behavior
    this.spawnPosition = this.actor.getLocalPosition().toVector2();
    // Get the starting position to become the current position of this behavior
    this.position = this.actor.getLocalPosition().toVector2();
    // Get the starting angle to become the current angle of this behavior
    this.angle = this.actor.getLocalEulerZ();
  }

  die() {
    Sup.Audio.playSound('Sounds/explosion');
    // Decrease life of one
    this.lifes--;
    // Set false to alive status
    this.alive = false;
    // Flag to check and update the life HUD
    Game.checkLifeHUD = true;
    // If life is 0, then the game is over
    if (this.lifes === 0) {
      // If this is the Asteroids game, death of ship mean victory for Alien
      if(Game.nameIndex === 0) {
        Sup.setTimeout(1000, function () { Game.gameOver("alien") });
      }
      else {
        // If this is Spacewar game and death of ship 1 mean victory for ship 2
        if (this.index === 0){
          Sup.setTimeout(1000, function () { Game.gameOver("ship2") });
        }
        // Else, this is ship 2 and it is a victory for ship 1
        else {
          Sup.setTimeout(1000, function () { Game.gameOver("ship1") });
        }
      }
    }
    // Check which ship index to see which lose points
    if (this.index === 0) {
      Game.addPoints(true, Game.points.death);
    }
    else {
      Game.addPoints(false, Game.points.death); 
    }
    // Flag to check and update the score HUD
    Game.checkScoreHUD = true;
    // Set timer before respawn
    this.spawnCooldown = Ships.respawnTimer;    
    // Set ship model visibility to false
    this.actor.getChild('Model').setVisible(false);
    // Set ship boosts visibility to false
    this.actor.getChild('Boost').setVisible(false);
    // Set sprite animation explosition to play once
    this.actor.getChild('Destruction').spriteRenderer.setAnimation("explode", false);
    // Reset speed movement on x and y axis to 0
    this.linearVelocity.set(0, 0);
    // Reset angular movement to 0
    this.angularVelocity = 0;
    // Reset angle to default for ship 1 or ship 2
    if (this.index === 0){
      this.angle = 1.6;
    }
    else{
      this.angle = -1.6;
    }
  }

  spawn() {
    // The ship respawn to spawn position
    this.position = this.spawnPosition.clone();
    // Set the new current position to the Ship actor
    this.actor.setLocalPosition(this.position);
    // Set the new angle to the Ship actor
    this.actor.setLocalEulerZ(this.angle);
    // The ship model visibility to true
    this.actor.getChild('Model').setVisible(true);
    // Set timer for invincibility
    this.invincibilityCooldown = Ships.invincibleTimer;
  }

  shoot() {
    // Initialize a new missile
    let missile: Sup.Actor;
    // If the ship is ship 1 then create a Ship 1 missile and set the variable missile to the Missile actor
    if (this.index === 0) {
      missile = Sup.appendScene("Ship/0/Missile/Prefab")[0];
    } 
    // Else do the same but for the missile of ship 2
    else {
      missile = Sup.appendScene("Ship/1/Missile/Prefab")[0];
    }
    // Set position of the actor to the current position of the ship
    missile.setLocalPosition(this.position);
    // Set local variables of the missile behavior
    // Report the position of the ship to the variable position of the behavior
    missile.getBehavior(ShipMissileBehavior).position = this.position.clone();
    // Report the angle of the ship to the angle direction of the missile
    missile.getBehavior(ShipMissileBehavior).angle = this.angle;
    // Set the shipIndex related to this missile
    missile.getBehavior(ShipMissileBehavior).shipIndex = this.index;
    // Set Shooting timer to be able to shoot again
    this.shootCooldown = Ships.shootingTimer;
  }

  boost(intensity: string) {
    // Create a new variable boost that get the Boost actor child of Ship actor
    let boost:Sup.Actor = this.actor.getChild("Boost");
    // Set the boost actor visible true
    boost.setVisible(true);
    // Set animation to both sprite with the intensity normal or fast
    boost.getChild("0").spriteRenderer.setAnimation(intensity);
    boost.getChild("1").spriteRenderer.setAnimation(intensity);
  }

  rotateBoost(direction: string) {
    // Create a new variable boost that get the Boost actor child of Ship actor
    let boost:Sup.Actor = this.actor.getChild("Boost");
    // Set the boost actor visible true
    boost.setVisible(true);
    // If rotate on the left direction
    if(direction === "left"){
      // Switch animation for right boost stronger
      boost.getChild("0").spriteRenderer.setAnimation("fast");
      boost.getChild("1").spriteRenderer.setAnimation("normal");
    }
    // If rotate on the right direction
    if(direction === "right"){
      // Switch animation for left boost stronger
      boost.getChild("1").spriteRenderer.setAnimation("fast");
      boost.getChild("0").spriteRenderer.setAnimation("normal");
    }
  }

  update() {
    // Keep respawning
    // If the spawnCooldown timer is more than 0
    if (this.spawnCooldown > 0) {
      // Decrease by one the timer
      this.spawnCooldown--
      // If the spawnCooldown is 0
      if (this.spawnCooldown === 0) {
        // Call the spawn method
        this.spawn();
      }
      //restart update loop to skip the following code
      return;
    }

    // Keep shooting
    // If the shootCooldown timer is more than 0
    if (this.shootCooldown > 0) {
      // Decrease by one the timer
      this.shootCooldown--;
    }
    // If the timer is 0 (!0 = true)
    if (!this.shootCooldown) {
      // If the shoot key is pressed
      if(Sup.Input.wasKeyJustPressed(Ships.commands[this.index].shoot)){
        Sup.Audio.playSound('Sounds/shipShot');
        // Call the shoot method
        this.shoot();
      }
    }

    // Keep moving
    // If forward key is pressed down
    if (Sup.Input.isKeyDown(Ships.commands[this.index].forward)){
      // Set the impulse with the linearAcceleration
      let impulse = new Sup.Math.Vector2(Ships.linearAcceleration, 0);
      // Convert the impulse to the current angle
      impulse.rotate(this.angle);
      // Add the impulse to the linearVelocity
      this.linearVelocity.add(impulse);
        // Call the boost method with a normal intensity
        this.boost("normal");
        // If the boost key is pressed down
        if (Sup.Input.isKeyDown(Ships.commands[this.index].boost)) {
          // Add a second time the impulse to the linearVelocity
          this.linearVelocity.add(impulse);
        // Call the boost method with a fast intensity
          this.boost("fast");
        }
    }
    else {
      // Set visible false booster if not going forward
      this.actor.getChild("Boost").setVisible(false);
    }

    // Keep rotating
    // If left key is pressed down
    if (Sup.Input.isKeyDown(Ships.commands[this.index].left)){
      // The angularVelocity get the angularAcceleration
      this.angularVelocity += Ships.angularAcceleration;
      // Boost sprite for left side
      this.rotateBoost("left");
    }

    // If right key is pressed down
    if (Sup.Input.isKeyDown(Ships.commands[this.index].right)){
      // The angularVelocity get the opposite angularAcceleration
      this.angularVelocity -= Ships.angularAcceleration;
      // Boost sprite for left side
      this.rotateBoost("right");
    }

    // Set boost to default if key left, right and forward are NOT pressed
    if (!Sup.Input.isKeyDown(Ships.commands[this.index].left) && 
        !Sup.Input.isKeyDown(Ships.commands[this.index].right) && 
        !Sup.Input.isKeyDown(Ships.commands[this.index].forward)){
          this.actor.getChild("Boost").setVisible(false);
          this.actor.getChild("Boost").getChild("0").setVisible(true);
          this.actor.getChild("Boost").getChild("1").setVisible(true);
        }

    // Keep slowing down
    // The linearVelocity multiply the linearDamping
    this.linearVelocity.multiplyScalar(Ships.linearDamping);
    // The angularVelocity multiply the angularDamping
    this.angularVelocity *= Ships.angularDamping;

    // Stay on the game screen
    if (this.position.x > Game.bounds.width / 2) {
      this.position.x = -Game.bounds.width / 2;
    }

    if (this.position.x < -Game.bounds.width / 2) {
      this.position.x = Game.bounds.width / 2;
    }

    if (this.position.y > Game.bounds.height / 2) {
      this.position.y = -Game.bounds.height / 2;
    }

    if (this.position.y < -Game.bounds.height / 2) {
      this.position.y = Game.bounds.height / 2;
    }

    // Update position and angle
    // Add the linearVelocity to the current position
    this.position.add(this.linearVelocity);
    // Set the new current position to the Ship actor
    this.actor.setLocalPosition(this.position);
    // Add the angularVelocity to the current angle
    this.angle += this.angularVelocity;
    // Set the new angle to the Ship actor
    this.actor.setLocalEulerZ(this.angle);

    // Blinking
    // If the invincibilityCooldown Timer is more than 0
    if (this.invincibilityCooldown > 0) {
      // Decrease by one the timer
      this.invincibilityCooldown--;
      // Set actor visible become true every half second, false the other half and stay visible at the end
      this.actor.setVisible(this.invincibilityCooldown % 60 < 30);
      // When invincibilityCooldown reach 1, get back vulnerability
      if (this.invincibilityCooldown === 1) {
        // Set true to alive status
        this.alive = true;  
      }
      // Restart update loop to skip the collision blocks code
      return;
    }

    // If game is Super Asteroids, chek for collision with asteroids, alien missiles and alien ship
    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();
      }
      // Check collision between the player ship and the asteroids
      if (Game.checkCollisionAsteroids(this.actor, this.amplitude)){
        this.die();
      }
    }

    // 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();
        }
      }
      // Check collision between this ship and the other ship
      if (Game.checkCollisionShip(this.actor, this.amplitude)) {
            this.die();
      }
    }
  }
}
Sup.registerBehavior(ShipBehavior);
Missile.ts (Ship)
class ShipMissileBehavior extends Sup.Behavior {
  // shipIndex owner of this missile actor
  shipIndex: number;
  // Current position
  position: Sup.Math.Vector2;
  // Current movement velocity
  velocity: Sup.Math.Vector2;
  // Missile trajectory angle
  angle: number;
  // Timer before death
  lifeTime: number;

  start() {
    // Set the timer lifeTime to the default value Ships.missileLife
    this.lifeTime = Ships.missileLife;
    // Create a new vector with the default Ships.missileSpeed as value for the velocity
    this.velocity = new Sup.Math.Vector2(Ships.missileSpeed, 0);
    // Convert the velocity with the trajectory angle
    this.velocity.rotate(this.angle);
    // Add the current missile actor to the global Ships.missiles[shipIndex] list (0 for ship1 and 1 for ship2)
    Ships.missiles[this.shipIndex].push(this.actor);
  }

  update() {
    // Keep moving
    this.position.add(this.velocity);
    this.actor.setLocalPosition(this.position);

    // If the timer is superior to 0, decrease by one
    if (this.lifeTime > 0) {
      this.lifeTime--;
      // If the timer reach 10 frame before death, play the destruction animation once
      if (this.lifeTime === 10) {
        this.actor.getChild("Sprite").spriteRenderer.setAnimation("explode", false);
      }
    } 
    // Once the lifeTime timer reach 0, destroy the Actor
    else {
      this.actor.destroy();
    }
  }

  onDestroy() {
    // Remove the current actor from the Global list from the shipIndex owner
    Ships.missiles[this.shipIndex].splice(Ships.missiles[this.shipIndex].indexOf(this.actor), 1);
  }
}
Sup.registerBehavior(ShipMissileBehavior);
class MenuBehavior extends Sup.Behavior {
  // The current menu screen
  screen: string;
  // Ray casting
  ray = new Sup.Math.Ray();
  // First button
  button0: Sup.Actor;
  // Second button
  button1: Sup.Actor;

  awake() {
    // Set the menu with the Main screen
    this.setScreen(Menu.screens.main);
    // Update the buttons
    this.updateButtonsText();
  }

  setScreen(screenName: string){
    // Set the current screen to the screen string Name
    this.screen = screenName;
    // Loop through all the screen of the scene and set visible the current screen
    for(let screen in Menu.screens){
      // If the screen of the loop is the same as the current screen
      if (Menu.screens[screen] == this.screen){
        // Set visible true the screen
        Sup.getActor("Screens").getChild(Menu.screens[screen]).setVisible(true);
      }
      // Else, hide the screen, set visible false
      else {
        Sup.getActor("Screens").getChild(Menu.screens[screen]).setVisible(false);
      }
    }
    // call the function updateTitle
    this.updateTitle();
  }

  updateTitle(){
    // Set title visible true
    Sup.getActor("Title").setVisible(true);
    // Set score visible false
    Sup.getActor("Score").setVisible(false);
    // Change the title depending if the game is asteroids or spacewar
    if(this.screen === Menu.screens.asteroids|| this.screen === Menu.screens.spacewar){
      Sup.getActor("Title").getChild("Text2").textRenderer.setText(this.screen);
    }
    // Else, it change the title with an empty string
    else{
      Sup.getActor("Title").getChild("Text2").textRenderer.setText("");
    }
  }

  updateButtonsText() {
    // Get the two buttons actors in one variable each
    this.button0 = Sup.getActor("Buttons").getChild("Button1");
    this.button1 = Sup.getActor("Buttons").getChild("Button2");
    // If it is the Main screen
    if(this.screen === Menu.screens.main){
      // Set the button to display the game name
      this.button0.getChild("Text").textRenderer.setText("asteroids");
      this.button1.getChild("Text").textRenderer.setText("spacewar");
    }
    // For the other screens
    else {
      // Set the button to display start and return
      this.button0.getChild("Text").textRenderer.setText("start");
      this.button1.getChild("Text").textRenderer.setText("return");
    }
  }

  update() {
    // Update the position of the raycaster of the mouse
    this.ray.setFromCamera(Sup.getActor("Camera").camera, Sup.Input.getMousePosition());
    // Return an object if the raycaster intersect the actor buttons
    let hitB1 = this.ray.intersectActor(this.button0.getChild("Sprite"));
    let hitB2 = this.ray.intersectActor(this.button1.getChild("Sprite"));

    // If the button 1 is hovered
    if(hitB1[0]){
      // Change the opacity of the button to full bright
      this.button0.getChild("Sprite").spriteRenderer.setOpacity(1);
      // If the left mouse button is pressed
      if (Sup.Input.wasMouseButtonJustPressed(0)) {
        Sup.Audio.playSound("Sounds/shipShot");
        // Differents case possible depending the current screen
        switch (this.screen){
          // If the current screen is the Main screen
          case Menu.screens.main:
            // Set the new screen to Asteroids screen
            this.setScreen(Menu.screens.asteroids);
            break;
          // If the current screen is the Asteroid screen
          case Menu.screens.asteroids:
            // Set the nameIndex to 0
            Game.nameIndex = Menu.names.Asteroids;
            // Start the game
            Game.start();
            return;
          // If the current screen is the Spacewar screen
          case Menu.screens.spacewar:
            // Set the nameIndex to 1
            Game.nameIndex = Menu.names.Spacewar;
            // Start the game
            Game.start();
            return;
          // If the current screen is the Game Over screen
          case Menu.screens.gameover:
            // Restart the game
            Game.start();
            return;
        }
      }
    }
    // If the button is not hovered, set opacity half bright
    else{
      this.button0.getChild("Sprite").spriteRenderer.setOpacity(0.5); 
    }

    // If the button 2 is hovered
    if(hitB2[0]){
      // Change the opacity of the button to full bright
      this.button1.getChild("Sprite").spriteRenderer.setOpacity(1);
      // If the left mouse button is pressed
      if (Sup.Input.wasMouseButtonJustPressed(0)) {
        Sup.Audio.playSound("Sounds/shipShot");
        // Loop through different case
        switch (this.screen){
          // If the current screen is the Main screen
          case Menu.screens.main:
            // Set the screen of the Spacewar game screen
            this.setScreen(Menu.screens.spacewar);
            break;
            // Else, in other case, return to main screen
          default:
            this.setScreen(Menu.screens.main);
        }
      }
    }
    // If the button is not hovered, set opacity half bright
    else{
      this.button1.getChild("Sprite").spriteRenderer.setOpacity(0.5);
    }
    // Update the buttons
    this.updateButtonsText();
  }
}
Sup.registerBehavior(MenuBehavior);
Background.ts
class BackgroundBehavior extends Sup.Behavior {
  // Initialize position and speed variables
  position: number;
  speed : number;

  awake() {
    // Get the position on Y axis of the actor
    this.position = this.actor.getLocalY();
    // Set the speed of background scrolling
    this.speed = 0.01;
  }

  update() {
    // Add the speed movement to the current position
    this.position += this.speed;
    // Set the current position to the actor
    this.actor.setLocalY(this.position);

    // If the sprite moved all its height (480/16 = 60 units)
    if (this.position > 60) {
      // Then return down to start again
      this.position = -60;
    }
  }
}
Sup.registerBehavior(BackgroundBehavior);

<-- back to chapter 11