Afra Binjerais – Midterm final

HAILSTORM HAVOC

My game draws significant inspiration from the hailstorm that occurred in Abu Dhabi a few weeks ago.

 

I was captivated by the idea of creating a game where the player must dodge hail. I envisioned a game centered around a car avoiding the hail, however, this concept proved to be overly complex, and after trial and error, I was able to overcome this challenge. To play the game: players must move the car away from the falling hail using the mouse click function. Interestingly, the mouse click function in my game serves two purposes, a feature I was unaware of until Pi assisted me in rectifying a misunderstanding. This allowed me to control both the car and a sprite, adding a fascinating layer to the gameplay.

function mousePressed() {
  console.log("Mouse Press");
  //just flipping between modes 0 and 1 in this example

  clouds.forEach((cloud) => cloud.startAnimation());
}

function mouseReleased() {
  clouds.forEach((cloud) => cloud.stopAnimation());
}

How the game works: 

    1. You must press Click to start
    2. To move the car you have to press the mouse
      1. The longer the press the further right the ball will go
    3. Dodge the hail (white balls) and avoid touching the red line as both are game over
    4. Press “space” to restart the game 

This is my game:


If it doesn’t work, visit:

https://editor.p5js.org/Afrabinjerais/sketches/q0cF4mKOG

The game is straightforward yet incorporates all the coding components we have learned to date. Its design is uncomplicated, effectively conveying the intended message. I got inspired by this p5 sketch and, the link takes you to a simple game on P5, which has a similar concept to my game, where the objects are falling from the sky.

https://editor.p5js.org/jordanBlueshift/sketches/vSO_bzkaD

 My favorite aspect of my game is the cloud sprites, which fidget whenever the mouse is clicked, creating the impression that the clouds are generating hail. On another hand,  I also encountered a significant challenge when integrating sound effects to play each time the score increased, which was undoubtedly the most difficult part for me to implement. 

    time += 1;
    if (frameCount % 60 == 0) {
      score++;
    }

function scoreUpdate() {
  // score += 10;
  if (score != prevScore) {
    scoreSound.play();
  }
  
  prevScore = score;
  
  fill(128, 128, 128, 150);
  rect(width - 100, 5, 75, 20, 5);
  fill(255);
  text("SCORE: " + int(score), width - 65, 15);
}

The issue stemmed from the points increasing too rapidly. By reducing the timer and implementing a modulo operation as suggested by the professor, I was able to resolve this problem. Looking ahead, or given more time, I would be eager to experiment with transforming the background to be interactive and making it move to visualize a street.  Although my initial attempt at this modification was unsuccessful, I am keen on dedicating time to delve into unfamiliar areas of coding to make this feature a reality.

This is the code for the game:

let gameMode = 0; // Variable to store the current game mode
let musicSound; // Variable to hold the music sound object
let gameoverSound; // Variable to hold the game over sound object
let scoreSound; // Variable to hold the score sound object
var landscape; // Variable to store the landscape graphics
var car_diameter = 15; // Diameter of the ball
var bomb_diameter = 10; // Diameter of the bombs
var xpoint; 
var ypoint; 
var zapperwidth = 6; // Width of the zapper
var numofbombs = 10; // Number of bombs
var bombposX = []; // Array to store X positions of bombs
var bombposY = []; // Array to store Y positions of bombs
var bombacceleration = []; // Array to store acceleration of each bomb
var bombvelocity = []; // Array to store velocity of each bomb
var time = 0; // Variable to track time, usage context not provided
var timeperiod = 0; // Variable to store a time period, usage not clear without further context
var score = 0; // Variable to store the current score
var posX; // X position, usage context not provided
var inMainMenu = true; // Boolean to check if the game is in the main menu
var prevScore = 0; // Variable to store the previous score
let font; // Variable to store font, usage context not provided


//Cloud Variables
let spritesheet;
let oneDimensionarray = [];

function preload() {
  spritesheet = loadImage("clouds.png");
  musicSound = loadSound("sounds/song.mp3");
  gameoverSound = loadSound("sounds/gameover.mp3");
  scoreSound = loadSound("sounds/score.mp3");
  glassbreak = loadSound("sounds/glassbreaking.wav");
  font = loadFont("fonts/Inconsolata_Condensed-Light.ttf");
  car = loadImage("car.png");
  car2 = loadImage("car2.png");
}

// Cloud class starts
class Cloud {
  constructor(x, y, speed, stepSpeed, scale) {
    this.x = x;
    this.y = y;
    this.scale = scale; // Add scale property
    this.speed = speed;
    this.stepSpeed = stepSpeed;
    this.step = 0;
    this.facingRight = false; // Initially moving to the left
    this.animationTimer = null;
  }

  move() {
    if (this.facingRight) {
      this.x += this.speed;
      if (this.x > width + spritesheet.width / 4) {
        this.x = -spritesheet.width / 4; // Wrap around to the left side
      }
    } else {
      this.x -= this.speed;
      if (this.x < -spritesheet.width / 4) {
        this.x = width + spritesheet.width / 4; // Wrap around to the right side
      }
    }
  }

  display() {
    push();
    if (!this.facingRight) {
      scale(-this.scale, this.scale); // Apply scale with horizontal flip
      image(oneDimensionarray[this.step], -this.x, this.y);
    } else {
      scale(this.scale, this.scale); // Apply scale
      image(oneDimensionarray[this.step], this.x, this.y);
    }
    pop();
  }

  advanceStep() {
    this.step = (this.step + 1) % 8;
  }

  startAnimation() {
    this.facingRight = true;
    clearInterval(this.animationTimer);
    this.animationTimer = setInterval(() => this.advanceStep(), this.stepSpeed);
  }

  stopAnimation() {
    this.facingRight = false;
    clearInterval(this.animationTimer);
  }
}

let clouds = [];
// Cloud class ends

function setup() {
  createCanvas(640, 480);
  textAlign(CENTER);
  musicSound.play();
  
var temp00 = 0, 
    temp01 = -20; 

// A while loop that increments temp01 based on temp00 until temp01 is less than the canvas height
while (temp01 < height) {
  temp00 += 0.02; // Increment temp00 by 0.02 in each loop iteration
  temp01 += temp00; // Increment temp01 by the current value of temp00
  timeperiod++; // Increment timeperiod in each iteration
}

// Calculate the initial position of posX based on zapperwidth and car_diameter
posX = zapperwidth + 0.5 * car_diameter - 2;

// Set xpoint and ypoint relative to the width and height of the canvas
xpoint = 0.7 * width; // Set xpoint to 70% of the canvas width
ypoint = height - 0.5 * car_diameter + 1; // Set ypoint based on the canvas height and car_diameter

initbombpos(); // Call the initbombpos function (presumably initializes bomb positions)

imageMode(CENTER); // Set the image mode to CENTER for drawing images centered at coordinates

// Initialize variables for width and height based on sprite sheet dimensions divided by specific values
let w = spritesheet.width / 2;
let h = spritesheet.height / 4;

// Nested for loops to extract sprite images from the sprite sheet and push them to oneDimensionarray
for (let y = 0; y < 4; y++) {
  for (let x = 0; x < 2; x++) {
    oneDimensionarray.push(spritesheet.get(x * w, y * h, w, h)); // Get sub-images from spritesheet and add to oneDimensionarray
  }
}

  
  // Create 3 clouds with horizontal offsets, different speeds and scales
  clouds.push(new Cloud(width / 8, height / 9, 0, 100, 0.9)); // First cloud
  clouds.push(new Cloud((2 * width) / 5, height / 9, 0, 100, 1.2)); // Second cloud
  clouds.push(new Cloud((2 * width) / 2, height / 9, 0, 200, 1.0)); // Third cloud
}

function draw() {
  background(58, 66, 94);

  if (gameMode == 0) {
  clouds.forEach((cloud) => {
      cloud.display();
    });
  textFont(font);
  fill(255);
  textSize(50); // Larger text size for the game title
  textAlign(CENTER, CENTER); // Align text to be centered
   text('HAILSTORM HAVOC', width / 2, height / 2 - 40);
  textSize(16); // Smaller text size for the directions
  // Draw the directions right below the game title
  text('DIRECTIONS:\n click mouse to dodge hail\n the longer the press the further right\n the car will go\n\n AVOID the red line - crossing it means game over', width / 2, (height / 2) + 50);
  textSize(20);
  text('Click to start!', width / 2, (height / 2) + 140);
   
}  
   else if (gameMode == 1) {
    clouds.forEach((cloud) => {
      cloud.move();
      cloud.display();
    });

    fill(239, 58, 38);
    rect(0, 0, zapperwidth, height);
    scoreUpdate();

    fill(255);
    noStroke();
    for (var i = 0; i < numofbombs; i++) {
      ellipse(bombposX[i], bombposY[i], bomb_diameter, bomb_diameter);
      
    }

    updatebombpos();

    
    // ellipse(xpoint, ypoint, car_diameter, car_diameter);
        image(car,xpoint, ypoint-30, car_diameter*5, car_diameter*5);

     
     xpoint -= 3;

    // Check if the mouse is pressed and the xpoint is within the canvas boundaries
if (mouseIsPressed && xpoint + 0.5 * car_diameter < width) {
  xpoint += 6; // Move the xpoint to the right by 6 units
}
     
     // Check if xpoint is less than or equal to posX or if a collision with a bomb has occurred
    if (xpoint <= posX || bombCollistonTest()) {
      gameover(); // Call the gameover function if either condition is true
    }
// Increment the score every 60 frames
    time += 1;
    if (frameCount % 60 == 0) {
      score++; // Increase score by 1
    }
  }

}

function updatebombpos() {
  // Iterate over each bomb
  for (var i = 0; i < numofbombs; i++) {
    bombvelocity[i] += bombacceleration[i]; // Update the velocity of the bomb by adding its acceleration
    bombposY[i] += bombvelocity[i]; // Update the Y position of the bomb based on its velocity
  }

  if (time > timeperiod) {
    initbombpos(); // Reinitialize the positions of the bombs by calling the initbombpos function
    time = 0;
  }
}

function initbombpos() {
  for (var i = 0; i < numofbombs; i++) {
    bombacceleration[i] = random(0.02, 0.03);
    bombvelocity[i] = random(0, 5);
    bombposX[i] = random(zapperwidth + 0.5 * car_diameter, width);
    bombposY[i] = random(-20, -0.5 * car_diameter) + 190;
  }
} //This function initializes the position and motion properties of each bomb by assigning random values within specified ranges.

function bombCollistonTest() {
  var temp = 0.5 * (car_diameter + bomb_diameter) - 2;
  var distance;
// Iterate over each bomb to check for a collision
  for (var i = 0; i < numofbombs; i++) {
    distance = dist(xpoint, ypoint, bombposX[i], bombposY[i]);
    if (distance < temp) {
      return true;
    }
  }
  return false;
} //This function checks for collisions between the player and each bomb by comparing the distance between them to a threshold. If any bomb is too close (within the threshold), it returns true (collision detected). Otherwise, it returns false.

function gameover() {
  image(car2,xpoint, ypoint-30, car_diameter*5, car_diameter*5);
  musicSound.pause();
  glassbreak.play();
  gameoverSound.play();
  textFont(font);
  fill(255);
  textSize(50); // Set the text size
  textAlign(CENTER, CENTER); // Align text to the center
  text("GAME OVER", width / 2, height / 2 - 20); // Center the text horizontally and vertically
  textSize(15); // Decreased text size for "press space to restart" text
  text("Press space to restart", width / 2, height / 2 + 20); // Positioned below "GAME OVER" text
  noLoop();
}

function scoreUpdate() {
  // score += 10;
  if (score != prevScore) //// Play the scoring sound only if the current score has changed from the previous score 
  {
    scoreSound.play();
  }
  
  prevScore = score;
  
  fill(128, 128, 128, 150);
  rect(width - 100, 5, 75, 20, 5);
  fill(255);
  text("SCORE: " + int(score), width - 65, 15);
}

function keyPressed() {
  if (keyCode === 32) {
    // Check if the key pressed is space (keyCode 32)
    restartGame(); // Call the function to restart the game
  }
  
}

function mousePressed() {
  if (gameMode==0)
    gameMode=1;
  console.log("Mouse Press");
  //just flipping between modes 0 and 1 

  clouds.forEach((cloud) => cloud.startAnimation());
}

function mouseReleased() {
  clouds.forEach((cloud) => cloud.stopAnimation());
}

function restartGame() {
  // Reset all game variables to their initial values
  musicSound.play();
  gameoverSound.pause();
  time = 0;
  score = 0;
  posX = zapperwidth + 0.5 * car_diameter - 2;
  xpoint = 0.5 * width;
  ypoint = height - 0.5 * car_diameter + 1;
  initbombpos();
  // Restart the game loop
  loop();
} 
//This function resets the game environment and variables to their initial state, essentially restarting the game. It resumes background music, pauses any game over sound, resets score and time, repositions the player and bombs, and restarts the game loop.

Enjoy!

 

References of pictures:

https://bnnbreaking.com/finance-nav/al-ains-unprecedented-hailstorm-a-costly-blow-to-the-car-sales-sector

https://bnnbreaking.com/weather/uaes-al-ain-transformed-unprecedented-hailstorm-blankets-streets-in-white

 

 

Raya Tabassum: Midterm Project

Link to the fullscreen version

Concept: My midterm project is a game I’ve named “Gold Rush”. It’s inspired from the Super Mario games’ design. The game features a character Super Onion boy that the player controls to jump over and walk to dodge enemies and collect coins to increase their score. The main character, an adventurer with a knack for treasure hunting, traverses a dynamically scrolling landscape filled with gold coins for him to collect. The game introduces a unique enemy, the “GOMBA,” a mysterious creature with a purple body, tentacles, and light-flickering eyes, adding a layer of intrigue and challenge to the player’s journey. Dodging the enemy gains the player +5 points and collecting each coin gains them +10 points, and the score system is displayed on top of the screen while the game is playing. If the player collides with the enemy GOMBA, the game over screen appears and to play again – the player needs to press SHIFT key and the game will restart.

User Experience: The game is structured around a loop where the player’s character is continuously moving forward, with the background scrolling to simulate progression through the game world. This effect is achieved through an implementation of looping background images that reset once they scroll past the view, creating an endless moving landscape. The characters, including the player, the coin, and the enemy, are animated using sprites, which are sequences of images that create the illusion of motion when displayed in rapid succession.


One aspect of the project I’m particularly proud of is the collision detection mechanism. It’s designed to be both efficient and accurate enough for the game’s scope, using simple geometric shapes to approximate the player, coins, and enemies’ positions. This simplicity ensures the game runs smoothly without compromising the gameplay experience. Another highlight is the sound integration, which enhances the immersive experience of the game. The careful selection and timing of sound effects for jumping, collecting coins, and colliding with enemies add a layer of polish that elevates the overall game.

Areas for Improvement and Challenges: One area for improvement involves enhancing the game’s replay value. Currently, the game offers a straightforward challenge with a single level that repeats. Introducing varied levels with increasing difficulty with speed, diverse environments, and additional enemy types could significantly enhance the player’s experience and engagement. Implementing a power-up system or special abilities for the character could also add depth to the gameplay.

During development, one challenge encountered was ensuring that the game remained engaging over time. Initially, if the player missed a coin, it wouldn’t reappear, which could’ve led to a monotonous gameplay loop. This issue was addressed by adjusting the coin’s reinitialization logic to ensure it reappears regardless of whether it’s collected or missed, maintaining the game’s pace and challenge. Another technical hurdle was balancing performance with the visual richness of the game, particularly with sprite animation and background scrolling. Also to integrate the sound loops perfectly was hard at first but after fixing the code logic in different ways the desired results were received.

Full code for the game:

//GOLD RUSH BY RAYA TABASSUM

//CHARACTERS: SUPER ONION BOY, GOLD COIN, GOMBA THE ENEMY

//5 POINTS FOR AVOIDING THE ENEMY, 10 POINTS FOR COLLECTING THE COIN

// Preload function for loading assets before the game starts
let img, game_over_bg, sf, coin_sound, jump_sound, enemy_sound, pixel_font;
let xpos = 0, ypos = 0, x1 = 0, x2 = 1000, y = 0, x3 = 0, y2 = 0, score = 0, win = true;
let player, enemy, coin;

function preload() {
  img = loadImage("plt.png"); // Background image
  game_over_bg = loadImage("Gameover.png"); // Game over background image
  soundFormats('mp3', 'ogg'); // Specify the sound formats to use
  sf = loadSound("backgroundmusic.mp3"); // Background music
  coin_sound = loadSound("coin.mp3"); // Sound for collecting coins
  jump_sound = loadSound("jumping.mp3"); // Sound for jumping
  enemy_sound = loadSound("enemy_sound.mp3"); // Sound when colliding with an enemy
  pixel_font = loadFont("PixelFont.ttf"); // Custom font for text display
}

// Player class with properties and methods for the player character
class Player {
  constructor() {
    this.playerYOnGround = 550; // Y position of the player on the ground
    this.playerSize = 60; // Size of the player character
    this.bgGroundHeight = 45; // Height of the ground
    this.animationSlowDown = 8; // Slows down the animation frame rate
    this.width = 1000; // Width of the canvas
    this.jumpHeight = 0; // Current jump height
    this.jumpStrength = 0; // Current strength of the jump
    this.jumpStrengthMax = 5; // Maximum strength of the jump
    this.gravity = 0.1; // Gravity affecting the jump
    this.jumping = false; // Is the player jumping?
    this.playerImg = []; // Array to hold player images for animation
    this.numberPlayerImg = 6; // Number of player images
    this.playerImgIndex = 0; // Current index of the player image
    for (let i = 1; i <= 3; i++) {
      this.playerImg.push(loadImage(`guy-${i}.png`)); // Load player images
    }
  }

  initPlayer() {
    xpos = (this.width * 0.5) - (this.playerSize * 0.5); // Initialize player's horizontal position
    ypos = this.playerYOnGround; // Initialize player's vertical position
  }

  animatePlayer() {
    // Handle jumping logic
    if (this.jumping) {
      this.jumpStrength = (this.jumpStrength * 0.99) - this.gravity; // Apply gravity
      this.jumpHeight += this.jumpStrength; // Update jump height
      if (this.jumpHeight <= 0) {
        // Reset jump parameters if player is back on ground
        this.jumping = false;
        this.jumpHeight = 0;
        this.jumpStrength = 0;
      }
    }

    ypos = this.playerYOnGround - this.jumpHeight; // Update player's vertical position based on jump

    // Display the player image, use jumping image if jumping or animate otherwise
    if (this.jumping) {
      image(this.playerImg[0], xpos, ypos);
    } else {
      image(this.playerImg[this.playerImgIndex], xpos, ypos);
      // Animate player images
      if (frameCount % this.animationSlowDown === 0) {
        this.playerImgIndex = (this.playerImgIndex + 1) % 3;
      }
    }
  }
}

// Enemy class, inherits from Player and represents the enemy character
class Enemy extends Player {
  constructor() {
    super(); // Call the parent class constructor
    this.enemyImg = []; // Array to hold enemy images for animation
    for (let i = 1; i <= 3; i++) {
      this.enemyImg.push(loadImage(`enemy-${i}.png`)); // Load enemy images
    }
    this.enemyX = 800; // Initial horizontal position of the enemy
    this.enemyY = 460; // Initial vertical position of the enemy
    this.enemySize = 100; // Size of the enemy character
    this.enemyOnSky = random(250, 510); // Randomize enemy's vertical position
    this.enemyImgIndex = 0; // Current index of the enemy image
  }

  initEnemy() {
    this.enemyX -= 2; // Move the enemy horizontally
    this.enemyY = this.enemyOnSky; // Update the enemy's vertical position
  }

  animateEnemy() {
    // Display and animate the enemy character
    image(this.enemyImg[this.enemyImgIndex], this.enemyX, this.enemyY, this.enemySize, this.enemySize);
    this.enemyX -= 1; // Move the enemy horizontally
    this.initEnemy(); // Re-initialize the enemy position
    if (frameCount % this.animationSlowDown === 0) {
      this.enemyImgIndex = (this.enemyImgIndex + 1) % 3; // Animate enemy images
    }

    // Reset enemy position and increase score when it moves off-screen
    if (this.enemyX <= 0) {
      this.enemyX = 1000;
      this.enemyOnSky = random(250, 510);
      this.initEnemy();
      score += 5; // Increase score
    }

    // Check for collision with the player
    if (dist(this.enemyX, this.enemyY, xpos, ypos) <= (this.playerSize / 2 + this.enemySize / 2)) {
      win = false; // End the game if collision detected
      if (!enemy_sound.isPlaying()) {
        if (sf.isPlaying()) {
            sf.stop(); // Stop the background music
        }
        enemy_sound.loop(); // Play the enemy sound on loop
      }
    }
  }
}

// Coin class, inherits from Player and represents collectible coins
class Coin extends Player {
  constructor() {
    super(); // Call the parent class constructor
    this.coinImg = []; // Array to hold coin images for animation
    for (let i = 1; i <= 5; i++) {
      this.coinImg.push(loadImage(`coin-${i}.png`)); // Load coin images
    }
    this.coinX = 800; // Initial horizontal position of the coin
    this.coinY = 460; // Initial vertical position of the coin
    this.coinSize = 48; // Size of the coin
    this.coinImgIndex = 0; // Current index of the coin image
  }

  initCoin() {
    this.coinX = width; // Place the coin at the edge of the screen
    this.coinY = random(250, 510); // Randomize the coin's vertical position
  }

  animateCoin() {
    // Display and animate the coin
    image(this.coinImg[this.coinImgIndex], this.coinX, this.coinY, this.coinSize, this.coinSize);
    this.coinX -= 1; // Move the coin horizontally
    if (frameCount % this.animationSlowDown === 0) {
      this.coinImgIndex = (this.coinImgIndex + 1) % 5; // Animate coin images
    }

    // Check if the coin has been collected
    if (dist(this.coinX, this.coinY, xpos, ypos) <= (this.playerSize / 2 + this.coinSize / 2)) {
      this.initCoin(); // Re-initialize the coin position for it to reappear again
      score += 10; // Increase score
      if (!coin_sound.isPlaying()) {
        coin_sound.play(); // Play the coin collection sound
      }
    }
    // Check if the coin has moved off the left edge of the screen without being collected
    else if (this.coinX < 0) {
      this.initCoin(); // Re-initialize the coin position for it to reappear again
    }
  }
}

function setup() {
  createCanvas(1000, 750); // Set up the canvas
  if (!sf.isPlaying()) {
    sf.loop(); // Loop the background music if not already playing
  }
  player = new Player(); // Instantiate the player
  enemy = new Enemy(); // Instantiate the enemy
  coin = new Coin(); // Instantiate the coin
  player.initPlayer(); // Initialize the player
  coin.initCoin(); // Initialize the coin
  enemy.initEnemy(); // Initialize the enemy
}

function draw() {
  background(220); // Set the background color
  image(img, x1, y); // Draw the background image twice for a scrolling effect
  image(img, x2, y);
  x1 -= 1; // Move the background images to create a scrolling effect
  x2 -= 1;
  if (x1 <= -1000) { // Reset the background images positions for continuous scrolling
    x1 = 1000;
  }
  if (x2 <= -1000) {
    x2 = 1000;
  }

  // Animate player, coin, and enemy
  player.animatePlayer();
  coin.animateCoin();
  enemy.animateEnemy();
  
  // Display the score and instructions
  textSize(25);
  textFont(pixel_font);
  fill(255, 215, 0);
  text(`Score: ${score}`, 450, 100);
  text("Use UP Key to Jump", 375, 25);
  textSize(20);
  text("Collect the coins and avoid hitting the enemy!", 250, 45);

  if (!win) {
    image(game_over_bg, x3, y2); // Display the game over background
    textSize(30);
    text("Press SHIFT Key to Play Again", 285, 25);
    //noLoop(); // Stop the draw loop
  }
}

function keyPressed() {
  // Jump when UP key is pressed
  if (keyCode === UP_ARROW && win) {
    player.jumping = true; // Enable jumping
    if (!jump_sound.isPlaying()) {
      jump_sound.play(); // Play jump sound
    }
    player.jumpStrength = player.jumpStrengthMax; // Set jump strength to maximum
  }

  // Reset game when SHIFT key is pressed after losing
  if (keyCode === SHIFT && !win) {
    win = true; // Reset game status
    score = 0; // Reset score
    if (enemy_sound.isPlaying()) {
        enemy_sound.stop(); // Stop the enemy sound if playing
    }
    loop(); // Resume the draw loop
    setup(); // Reinitialize game setup
  }
}

 

Midterm – Hamdah AlSuwaidi

Recently, I had the opportunity to visit The Metropolitan Museum of Art (The MET) and was truly inspired by the diverse and rich history encapsulated within its walls. This visit sparked an idea in me to design an exhibit for The MET’s Costume Institute, focusing on the evolution of haute couture through iconic designs from renowned fashion houses. My midterm, aims to showcase the enduring allure and innovation of haute couture, highlighting how designers have redefined beauty and style over the decades.


By focusing on the individual stories that each garment tells about its era, the exhibit aims to provide a rich, textual narrative that captures the essence and evolution of haute couture across different periods.

Interactive elements, such as clickable images that reveal the history and significance of each piece, are integral to the exhibit. These features are designed to engage users, encouraging them to delve deeper into each garment’s story and understand its place within the broader narrative of fashion history.

let titleSize = 40;
let subtitleSize2 = 20;
let subtitleSize = 10;
let  bg, bg2;
let dress1, dress1Zoom1, dress1Zoom2, dress1Zoom3, dress1Zoom4, dress1Zoom5;
let dress2, dress2Zoom1, dress2Zoom2, dress2Zoom3, dress2Zoom4;
let dress3, dress3Zoom1, dress3Zoom2, dress3Zoom3;
let dress4, dress4Zoom1, dress4Zoom2, dress4Zoom3, dress4Zoom4;
let dress5, dress5Zoom1, dress5Zoom2, dress5Zoom3, dress5Zoom4, dress5Zoom5;
let dress6, dress6Zoom1, dress6Zoom2, dress6Zoom3, dress6Zoom4;
let scene = "main"; // Start with the exhibit scene
let bgsound;



function preload() {
  bgsound = loadSound('bgsoundfile.mp3')
  bg = loadImage('bg.jpeg');
  bg2 = loadImage('fullscreen.png'); // The fullscreen image for the exhibit
  //dress1
  dress1 = loadImage('dress1.png');
  dress1Zoom1 = loadImage('dress1Zoom1.png');
  dress1Zoom2 = loadImage('dress1Zoom2.png');
  dress1Zoom3 = loadImage('dress1Zoom3.png');
  dress1Zoom4 = loadImage('dress1Zoom4.png');
  dress1Zoom5 = loadImage('dress1Zoom5.png');
  //dress2
  dress2 = loadImage('dress2.png');
  dress2Zoom1 = loadImage('dress2Zoom1.png');
  dress2Zoom2 = loadImage('dress2Zoom2.png');
  dress2Zoom3 = loadImage('dress2Zoom3.png');
  dress2Zoom4 = loadImage('dress2Zoom4.png');
  //dress3
  dress3 = loadImage('dress3.png');
  dress3Zoom1 = loadImage('dress3Zoom1.png');
  dress3Zoom2 = loadImage('dress3Zoom2.png');
  dress3Zoom3 = loadImage('dress3Zoom3.png');
  //dress4
  dress4 = loadImage('dress4.png');
  dress4Zoom1 = loadImage('dress4Zoom1.png');
  dress4Zoom2 = loadImage('dress4Zoom2.png');
  dress4Zoom3 = loadImage('dress4Zoom3.png');
  dress4Zoom4 = loadImage('dress4Zoom4.png');
  //dress5
  dress5 = loadImage('dress5.png');
  dress5Zoom1 = loadImage('dress5Zoom1.png');
  dress5Zoom2 = loadImage('dress5Zoom2.png');
  dress5Zoom3 = loadImage('dress5Zoom3.png');
  dress5Zoom4 = loadImage('dress5Zoom4.png');
  dress5Zoom5 = loadImage('dress5Zoom5.png');
  //dress6
  dress6 = loadImage('dress6.png');
  dress6Zoom1 = loadImage('dress6Zoom1.png');
  dress6Zoom2 = loadImage('dress6Zoom2.png');
  dress6Zoom3 = loadImage('dress6Zoom3.png');
  dress6Zoom4 = loadImage('dress6Zoom4.png');
  
}
function setup() {
  createCanvas(720, 400);
  textSize(titleSize);
  textAlign(CENTER, CENTER);
   bgsound.loop();
  
}
function draw() {
  
  if (scene === "main") {
    drawMainScene();
  } else if (scene === "exhibit") {
    drawExhibitScene();
  } else if (scene.startsWith("zoomedInDress")) {
    drawZoomedScene();
  } bgsound.play();
  
}
function drawMainScene() {
  background(bg);
  noStroke();
  fill(0);
  textSize(titleSize);
  text("THE MET", width / 3 + 120, height / 3 - 80);
  textSize(subtitleSize2);
  text("THE COSTUME INSTITUTE", width / 2, height / 2 - 110);
  fill(0);
  textSize(subtitleSize);
  text("Touch the start button to begin!", width / 2, height / 2 - 80);
  // Start Button
  fill(255);
  rect(width / 2 - 50, height / 2 + 50, 100, 40, 10);
  fill(0);
  textSize(16);
  text("Start", width / 2, height / 2 + 70);
}
function drawExhibitScene() {
  background(bg2); // Use the fullscreen image as the background for the exhibit
  // Back Button, only show if we're not in the initial exhibit scene
  fill(255); // White color
  rect(20, 20, 60, 30, 10);
  fill(0); // Black color
  textSize(16);
  text("Back", 50, 35);
}
function drawZoomedScene() {
  let zoomedImage;
  switch (scene) {
    case "zoomedInDress1_1":
      zoomedImage = dress1Zoom1;
      break;
    case "zoomedInDress1_2":
      zoomedImage = dress1Zoom2;
      break;
    case "zoomedInDress1_3":
      zoomedImage = dress1Zoom3;
      break;
    case "zoomedInDress1_4":
      zoomedImage = dress1Zoom4;
      break;
    case "zoomedInDress1_5":
      zoomedImage = dress1Zoom5;
      break;
    case "zoomedInDress2_1":
      zoomedImage = dress2Zoom1;
      break;
    case "zoomedInDress2_2":
      zoomedImage = dress2Zoom2;
      break;
    case "zoomedInDress2_3":
      zoomedImage = dress2Zoom3;
      break;
    case "zoomedInDress2_4":
      zoomedImage = dress2Zoom4;
      break;
    case "zoomedInDress3_1":
      zoomedImage = dress3Zoom1;
      break;
    case "zoomedInDress3_2":
      zoomedImage = dress3Zoom2;
      break;
    case "zoomedInDress3_3":
      zoomedImage = dress3Zoom3;
      break;
    case "zoomedInDress4_1":
      zoomedImage = dress4Zoom1;
      break;
    case "zoomedInDress4_2":
      zoomedImage = dress4Zoom2;
      break;
    case "zoomedInDress4_3":
      zoomedImage = dress4Zoom3;
      break;
    case "zoomedInDress4_4":
      zoomedImage = dress4Zoom4;
      break;
    case "zoomedInDress5_1":
      zoomedImage = dress5Zoom1;
      break;
    case "zoomedInDress5_2":
      zoomedImage = dress5Zoom2;
      break;
    case "zoomedInDress5_3":
      zoomedImage = dress5Zoom3;
      break;
    case "zoomedInDress5_4":
      zoomedImage = dress5Zoom4;
      break;
    case "zoomedInDress5_5":
      zoomedImage = dress5Zoom5;
      break; 
    case "zoomedInDress6_1":
      zoomedImage = dress6Zoom1;
      break;
    case "zoomedInDress6_2":
      zoomedImage = dress6Zoom2;
      break;
    case "zoomedInDress6_3":
      zoomedImage = dress6Zoom3;
      break;
    case "zoomedInDress6_4":
      zoomedImage = dress6Zoom4;
      break;  
  }
  background(zoomedImage); // Display the zoomed-in image on full canvas
  // Back Button
  fill(255); // White color
  rect(20, 20, 60, 30, 10);
  fill(0); // Black color
  textSize(16);
  text("Back", 50, 35);
}
function mouseClicked() {
   if (!bgsound.isPlaying()) { // Check if the sound is not already playing
    bgsound.loop(); // Start playing the sound
  }
 
  if (scene === "main") {
    // If the Start button is clicked, change to the exhibit scene
    if (mouseX > width / 2 - 50 && mouseX < width / 2 + 50 && mouseY > height / 2 + 50 && mouseY < height / 2 + 90) {
      scene = "exhibit";
      
    }
  } else if (scene === "exhibit") {
    // Check if the click is within the back button's coordinates
    if (mouseX > 20 && mouseX < 80 && mouseY > 20 && mouseY < 50) {
      scene = "main";
     
    }
    // Check if the click is within Dress1's designated area
    else if (mouseX > 148 && mouseX < 228 && mouseY > 43 && mouseY < 149) {
      scene = "zoomedInDress1_1";
     
    }
    // Check if the click is within Dress2's designated area
    else if (mouseX > 325 && mouseX < 405 && mouseY > 44 && mouseY < 149) {
      scene = "zoomedInDress2_1";
      
    }
    //  Check if the click is within Dress3's designated area
    else if (mouseX > 450 && mouseX < 580 && mouseY > 44 && mouseY < 149) {
      scene = "zoomedInDress3_1";
      
    }
    //  Check if the click is within Dress4's designated area
    else if (mouseX > 148 && mouseX < 229 && mouseY > 203 && mouseY < 308) {
      scene = "zoomedInDress4_1";
      
    }
    //  Check if the click is within Dress5's designated area
    else if (mouseX > 323 && mouseX < 406 && mouseY > 201 && mouseY < 309) {
      scene = "zoomedInDress5_1";
      
    }
    //  Check if the click is within Dress6's designated area
    else if (mouseX > 450 && mouseX < 580 && mouseY > 201 && mouseY < 307) {
      scene = "zoomedInDress6_1";
      
 
    }
    
  } else if (scene.startsWith("zoomedInDress")) {
    // If the Back button is clicked in a zoomed-in scene, go back to the exhibit
    if (mouseX > 20 && mouseX < 80 && mouseY > 20 && mouseY < 50) {
      scene = "exhibit";
  
    } else {
      // Cycle through the zoomed-in images
      let parts = scene.split('_');
      let dressNum = parts[0].replace("zoomedInDress", "");
      let zoomLevel = parseInt(parts[1]);
      zoomLevel = zoomLevel >= 4 ? 1 : zoomLevel + 1; // Loop back after the last image for dress1 and dress2
      if(dressNum === "3" && zoomLevel > 3) zoomLevel = 1; // Since dress3 has only 3 zoom levels
      scene = `zoomedInDress${dressNum}_${zoomLevel}`;
    }
  }
}

 

 

Midterm & Midterm documentation – Shereena AlNuaimi

Catch The Dates

Why not use the “Fruit Ninja” game, which I spent years of my childhood playing, serve as the inspiration for this minigame? With little consideration, “Catch the dates” turned into a mini game.

Dates and explosives will begin to fall from the NYUAD  palm trees that serve as the game’s backdrop as soon as you begin playing. The objective is to avoid receiving a score of zero or below. The player can score two points on dates, but their score will be reduced by three if they catch the bomb in the basket.

One thing that I’m most proud of is the game itself as a whole. Being an extremely indecisive person, it was hard for me to just stick with one idea. However, with careful consideration I came to being excited to code this small game itself while including a part of my childhood in it while embedding culture and symbolizing the UAE’S culture and heritage by using dates instead of fruits.

One challenge I have encountered when coding this midterm, was the basket leaving a trace at the bottom as well as the background being not too flexible with height and fit on the screen.

However, I solved that issue with adjusting the image width and height in order to fit perfectly. 

In conclusion, this midterm really made me step out of my comfort zone. In reality I am not too proud of the work that I achieve nor do I like to share it, however, with this course, I was able to achieve that and step out of my comfort zone a little bit and be proud of the work I have accomplished especially when creating this little game. 

// Declare variables
let titleSize = 35
let bg;
let basketImg;
let dates = [];
let bombs = [];
let score = 0;
let gameStarted = false;
let gameOver = false;
let basket;
let basketScale = 0.5; // Adjust basket scale
let basketSpeed = 7;
let nextDateFrame = 0; // Frame count for next date
let nextBombFrame = 0; // Frame count for next bomb
let scoreBoxWidth = 200; // Width of score box
let scoreBoxHeight = 50; // Height of score box

// Preload images
function preload() {
  bg = loadImage('palm trees.png');
  basketImg = loadImage('basket.png');
}

// Setup canvas and game state
function setup() {
  createCanvas(800, 800); // Larger canvas size
  textAlign(RIGHT, TOP);
  textSize(24);
  resetGame();
}

// Reset game state
function resetGame() {
  score = 0;
  gameStarted = false;
  gameOver = false;
  dates = [];
  bombs = [];
  basket = createVector(width / 2, height - basketImg.height * basketScale); // Adjusted basket size
  nextDateFrame = 0; // Reset frame count for next date
  nextBombFrame = 0; // Reset frame count for next bomb
}
// Main game loop
function draw() {
  // Display background
  image(bg, 0, 0,width *1.5 , height*1.5);

  // Display instructions if game not started
  if (!gameStarted) {
    displayInstructions();
    return;
  }

  // Display game over screen if game is over
  if (gameOver) {
    displayGameOver();
    return;
  }
  
  // Display basket
  image(basketImg, basket.x, basket.y, basketImg.width * basketScale, basketImg.height * basketScale); // Adjusted basket size

  // Move basket continuously when arrow keys are held down
  if (keyIsDown(LEFT_ARROW) && basket.x > 0) {
    basket.x -= basketSpeed;
  }
  if (keyIsDown(RIGHT_ARROW) && basket.x < width - basketImg.width * basketScale) {
    basket.x += basketSpeed;
  }
  
  // Move and display dates
  if (frameCount >= nextDateFrame) {
    let date = new FallingObject('dates');
    dates.push(date);
    nextDateFrame = frameCount + int(random(120, 240)); // Randomize next date appearance
  }
  for (let date of dates) {
    date.move();
    date.display();
    if (date.checkCollision(basket.x, basket.y, basketImg.width * basketScale, basketImg.height * basketScale)) {
      date.reset();
      score += 2;
    }
  }

  // Move and display bombs
  if (frameCount >= nextBombFrame) {
    let bomb = new FallingObject('bomb');
    bombs.push(bomb);
    nextBombFrame = frameCount + int(random(120, 240)); // Randomize next bomb appearance
  }
  for (let bomb of bombs) {
    bomb.move();
    bomb.display();
    if (bomb.checkCollision(basket.x, basket.y, basketImg.width * basketScale, basketImg.height * basketScale)) {
      bomb.reset();
      score -= 3;
      if (score <= 0) {
        score = 0;
        gameOver = true;
      }
    }
  }

  // Display score
  fill(0, 0, 255);
  rect(width - scoreBoxWidth, 0, scoreBoxWidth, scoreBoxHeight);
  fill(255);
  textAlign(RIGHT, TOP);
  text(`Score: ${score}`, width - 10, 10);
}


// Handle key presses
function keyPressed() {
  // Start game on spacebar press
  if (!gameStarted && key === ' ') {
    gameStarted = true;
  }

  // Reset game on 'r' press
  if (gameOver && key === 'r') {
    resetGame();
  }
}

// Display game instructions
function displayInstructions() {
  fill(255);
  stroke(2);
  strokeWeight(5);
  textSize(titleSize);
  text("CATCH THE DATES", width/2 +190, height/2 -150);
  fill(255);
  text('Instructions:',width / 2 +120 , height / 2 -90);
  text('Use arrow keys to move the basket',width / 2 + 300 , height / 2 -40);
  text('Catch dates to score points',width / 2 + 250 , height / 2);
  text('Avoid bombs',width / 2 + 120 , height / 2 + 40);
  text('Press space to start',width / 2 + 180 , height / 2 + 80);
}

// Display game over screen
function displayGameOver() {
  fill(255, 0, 0);
  triangle(width / 2, height / 2 - 100, width / 2 - 150, height / 2 + 150, width / 2 + 150, height / 2 + 150);
  fill(255);
  textAlign(CENTER, CENTER);
  textSize(25);
  stroke(1);
  strokeWeight(3);
  text('Game Over!', width / 2, height / 2 + 30);
  text('Press "r" to play again', width / 2, height / 2 +  118);
}

// FallingObject class
class FallingObject {
  constructor(type) {
    this.x = random(width);
    this.y = -50;
    this.speed = random(2.5, 3.5); // Adjusted speed
    this.type = type;
    this.image = loadImage(`${type}.png`);
  }

  // Move the falling object
  move() {
    this.y += this.speed;
    if (this.y > height) {
      this.reset();
    }
  }

  // Display the falling object
  display() {
    image(this.image, this.x, this.y, 50, 50);
  }

  // Reset the falling object's position
  reset() {
    this.x = random(width);
    this.y = -50;
    this.speed = random(2.5, 3.5); // Adjusted speed
  }

  // Check for collision with another object
  checkCollision(objX, objY, objWidth, objHeight) {
    return this.x > objX && this.x < objX + objWidth && this.y > objY && this.y < objY + objHeight;
  }
}

 

Pi Midterm : The Deal

(Instructions : If you have a guitar, you can plug in your guitar and play the game. However, without a guitar, you can play with arrow keys. More instructions inside the game.)

Pi’s P5 Midterm : https://editor.p5js.org/Pi-314159/full/FCZ-y0kOM
If something goes wrong with the link above, you can also watch the full gameplay below.

Overall Concept

The Deal is a highly cinematic cyberpunk narrative I am submitting as my midterm project.

Meet Pi, the devil’s engineer, in a world where technology rules and danger lurks in every shadow. After receiving a mysterious call from a woman offering a lucrative but risky job., he’s plunged into a world of corporate espionage over brain-controlled robots.

The inspiration for this comes from my daily life, where I have to deal with tech clients, and … yes I actually do Brain controlled robots in my lab, you scan the brain waves through electroencephalogram (EEG) and use that feed that signal through a neural network to translate to robot movements. It’s in very early stage, but it is moving.

How the project works

The game is supposed to be played with the guitar. In fact, it is to be used not as a game, but as a storytelling tool by a guitar playing orator to a live audience. Below is the demonstration of the game.

A number of tools and a not so complicated workflow is used to create this game in a week. The full workflow is illustrated below.

For a midterm project, the project is rather huge. I have 16 individual Javascript Modules working together to run the player, cinematics, background music, enemies, rendering and parallax movements. Everything is refactered into classes for optimization and code cleanliness. For example, my Player class begins as follows.

class Player {
  constructor(scene, x, y, texture, frame) {
    // Debug mode flag
    this.debugMode = false;
    this.debugText = null; // For storing the debug text object
    this.scene = scene;
    this.sprite = scene.matter.add.sprite(x, y, texture, frame);
    this.sprite.setDepth(100);
    // Ensure sprite is dynamic (not static)
    this.sprite.setStatic(false);

    // Setup animations for the player
    this.setupAnimations();

    // Initially play the idle animation
    this.sprite.anims.play("idle", true);

    // Create keyboard controls
    this.cursors = scene.input.keyboard.createCursorKeys();

    // Player physics properties
    this.sprite.setFixedRotation(); // Prevents player from rotating

    // Modify the update method to broadcast the player's speed
    this.speed = 0;

    this.isJumpingForward = false; // New flag for jump_forward state

    // Walking and running
    this.isWalking = false;
    this.walkStartTime = 0;
    this.runThreshold = 1000; // milliseconds threshold for running
    // Debugging - Enable this to see physics bodies
  }

  //Adjust Colors
  // Set the tint of the player's sprite
  setTint(color) {
    this.sprite.setTint(color);
  }

What I am proud of

I am super proud that I was able to use a lot of my skills in making this.

For almost all the game resources, including the soundtrack, I either created it myself or generated it through AI, then plugged into the rest of the workflow for final processing. Here, in the image above, you can see me rigging and animating a robot sprite in Spine2D, where the original 2D art is generated in Midjourney.

Problems I ran Into

During the testing, everything went well. But during the live performance, my character got confused between moving left and moving right. When I played guitar notes for the character to move left, it moved right instead . This is because I declared the key mappings from the fft (Fast Fourier Transform) signal as frequency bands, which maps to right and left arrow keys accordingly.

In theory, it should work, but in practice, as in diagram below, once the left key mapping stops, the signal inadvertently passes through the right key mapping region (due to not having an exact vertical slope), causing unintentional right key presses.

I had to resort to the fft-> keymapping workflow since I cannot directly access the javascript runtime through my external C++ fft program. However, had the game been implemented as a native game (i.e. using Unity,Godot), then I can directly send unique UDP commands instead of keymapping regions. This would resolve the issue.

Rubric Checklist :

  • Make an interactive artwork or game using everything you have learned so far (This is an interactive Game)
  • Can have one or more users (A single player game, with the player acting as the storyteller to a crowd)
  • Must include

At least one shape

The “BEGIN STORY” button is a shape, in order to fulfill this requirement. Everything else are images and sprites.

At least one image

We have a whole lot of images and Easter Eggs. The graphics are generated in Midjourney, and edited in Photoshop.

At least one sound

Pi composed the original soundtrack for the game. It is in A minor pentatonic for easy improvisation over it. In addition there are also loads of ambience sounds. 

For the prologue monologue, I used this : https://youtu.be/Y8w-2lzM-C4

At least one on-screen text

We got voice acting and subtitles.

Object Oriented Programming

Everything is a class. We have 18 Classes in total to handle many different things, from Cinematics to Data Preloader to Background Music Manager to Parallax Background Management. Below is the Cinematic implementation.

class Cinematic {
  constructor(scene, cinematicsData, player) {
    this.scene = scene;
    this.cinematicsData = cinematicsData;
    this.player = player; // Store the player reference
    this.currentCinematicIndex = 0;
    this.subtitleText = null;
    this.isCinematicPlaying = false;
    this.collidedObject = null;
    this.lastSpawnSide = "left"; // Track the last spawn side (left or right)
    // Game objects container
    this.gameObjects = this.scene.add.group();
    this.phonecallAudio = null; // Add this line
  }

  create() {
    // Create the text object for subtitles, but set it to invisible
    this.subtitleText = this.scene.add
      .text(this.scene.scale.width / 2, this.scene.scale.height * 0.5, "", {
        font: "30px Arial",
        fill: "#FFFFFF",
        align: "center",
      })
      .setOrigin(0.5, 0.5)
      .setDepth(10000)
      .setVisible(false);

    // Setup collision events
    this.setupCollisionEvents();
  }

  executeAction(action) {
    switch (action) {
      case "phonecall":
        console.log("Executing phone call action");
        // Play the 'nokia' audio in a loop with a specified volume
        if (!this.phonecallAudio) {
          this.phonecallAudio = this.scene.sound.add("nokia", { loop: true });
          this.phonecallAudio.play({ volume: 0.05 });

 

  • The experience must start with a screen giving instructions and wait for user input (button / key / mouse / etc.) before starting (The main menu waits for the user click)
  • After the experience is completed, there must be a way to start a new session (without restarting the sketch) (After the story, it goes back to main menu)

Interaction design (is clear to user what they are controlling, discoverability, use of signifiers, use of cognitive mapping, etc.)

(We have a super simple Keyboard or Guitar input instructions)

 

 

Midterm – Saeed Lootah

For the midterm I wanted to do some kind of video game. I felt that it would be more fun and ironically more interactive than an interactive artwork. Originally I was stuck on the idea of Russian Roulette but that lead me to the idea of a Mexican Standoff. The premise of the game is that two players are facing each other and after a countdown they each have to shoot before the other player does. Furthermore, I wanted to prevent people from just holding down the shoot key so I made it so that if you shoot too early you fail.

I wanted the player to first see a main menu before they start the game. I was inspired by some of the previous works of students on their midterm and wanted something similar but also unique to my game. However, before I could start making it I had to find out how to do so. In class, I don’t remember which one exactly since it was a while ago now, I asked Pi about something to do with different pages/menus and he told me about a scene management class. That is a function or part of the code dedicated towards picking different menus. This ingenious solution was what I needed, and for that reason I put Pi in the credits menu for my game. Furthermore, Pi told me about game state machines and more about states in general in games, that concept helped me a lot during the creation of my project. I was proud of the code I made for the scene management class but not as proud as the code I made for the gameplay. A lot of work went into making the actual game itself and I wish I could show more of what I did without it being too boring but in any case; below is what I am most happy with.

function countdownFunctionality() {
  // frame rate is 60
    gameStarted = true;
    let x = 1;
    let xSecond = 60 * x; 
  
    let firstSecond =   initialFrameCount + xSecond * 1;
    let secondSecond =  initialFrameCount + xSecond * 2;
    let thirdSecond =   initialFrameCount + xSecond * (3+randomNumber)
  
    if(!(tooSoon1 || tooSoon2)) {
        // TODO sounds
      if(frameCount < firstSecond) {
        countDownDraw(3);
      }
  
      if(frameCount > firstSecond && frameCount < secondSecond) {
        countDownDraw(2);
      }
  
      if(frameCount > secondSecond && frameCount < thirdSecond) {
        countDownDraw(1);
      }
  
      if(frameCount > thirdSecond) {
        countDownDraw(4);
        player1.update();
        player2.update();
        fire = true;
      }
    }
  
  
  }

Above is the code for the countdown. I needed something that would (obviously) act like a normal countdown, going from 3 to 1 then to the words fire, but I also needed to have the program be able to detect a player firing at any point during the countdown. This meant I could not stop the program completely but still needed a way to add a delay. I chose to do this through essentially counting frames. Where when the function is called the frameCount at the time is kept and then the 1st, 2nd, 3rd, and final seconds are kept as a number which the frameCount has to be greater than for the appropriate icon to show. Then at the end of the code you can see the line “fire = true”. Fire is a boolean variable that was initialized outside the function and it is used to keep track of the states of the game, like Pi taught me 🙂 When it is true the players are allowed to shoot.

There are also a lot of general things about my code that I’m happy about. Primarily the way that I organized the code. I created a separate javascript file for each menu as well as the scene management class. This made things easier to navigate. Furthermore, going into this project I decided to use visual studio code instead of the website editor, I wasn’t sure how to at first but after some figuring things out I learned I had to use extensions, I found the specific extensions then spent a while learning how to use the extensions in the first place.

While the program works now, there are A LOT of things I would change. I added a lot of files thinking and sometimes trying to add them but eventually giving up because I ran into some kind of bug. If you end up going through the files you will notice this, and if you go through the comments you will notice that I wanted to add some extra features. I originally wanted to add a best of five game-mode but because of time constraints I had to give up on it for this project but maybe next time. Another thing I wish I could get to work, and there is still time is music that plays specific to the play menu and not just the main menu music which plays all the time. I haven’t been able to figure it out just yet because I was experiencing some bugs but I unfortunately cannot put aside time to do it at the moment, perhaps in the future, maybe even before class. Anyways, that’s all I have to say about this project. I’m happy with it overall, it is the most effort I’ve put into a coding project and the most lines of code that I’ve ever written, and I think it shows. I hope people have fun playing it.

P.S. The credits to all the people that have helped me and all of the people that made the sprites, images, etc. are in the credits section which you can find from the main menu.

 

 

6th March 2024:
Fixed a bug where if player 2 shoots too soon then player 1 does as well it shows that player 1 shot too soon and not player 2.

Response – Week 5, Golan Levin’s Computer Vision for Artists and Designers

Golan Levin’s Computer Vision for Artists and Designers,  explains the concept computer vision for beginner programmers like myself, placing a emphasizing on utilizing vision-based detection and tracking techniques within interactive media. The initial section introduces the artistic applications of computer vision beyond industrial and military realms.

It was a very interesting read that almost shows history in interactive media through computer vision, at least in some way or the other. It is through computer vision the first computer interactive art performance was developed.

For example, ‘Standards and Double Standards’ by Rafael Lozano-Hemmer is something that intrigued me. Belts spinning around based on who’s passing by – visually cool, but there’s this metaphorical layer that adds depth. It’s the kind of stuff that makes you ponder. Which is what I would like to implement in my work.

 

Midterm: Feline Frenzy

For my midterm, I had to create a game primarily focused on cats, driven by my love for them. The game’s objective is to reach a score of 20 by catching as many cats as possible within thirty seconds. Additionally, there is a power-up cat that boosts your score. I encountered two unsuccessful attempts in executing my ideas, leading me to start over, which I believe turned out quite well.

INITIAL IDEAS:

My initial idea was to create a game inspired by “My Talking Tom,” a game I enjoyed playing while growing up. I aimed to introduce a twist by replacing the cat with a camel. However, when it came time to execute the idea, I was unsure of how to begin and could not find any examples to follow.

 

My second idea involved creating an interactive game using PoseNet, modeled after the classic “I’ve got your nose” game. The core code functioned well; however, implementing the menu and other deliverables proved difficult, as they interfered with the rest of the code’s functionality. If you’re interested in trying it out, I recommend visiting the actual sketch, as you’ll need your webcam.

Moving on to the actual game, I aimed to create something simple and easy to play. However, I do wish it offered more of a challenge to players. Perhaps increasing the speed or adding another character that reduces the score instead of offering power-ups could enhance the difficulty. Additionally, feedback from one of my classmates highlighted the importance of clear instructions. Given the potential for confusion, providing explicit instructions regarding the timeframe, the scoring system, and the power-up cat would make it easier for users to understand and enjoy the game.

In terms of challenges, I faced issues similar to those of my classmates, particularly with the sketch not accepting images. I was advised to either work on the code locally or clear my cache to resolve this issue. However, my main challenges were related to the stylistic aspects of the game and deciphering the keyPressed(); function. Additionally, linking all parts of the game—from the menu to the game itself, and then to the win or lose screen—before looping it all back to the menu was probably the most time-consuming aspect of this entire project.

The most enjoyable aspect of this project for me was creating the graphics. I utilized Canva and Vecteezy to source all the elements needed to achieve the pixelated Mario aesthetic I desired.

Code + Notes:

// Midterm Project by Amal Almazrouei
// Credit:
// Sound: https://www.voicy.network/
// Font: Google Fonts
// Images: https://www.vecteezy.com/ ; pinterest.com
// Graphics: canva.com
// Example Code: https://editor.p5js.org/simontiger/sketches/Dj7GS2035


// Flags to control game state
let started = false;
let gameEnded = true; // Treat game as ended initially to present start menu
let showingEndScreen = false; // Indicates if end screen is currently displayed

// Game timing and positioning variables
let startTime;
let x = 200;
let y = 200;
let powerX = 100;
let powerY = 200;

// Score and game dynamics variables
let score = 0;
let r = 48; // Radius for interactive elements
let interval = 60; // Controls frequency of game events
let frames = interval; // Countdown to trigger game events
let powerIsThere = false; // Flag for power-up appearance
let increase = 1; // Score increment
let increased = 0;

// Assets and sound variables
let img, powerImg, startScreenBg; // Image variables for game elements
let myFont; // Custom font variable
let backgroundMusic, catSound, powerUpSound, winSound, loseSound; // Sound variables

function preload() {
  // Preload all necessary media assets
  img = loadImage('Media/cat1.png'); // Main character image
  powerImg = loadImage('Media/cat2.png'); // Power-up image
  startScreenBg = loadImage('Media/startScreenBg.png'); // Start screen background image
  myFont = loadFont('assets/MyFont.ttf'); // Custom font
  // Loading sound assets
  backgroundMusic = loadSound('sounds/backgroundMusic.mp3');
  catSound = loadSound('sounds/catSound.mp3');
  powerUpSound = loadSound('sounds/powerUpSound.mp3');
  winSound = loadSound('sounds/winSound.mp3');
  loseSound = loadSound('sounds/loseSound.mp3');
}

function setup() {
  createCanvas(400, 400);
  textFont(myFont); // Set the custom font
  backgroundMusic.loop(); // Play background music on loop
}

function keyPressed() {
  // Handles key presses for restarting or transitioning from end screen
  if (gameEnded && showingEndScreen) {
    showingEndScreen = false; // Exit end screen to show start menu
  } else if (!started && !showingEndScreen && gameEnded) {
    resetGame(); // Reset and start game from menu
  }
}

function resetGame() {
  // Reinitialize game variables for a new session
  started = true;
  gameEnded = false;
  showingEndScreen = false;
  startTime = millis(); // Reset game timing and positioning
  x = 200;
  y = 200;
  score = 0;
  r = 48;
  interval = 60;
  frames = interval;
  powerIsThere = false;
  increase = 1;
  increased = 0;
  clear(); // Clear the screen before starting
}

function mousePressed() {
  // Handle player interactions with game elements
  if (started && !gameEnded && !showingEndScreen) {
    if (dist(mouseX, mouseY, x, y) < r) {
      catSound.play();
      // Random chance for score increase
      const rand = random(1);
      if (rand < 0.02) {
        increase = 50;
      } else if (rand < 0.1) {
        increase = 10;
      } else {
        increase = 1;
      }
      score += increase;
      increased = 20;
      interval--;
      r--;
    }
    if (powerIsThere && dist(mouseX, mouseY, powerX, powerY) < r) {
      powerUpSound.play();
      score++;
      interval = 60;
      r = 48;
      powerIsThere = false; // Remove power-up after clicking
    }
  }
}

function draw() {
  // Main animation loop: Update game screen based on state
  if (!started && !showingEndScreen) {
    gameStartScreen(); // Show start screen if game hasn't started
  } else if (started && !gameEnded) {
    // Game play updates
    let currentMillis = millis();
    background(135, 206, 235); // Set background
    // Display power-up image if present
    if (powerIsThere) {
      image(powerImg, powerX - r, powerY - r, r * 2, r * 2);
    }
    // Display main character image
    image(img, x - r, y - r, r * 2, r * 2);
    // Display score and time left
    textAlign(LEFT, TOP);
    textSize(30);
    fill(255, 0, 255); // Set text color
    text(score, 15, 15);
    let timeLeft = 30 - Math.floor((currentMillis - startTime) / 1000);
    text("Time: " + timeLeft, 15, 50);
    // Update positions and check for game end conditions
    frames--;
    if (frames <= 0) {
      frames = interval;
      x = random(width);
      y = random(height);
      if (random(1) < 0.07) {
        powerIsThere = true;
        powerX = random(width);
        powerY = random(height);
      } else {
        powerIsThere = false;
      }
    }
    if (score >= 20) {
      endGame(true); // Player won
    } else if (currentMillis - startTime > 30000) {
      endGame(false); // Time's up, player lost
    }
  }
}

function endGame(win) {
  // Handle game end: Show win or lose screen
  started = false;
  gameEnded = true;
  showingEndScreen = true;
  background(135, 206, 235); // Optionally reset background
  textAlign(CENTER, CENTER);
  textSize(32);
  // Display win or lose message
  if (win) {
    winSound.play();
    fill(0, 0, 128); // Blue color for win
    text("You Win!", width / 2, height / 2);
  } else {
    loseSound.play();
    fill(0, 0, 128); // Blue color for lose
    text("You Lose!", width / 2, height / 2);
  }
  fill(255, 0, 255); // Magenta color for subtitle
  textSize(24); // Smaller font size for subtitle
  text("Press any key to continue", width / 2, height / 2 + 20);
}

function gameStartScreen() {
  // Display start screen at game launch
  image(startScreenBg, 0, 0, 400, 400);
}

Credit + Resources:

Sound: https://www.voicy.network/
Font: Google Fonts
Images: https://www.vecteezy.com/ ; pinterest.com
Graphics: canva.com
Example Code: https://editor.p5js.org/simontiger/sketches/Dj7GS2035

 

 

 

Raya Tabassum: Reading Response 4

The article provides a comprehensive examination of how computer vision technologies have evolved from highly specialized tools to accessible instruments for artistic expression and design. The inclusion of historical and contemporary examples of interactive art, such as Myron Krueger’s Videoplace and Golan Levin’s own Messa di Voce, serves not only to illustrate the potential applications of computer vision in art but also to inspire readers to think creatively about how they might employ these tools in their own work. The article does not shy away from addressing the challenges and ethical considerations inherent in surveillance and data collection, using projects like the Suicide Box by the Bureau of Inverse Technology to provoke critical thought about the implications of computer vision technology. The workshop example, LimboTime, serves as a tangible outcome of Levin’s pedagogic approach, demonstrating how novices can rapidly prototype and implement an interactive game using basic computer vision techniques. This example encapsulates the article’s core message: that with the right guidance and tools, computer vision can become a powerful medium for artistic exploration and expression.
By demystifying computer vision and providing practical tools and techniques for novices, Levin opens up new avenues for creative expression and interaction, reinforcing the idea that art and technology are not disparate fields but complementary facets of human creativity.

Week 5 : Midterm Progress

When I heard how the midterm would be to create a game, my mind immediately went to my favorite type of game: Rhythm Games. I wanted to try and recreate one of my favorite arcade games called Dance Dance Revolution where the player would have to stand on the arrows on the ground that would correspond to the rhythm of the background music.

This is the DDR Arcade Machine:

I successfully created three lives that would get lost when the player would miss a ball. I also managed to create a “Game Over” state which I have to modify. However, I need to work on the interactive part of it. The mechanics that I have currently are Mouse click based, meaning that a player would have to click on the target area in order to gain points. When I tried to play it using a mouse, it felt very awkward so I will probably change it to the arrow keys which would be way more convenient.

Update: Okay, turns out that It does not even work properly with the mouse no matter how hard I try. I will be switching the strategy to the arrow keys.  I found the exact arrows that I would want to use which are the following. Now I have to figure out how I could code the arrow keys and assign them to each of the targets.

This is what I would like it to look like: