Midterm Project | Space Adventure | Aadil

Concept

I had initially intended to make an automated generative kaleidoscope but there were lots of issues with it and the sketch would lag a lot because of the large amount of random numbers generated. So, I decided to make a fun 2-Player game .

I used to play this game on my phone called 2 Player Games that had a variety of games in it that could be played by 2 players. One of them was Star Catcher, which is what inspired me to make this project.

Star Catcher is a game where you have two spaceships each controlled by a player and stars randomly appear on the screen . The player who collects a required number of stars first wins the game . There are asteroids that the spaceships can collide with and also collision between the spaceships of the players. I could not find an online version of this game for PC so I thought that it would be great if I could build something like this .

I had do something’s differently for the keyboard control and Fullscreen options as compared to the mobile version which just relies on touch . I came up with the following rules and specifications for the game :

  1. There has to be a start scene ,  a game scene and a final winning scene
  2. The game starts after spacebar is pressed from the start scene, the first spaceship is controlled by WASD and the second spaceship is controlled by arrow keys. The movement must be smooth and there must be acceleration and deceleration rather than just velocity. The spaceships must rotate and be capable of moving in all directions.
  3. The goal is to collect 10 stars before the opponent.
  4. There are asteroids generated randomly that can collide with the player spaceship to make them bounce
  5. There is sound interaction for collisions, start screen, star collection and winning scene.

Sketch

Please run the sketch in p5 (it does not enter into Fullscreen mode properly in WordPress) . The Project contains 3 classes in addition to the sketch.js files . If using a browser that has esc as default key for exiting Fullscreen , make sure to use ‘e’ instead of esc to exit fullscreen.

Link to the sketch –p5.js Web Editor | IMMidtermProjectSpaceDodge (p5js.org)

How the Project works/ some Implementation details

The Project has 3 classes :

  1. Player Class:
    • Represents the players in the game.
    • Handles player movement based on keyboard input.
    • Updates player position, velocity, and angle.
    • Detects collisions with other players and asteroids.
  2. Asteroid Class:
    • Represents the asteroids in the game.
    • Updates asteroid position and rotation.
    • Displays asteroids on the canvas.
  3. Star Class:
    • Represents the stars that players can collect for points.
    • Updates star position and detects collisions with players.
    • Displays stars on the canvas.

The movement is smoothened out by adding acceleration and friction which are two variables defined in the beginning of the code .

I also have the following helper functions :

Helper Functions:

  • Functions for handling asteroid generation, resetting the game state, toggling Fullscreen mode, and resetting sound flags.
      1. handleAsteroidGeneration(): Generates a new set of asteroids based on the current game mode (Fullscreen or windowed) with random positions, speeds, sizes, and rotation speeds.
      2. handlePlayerAsteroidCollision(player): Checks for collisions between a player and asteroids and adjusts player velocity accordingly upon collision.
      3. handleCollision(): Detects and handles collisions between players in the game.
      4. resetGame(): Resets game variables and objects to their initial state for starting a new game.
      5. toggleFullscreen(): Toggles the game between Fullscreen and windowed mode.
      6. exitFullscreen(): Exits Fullscreen mode and adjusts game elements accordingly.
      7. resetSoundFlags(): Resets flags related to sound effects to their initial state.
      8. drawStartMenu(): Draws the start menu interface on the start canvas.
      9. drawEndScreen():
        • Draws the end screen interface when the game is over.

and an event handler:

  • key Pressed: Handles key presses for starting the game, toggling Fullscreen, and exiting Fullscreen.
Assets Used

Assets used for making the game are :

Images:

  1. Asteroid image: ‘Images/asteroid.png’
  2. Star image: ‘Images/star2.png’
  3. Background image: ‘Images/Space_background.jpg’

Audio:

(All audio used was found from Pixabay and trimmed to fit the game sound needed)

  1. Start menu background music: ‘AudioFiles/start_menu.mp3’
  2. Winner music: ‘AudioFiles/win_sound.mp3’
  3. Collision spacecraft sound: ‘AudioFiles/bump.mp3’
  4. Collect star sound: ‘AudioFiles/star_collect.mp3’

Font:

    1. Font file: ‘Roboto-Regular.ttf’

Key Challenges Faced 

  1. The movement implementation was quite challenging , I had to make sure to implement acceleration , deceleration and collision and make sure it looks realistic . I experimented with values to determine what looks good for the canvas size and full screen.
  2. Sounds- I faced difficulty implementing audio and spent a lot of time making sure that audio did not overlap. At the end, I decided to use Boolean flags to check whether an audio is being played or not.
  3. Switch to Fullscreen- I had some issues switching back to full screen, of course when going in full screen you need to change the speed of the spaceships and the number of asteroids to make the game fun.

 

Code that I am Proud Of

Implementing the movement mechanism was a bit difficult , as it had to be smooth and the spaceship had to change direction , this is implemented in the player class as follows:

  class Player{
//..........
update() {
    // Apply friction to the acceleration
    this.acceleration.mult(1 - friction);

    // Apply friction to the velocity
    this.velocity.mult(1 - friction);

    // Update velocity based on acceleration
    this.velocity.add(this.acceleration);

    // Limit the velocity to the maximum speed
    this.velocity.limit(maxSpeed);

    // Check if maximum speed is reached
    this.maxSpeedReached = this.velocity.mag() >= maxSpeed;

    // Update position based on velocity
    this.x += this.velocity.x;
    this.y += this.velocity.y;

    // Wrap around the canvas boundaries
    if (this.x < 0) {
      this.x = width;
    } else if (this.x > width) {
      this.x = 0;
    }
    if (this.y < 0) {
      this.y = height;
    } else if (this.y > height) {
      this.y = 0;
    }

    // Calculate angle
    if (this.velocity.x !== 0 || this.velocity.y !== 0) {
      this.angle = PI / 2 - atan2(-this.velocity.y, this.velocity.x);
    }
/* ........*/
}

This code brings acceleration and friction to the player movement, the calculate angle part changes the orientation of the spaceship with respect to its velocity vectors.

The collision mechanism was implemented in the two functions below :

 function handleCollision() {
  // Check for collision between players
   // Check for collision between players
    if (player1.collides(player2) && !player1Collision && !player2Collision){
        // Set collision state for both players
        player1Collision = true;
        player2Collision = true;

        // Record the time of collision
        lastCollisionTimePlayer1 = millis();
        lastCollisionTimePlayer2 = millis();}
  if (player1.collides(player2)) {
    // Calculate direction vector between the centers of the players
    let collisionVector = createVector(player2.x - player1.x, player2.y - player1.y).normalize(); //gives unit vector in direction of centeres and stores it in collision vector

    // Calculate the magnitude of the velocity components along the collision vector
    let velocity1AlongCollision = player1.velocity.dot(collisionVector);
    let velocity2AlongCollision = player2.velocity.dot(collisionVector);
    
    // Calculate the new velocities after the collision ,swaps the velocities
    let newVelocity1AlongCollision = velocity2AlongCollision;
    let newVelocity2AlongCollision = velocity1AlongCollision;


    // Update velocities with the new components
    let newVelocity1 = p5.Vector.add(player1.velocity, p5.Vector.mult(collisionVector, newVelocity1AlongCollision - velocity1AlongCollision));
    let newVelocity2 = p5.Vector.add(player2.velocity, p5.Vector.mult(collisionVector, newVelocity2AlongCollision - velocity2AlongCollision));

    player1.velocity.set(newVelocity1.x, newVelocity1.y);
    player2.velocity.set(newVelocity2.x, newVelocity2.y);

    // Play collision sound when spacecraft collides with another spacecraft
    if (!collisionSpacecraftSoundIsPlaying) {
      collisionSpacecraftSound.play();
      collisionSpacecraftSoundIsPlaying = true;
    }
  } else {
    // Reset collision sound flag when no collision occurs
    collisionSpacecraftSoundIsPlaying = false;
  }
}




function handlePlayerAsteroidCollision(player) {
  // Determine which player is being checked
  let isPlayer1 = player === player1;

  // Check if the player is in a collision state
  let playerCollision = isPlayer1 ? player1Collision : player2Collision;
  let lastCollisionTime = isPlayer1 ? lastCollisionTimePlayer1 : lastCollisionTimePlayer2;

  if (!playerCollision) {
    for (let asteroid of asteroids) {
      if (player.collides(asteroid)) {
        // Calculate collision angle
        let angle = atan2(player.y - asteroid.y, player.x - asteroid.x);

        // Calculate the relative velocity of the player with respect to the asteroid
        let relativeVelocity = p5.Vector.sub(player.velocity, createVector(asteroid.speed * cos(angle), asteroid.speed * sin(angle)));

        // Reflect player's velocity based on collision angle and asteroid's speed
        let velocityMagnitude = relativeVelocity.mag();
        let reflectionAngle = atan2(-relativeVelocity.y, -relativeVelocity.x);
        let newVelocity = p5.Vector.fromAngle(reflectionAngle);
        newVelocity.setMag(velocityMagnitude);

        player.velocity.set(newVelocity);

        // Set collision state for the player
        if (isPlayer1) {
          player1Collision = true;
          lastCollisionTimePlayer1 = millis();
        } else {
          player2Collision = true;
          lastCollisionTimePlayer2 = millis();
        }

        // Play collision sound when spacecraft collides with a rock
        if (!collisionSpacecraftSoundIsPlaying) {
          collisionSpacecraftSound.play();
          collisionSpacecraftSoundIsPlaying = true;
        }
      }
    }
  }
}

 

Another piece of code that is interesting is to solve the challenge I faced to change the asteroid count and speed of asteroids when the screen is made Fullscreen. This is implemented in the function below:

function handleAsteroidGeneration() {
  asteroids = []; // Clear existing asteroids
  let count = isFullscreen ? fullscreenAsteroidCount : defaultAsteroidCount;
  let speedMin = isFullscreen ? fullscreenAsteroidSpeedMin : defaultAsteroidSpeedMin;
  let speedMax = isFullscreen ? fullscreenAsteroidSpeedMax : defaultAsteroidSpeedMax;
  
  // Generate new asteroids based on the mode (fullscreen/windowed)
  for (let i = 0; i < count; i++) {
    let x = random(windowWidth); // Random x position
    let y = random(windowHeight); // Random y position
    let speed = random(speedMin, speedMax); // Random speed within the specified range
    let angle = random(TWO_PI); // Random initial rotation angle
    let rotationSpeed = random(-0.05, 0.05); // Random rotation speed
    let size = random(60,120); // Random size
    asteroids.push(new Asteroid(x, y, size, speed, angle, rotationSpeed));
  }
}

I have also implemented a similar switch for acceleration as indicated in the variable declarations that is modified in the toggle and exit Fullscreen functions.

let accelerationFullscreen = 0.7; // Acceleration value for fullscreen mode
let accelerationWindowed = 0.3; // Acceleration value for windowed mode
let acceleration = isFullscreen ? accelerationFullscreen : accelerationWindowed; // Set initial acceleration value based on current mode 
function toggleFullscreen() {
  fullscreen(true); // Switch to fullscreen
  isFullscreen = true;
  handleAsteroidGeneration(); // Handle asteroid generation for fullscreen
  acceleration = accelerationFullscreen; // Set acceleration for fullscreen mode
}

function exitFullscreen() {
  fullscreen(false); // Exit fullscreen
  isFullscreen = false;
  handleAsteroidGeneration(); // Handle asteroid generation for windowed mode
  resizeCanvas(600, 600); // Resize canvas back to original size
  acceleration = accelerationWindowed; // Set acceleration for fullscreen mode
}

 

This changes the speed as well as number of asteroids spawned according to the screen mode so that the user encounters roughly the same amount of challenge in both cases. Since these variables have been defined at top, they can be changed anytime .

Reflection

Overall, I am pretty happy with the final game I created. I learnt a lot of new things in p5 and realized how the organization of the code into classes representing various types of objects made it much easier to understand and develop. There is some scope for improvement that could be done in the future such as :

  1. Adding more Players to the game (The player class is already implemented).
  2. Adding more sounds to the game.
  3. Adding extra fun objects and classes such as a ‘black hole’ that pulls the spaceship towards it for some time and or a ‘super star’ that gives more points than usual but stays only for a certain duration of time.
  4. Changing the sprite of the spaceship? Right now, it’s just simple triangles but the code would also work for better sprites.
  5. Experimenting with different trails. I have a simple spacecraft booster with one large and two small ellipses but its possible to make more sophisticated ones and attach it to player class.

Leave a Reply