Midterm Project – Cat Rescue Game

 

Midterm Project – Cat Rescue Game

For my midterm project, I was very excited to create a game based on something that I love, cats! As I have grown up and lived in Abu Dhabi for half my life, I have noticed that there are a lot of stray cats. So with this in mind, I wanted to design a game where the player walks around a city, rescues stray cats, and takes them to a shelter. I was inspired by a photography project I completed last semester about the spirit of street cats in Abu Dhabi. I went around the city in Abu Dhabi and captured these cats’ lives and the environment they are in. (link to the photos). The game combines movement mechanics, object interactions, and a simple pet care system. The goal of the game is to rescue and rehome all the stray cats in order to win the game. I started this midterm project by drawing a rough sketch of how I wanted it to look like, and the various screens I wanted to implement. 

Link to sketch:

Midterm Sketch

 

Final Game:

Link to final game

Essentially, my game consists of three main screens, the start, game, and restart screen. I wanted a specific look for my background that I was not able to find online, so I decided to create my own. For both the start screen and the game background screen, I used Illustrator to make various shapes and used some images like trees or garbage bins. Then I converted these to PNG files so that I was able to upload them into p5.js and use them in my code. For the shelter, I used different shapes to make it look like a house. Additionally, I made the start screen display a box with on-screen text explaining how to play the game. Similarly, the restart screen also has a circle with on-screen text showing that you won, and a button to restart. For the interaction aspect of my project, I implemented a way for the player to control the girl using the arrow keys on the keyboard to move around. The stray cats are located at random locations in the city and if the girl touches a cat, it is rescued and sent to a shelter. I also used happy, chill background music that I found fitting to the game that starts once you press “Start Game”.

Overall I am extremely satisfied with the way that my final game turned out. Something I am particularly proud of is that instead of using pre-made designs from the internet, I designed my own backgrounds in Illustrator, which gives my game the unique quality I was seeking. Also, I am proud that I was able to successfully implement a collision detection system that ensures that once the player rescues a cat, it gets added to the shelter. I also liked the three-screen system (Start → Game → Restart) which provides the game with a clear structure. Lastly, I am extremely proud that I was able to draw inspiration from my real-life experiences in Abu Dhabi, which makes the game more unique and personal to me. I was able to connect my previous photography project and this game, linking different creative disciplines. I like that my game has an underlying story about spreading awareness about stray cats, which adds an emotional layer to my project.

 

Car class (which represents the girl’s movement):

class Car {
  constructor() {
    this.x = width / 2;
    this.y = height - 80;
    this.size = 50;
  }

  move() {
    if (keyIsDown(LEFT_ARROW)) this.x -= 5;
    if (keyIsDown(RIGHT_ARROW)) this.x += 5;
    if (keyIsDown(UP_ARROW)) this.y -= 5;
    if (keyIsDown(DOWN_ARROW)) this.y += 5;

    // Keep girl inside canvas
    this.x = constrain(this.x, 0, width - this.size);
    this.y = constrain(this.y, 0, height - this.size);
  }

  display() {
    image(carImage, this.x, this.y, 60, 100);
  }

 

Throughout developing this game, I faced a few challenges that required me to problem-solve. One of the issues was ensuring that the text box and circle were layered properly over the background so they remained visible. It took me some time to adjust their positioning and layering so that they were correctly visible on the screen. Another challenge I came across was making sure that the girl’s movement worked smoothly in all directions (up, down, left, and right). Debugging this movement system took some trial and error, but I was able to fix it. Additionally, I ran into smaller, frustrating issues, like file name errors, for example, a missing capital letter caused images not to load properly. Debugging these minor but important details taught me the importance of careful file management. However, I was able to overcome these challenges and successfully build a functioning game. 

If I had more time, there are a few features I would have liked to add to enhance the user experience. One idea was to expand on the rescue aspect, right now, when the cats reach the shelter, they are considered rescued. However, I would have liked to add an extra step where the player needs to care for the cats (feeding them, treating injuries, etc.) before they are fully rescued. Another improvement would be to introduce different difficulty levels (Easy, Medium, Hard) by adding obstacles that the player must overcome to rescue the cats. Also, smaller interactive details, like the cats meowing when clicked, would also add to the game. Even without these extra features, I am really happy with how my game turned out. This project challenged me to problem-solve, debug, and think creatively, and I’m proud of what I was able to accomplish.

 

Music citation:

Music track: Marshmallow by Lukrembo

Source: https://freetouse.com/music

Week 6: Midterm

Midterm Report

Link to Project: https://editor.p5js.org/corbanvilla/full/nXKMcC8T7

Overall Concept

My project is created as a project after an article I wrote for The Gazelle newspaper a couple of years ago, commenting on the often toxic hustle cultured exhibited by NYUAD students. For instance, students often carry around their various responsibilities, commitments, and academic struggles as “badges” that provide social status. In other words, the busier you appear to be, and the less sleep you are getting, the more you are seen as “successful” in our hustle culture. While this type of problem is obviously cannot be generalized to all students, it is certainly applicable to a sizable subset, in my experience. Looking back through my time at NYUAD, there are definitely times where I see myself falling into this trap of quantity over quality.

My project, “Drowning @ NYUAD,” brings this phenomena to life, exaggerating the absurdity of students drowning in commitments. The scoring system of the game is measured in negative hours of sleep–the longer you play, the more sleep you miss. Better scores are proportional to less sleep.

Project Architecture

The architecture of the project was important to me, to ensure that all parts of the code are modular and easily reusable. To that end, all screens of the game are split into different JavaScript files, under the “/screens” subdirectory. They are then imported into the global context under the “index.html” file, and registered into the screen controller in “sketch.js.” Speaking of which, the screen controller (ScreenController class) provides a screen rendering management interface, equipped with a transition animation feature and a “nextScreen” callback function.

To utilize this modularity, screens are “registered” in the order that they should be used in “sketch.js.” Each screen receives a callback function, exposed by the screen controller, that allows any screen to trigger a transition to the next screen at any time. This allowed me to focus on each screen in isolation, without worrying about how screens may overlap or interfere with each other. Furthermore, each screen class then exposes it’s own “draw” method, that is only called when the screen is active. Otherwise, resources are not wasted rendering a background screen. An example of this is shown below:

screenController = new ScreenController();

let nextScreenCallback = screenController.nextScreen.bind(screenController);

screenController.registerScreen(new SplashScreen(nextScreenCallback, gameConfig));
screenController.registerScreen(new CharacterSelectScreen(nextScreenCallback, gameConfig));
screenController.registerScreen(new WeaponSelectScreen(nextScreenCallback, gameConfig));
screenController.registerScreen(new GameScreen(nextScreenCallback, gameConfig));
screenController.registerScreen(new GameOverScreen(nextScreenCallback, gameConfig));

A new JavaScript feature I stumbled upon whilst creating this feature is the “.bind” method. It appeared as an autocomplete suggestion from GitHub copilot while I was writing the code itself. I did not know what “.bind” did, so I asked ChatGPT how it works. It explained that it creates a version of the function, which binds internal references to “.this” to the object it belongs to. Thus, called “.bind” allows me to pass this screen transition function to the various screens. Then, when they call the transition, it will successfully refer to the ScreenController class instance.

Areas for Improvement

One area for improvement that I have identified is a desire to improve the artwork in the game. For instance, while one character is animated, I want to enable the rest of the characters to be animated. Furthermore, I want to animate some of the weapons, such as the triangle ruler, to make it spin as it flies through the air.

I would also like to work on the difficulty of the game. While monsters do begin to spawn faster as time goes on, I want the player’s speed and ammunition speed to also increase, allowing the player to more successfully reach longer stages of the game. Furthermore, I think that power-up abilities, such as those in Mariokart, could be interesting to make the game a bit more balanced.

Screenshots

Here are a few screenshots from the available game screens:

Midterm Project

Concept

My midterm project is an escape room game where the players must follow a series of clues to find different objects around the room. The game begins with a hint that directs the player to the first object. An once it is found, the object reveals a number that the player must remember, as it is part of the final password. The object also provides the next clue, leading the player to the next item in the sequence. This process continues until all objects have been discovered, all of them giving a number needed for the final passcode. To ensure the correct sequence, my game has an ordered clicking type of logic, this means the players can only interact with objects in a specific order. If they attempt to click on an object out of sequence, it will not respond, preventing them from skipping ahead or guessing the passcode incorrectly. This makes sure the players follow the clues and remember the numbers in the right order, so that they can successfully input the password at the end to escape.

link to sketch: https://editor.p5js.org/tfr9406/full/1UNobzqQo

Code Highlights

I think a very important part of my game’s code is the ordered clicking system, which makes sure players find clues in the right order, and prevents them from skipping ahead . To do this I made it so that the code tracks the order of  the interactions using clickOrder and marks visited clues in the visitedClues object. This means, each clue can only be accessed if the previous one has been clicked. For example, the clock must be clicked first (clickOrder === 0) before moving to clue1, then the computer unlocks clue2, and so on until the final clue, the painting, leads to the password page.

I also made it so that the players can revisit clue while keeping the correct order. This is important because it allows players to go back and see the clue again without breaking the structured order. The visitedClues object keeps track of which clues have already been discovered, making sure that once a clue is unlocked, it remains accessible even if the player navigates away and returns. For example, once the player clicks on the clock, visitedClues.clue1 is set to true, meaning they can go back to it at any time. However, they can’t jump ahead to later clues unless they follow the intended order.  This is the code:

if (currentPage === "game") {
  // checks for valid order before allowing to open next clue page
  if (clickOrder === 0 && clock.isClicked(mouseX, mouseY)) {
    currentPage = "clue1";
    clickOrder = 1; // clock clicked first
    visitedClues.clue1 = true; // mark clue1 as visited
  } else if (clickOrder === 1 && computer.isClicked(mouseX, mouseY)) {
    currentPage = "clue2";
    clickOrder = 2; // computer clicked second
    visitedClues.clue2 = true; // mark clue2 as visited
  } else if (clickOrder === 2 && cupboard.isClicked(mouseX, mouseY)) {
    currentPage = "clue3";
    clickOrder = 3; // cupboard clicked third
    visitedClues.clue3 = true; // mark clue3 as visited
  } else if (clickOrder === 3 && books.isClicked(mouseX, mouseY)) {
    currentPage = "clue4";
    clickOrder = 4; // books clicked fourth
    visitedClues.clue4 = true; // mark clue4 as visited
  } else if (clickOrder === 4 && painting.isClicked(mouseX, mouseY)) {
    currentPage = "password"; // move to password page after painting
  }

To get to this final code, I first tested it out with a simpler object and basic logic to make sure the ordered clicking system worked correctly. Initially, I used a minimal setup with just 3 clickable elements and basic variables to track whether an item had been clicked. This helped me confirm that the logic for the sequential interactions was working before expanding it to include the full set of clues and the ability to revisit them. Below was the initial code:

function mousePressed() {
  if (!rectClicked && mouseX > 50 && mouseX < 130 && mouseY > 100 && mouseY < 150) { //first rectangle is clicked
    rectClicked = true;
  } else if (rectClicked && !triClicked && mouseX > 170 && mouseX < 230 && mouseY > 100 && mouseY < 150) { //triangle clicked true  if rectangle clicked first
    triClicked = true;
  } else if (rectClicked && triClicked && !circClicked && dist(mouseX, mouseY, 320, 125) < 25) {//circle clicked true if rectangle and triangle clicked before
    circClicked = true;
    escape = true; //clicking circle = players escapes
  }
}

Challenges

For my game I knew I wanted to implement hover animation to improve overall user experience by providing feedback. However, this was tricky at first because my game page was based on images rather than shapes. Unlike with shapes, where I could easily change the fill color on hover, I had to find a way to replace the whole image itself to give that same visual feedback. To solve this, I created an if-else condition that checks the mouse’s position relative to the designated area for hover. I then updated the image only if the mouse is hovering within the defined boundaries of the clickable area, and also made sure that when the hover condition is not met, the image reverts to its original state, which prevented it from being stuck in the wrong image.

// hover effect 
function handlePageHoverEffects() {
  if (currentPage === "landing") {
    
    // hover for Landing image (switches to landing2 on hover)
    if (mouseX >= 346 && mouseX <= 468 && mouseY >= 337 && mouseY <= 403) {
      currentImage = landing2; // switch to landing2 on hover
    } else {
      currentImage = landing1; // switch back to landing1 otherwise
    }

Improvement

Some of the improvements I could make to the game could maybe include adding different rooms or clues, which would provide more variety and depth to the game. Additionally, introducing difficulty levels would also make the game more accessible to a wider audience. For example, a beginner level with simple clues , and then progressively harder levels with more difficult riddles, hidden objects, and tougher challenges.

 

 

Midterm Project – Traffic Dash

Concept

The main idea was to create a fun, fast paced game which would be very fun and challenging at the same time. The game is in the “endless runner” genre of games (if you can avoid traffic for that long) where the main objective is to survive as long as possible and collect as many coins as you can therefor raising your score.

The game was inspired by other popular endless runner games such as “Subway Surfers” but with a progressive difficulty curve to make the game more difficult. The game also has some strategizing as sometimes the coins can lead you into a trap with no way out and a quick game over!

What are you waiting for, click here and try to dash through the traffic!

How the game works and code parts I am proud of

The game, of course, starts with an instructions screen where the player is asked to press enter to begin the game. As the game starts the player can see that the left and the right arrows control the movement of the car on the bottom of the screen. After a bit cars start spawning on top of the screen, randomly, but on carefully selected coordinates to ensure they don’t spawn off the screen or off the road (some traffic laws can’t be broken).

The player then sees coins appearing also randomly, but moving slower than the cars to ensure the user can pick them up safely. The user needs to decide if it is safe enough to move left or right to collect the coin and be sure to have enough time to move away from oncoming traffic.

Even though the goal is to collect as many coins as possible, as the game progresses, and the player is collecting coins and making their score higher, the speed of the oncoming traffic increases making the game much more difficult, but also making the distance values move faster which can make the survival more rewarding for the high score. The speed of the traffic is maxed at 25 so the game doesn’t become unplayable.

When the user eventually hits the oncoming traffic, they are met with a game over screen displaying the score they achieved in the last run with the text asking the user to press enter if they want to retry and beat the last score.

I am very proud with the collision detection system, which in the beginning did face some challenges (will talk more about it later in the documentation) but now works perfectly!

//Handling collision detection
function checkCollisions() {
  for (let t of trafficCars) {
    if (
      car.x < t.x + t.w &&
      car.x + car.w > t.x &&
      car.y < t.y + t.h &&
      car.y + car.h > t.y
    ) {
      // print("Collision with traffic!");
      crash.play();
      game_over = true;
      displayGameOverScreen();
      noLoop();
    }
  }

  if (
    car.x < coin.x + coin.w &&
    car.x + car.w > coin.x &&
    car.y < coin.y + coin.h &&
    car.y + car.h > coin.y
  ) {
    // print("Coin collected!");
    coin_collect.setVolume(0.3);
    coin_collect.play();
    score += 1;
    coin.y = random(-300, 0);
    coin.x = random(coin_spawn_points);
  }
}

I am also very proud of the speed increase with the score increase which also makes the distance value rise faster.

//Making the game faster as the score goes up
function increaseDifficulty() {
  let speedIncrease = min(20, 7 + score * 0.5);
  for (let t of trafficCars) {
    t.vy = speedIncrease;
  }
  coin.vy = speedIncrease - 0.5;

  // Adjust distance increase interval based on speed
  distanceUpdateInterval = max(50, 100 - speedIncrease * 50);
  // print(speedIncrease); 
}

This part took some testing, that is why I left the print statement there to show how I tested the code and the logic of the game.

Problems faced along the way

Unfortunately, like every code ever written, it didn’t come without hardship. The beginning was smooth sailing as I started with simple shapes and just implementing simple game logic.

This was a great start and I was happy with the progress I was making.

The first problem, though, arose when I started uploading photos to use for objects in the game.

This is the image that I used for the player object. I used a PNG file to have the transparent background so it only looks like the car is on the screen. Now, at first I thought this would work perfectly and that I wouldn’t have any issues to deal with, but would quickly learn otherwise. The issue was the collision detection which happened even if in theory there was enough distance between cars.

Why is this? Well even though we don’t see the transparent background of the PNG, it is still there and the collision detection algorithm detects it as a part of the car object. To fix this I used a built in image function which cropped the unnecessary parts of the image.

car = new Car(160, 300, 50, 100);

for (let i = 0; i < traffic_number; i++) {
  trafficCars.push(
    new Traffic(random(traffic_spawn_points), random(-500, 0), 65, 115)
  );
}

(The 3rd and 4th values of the functions)

Other issue I faced was with the file loading system, or specifically, writing in the csv file to make the game keep track of the highest score of a player.

My vision was to collect  all the scores of all the players who have every played and display the best, but I later learned that due to security reasons the p5js editor does not allow writing in files and has a read-only access to all the files uploaded. I decided to still keep the file and just display some high score to encourage players to try to beat it. Maybe in the future I could use google docs with their API to help me write in them, but due to the time constraint this will have to do for now.

There is also an issue with the objects sometimes spawning in one another. Fixing this would require changing the whole spawning logic and is unfortunately not possible in the short time span, but is definitely something that can be fixed in the future.

Future improvements and final thoughts

In the future I would love to work on this game even more and polish it to make it perfect. I would like to add some animations for crashes and maybe driving and would love to add power ups to spice up the gameplay. Unfortunately due to time constraints these implementations were not possible for this submission, but I think would be a fun addition to have in the future.

Overall I am happy with how the game turned out. In the beginning the game seemed like a big workload and a heavy task I set upon myself, but as I got to work on it more I got to work with everything we were taught in class and I really started to love the game and the experience of coding it.

This project has not been just a great learning opportunity, but has also inspired me to work on more different projects and become better at creating games. I will definitely be working on this project more and refining it to perfection.

Thank you for reading the documentation!

 

Midterm Project — Candy Collect

Introduction

This project was inspired by the food-theme (specifically, candy/sweets) games I used to play as a kid. I wanted to re-create the “vibe” of those games, also taking aesthetic references from Pinterest images. As I mentioned in my previous progress report, I drew up the idea on my iPad, then created the custom assets on Figma, such as the background image, boxes, buttons, logos, and so forth. The game I decided to create is called “CandyCollect”, where there are three different colored boxes, corresponding to a candy color. As the different colored candies are falling from the sky you have to move the corresponding box to catch the candy. You can switch the boxes by pressing the spacebar and the actively moveable box will glow yellow. You can then move the box by pressing down on the left and right arrow keys. You have a total of 30 seconds to catch as many candies as possible and depending on how you perform, you will be given different feedback from the game.

Custom Assets/Sketch
(on a real note, I actually ended up creating the candy shape with an ellipse and a rounded rectangle instead of using the image as I needed a shape haha.)

Code Highlights 

Glow effect on the box when active:

if (isActive) {
  for (let i = 0; i < 10; i++) {
    stroke(255, 255, 0, 255 - (i * 30)); 
    strokeWeight(4);
    noFill();
    
    // glow
    rect(this.x - i, this.y - i, this.width + 2 * i, 60 + 2 * i);
  }

I used a loop to create a glowing effect around the active box. The idea is pretty simple: I draw multiple rectangles on top of each other, each with a slightly lower opacity than the last. By tweaking stroke(255, 255, 0, 255 - (i * 30)), I make sure the glow fades out smoothly, giving the box a soft, dynamic highlight.

Collision detection mechanism:

let box = boxes.find((box) => box.colorName === candies[i].colorName);
if (
  candies[i].y + candies[i].size > box.y &&
  candies[i].x > box.x &&
  candies[i].x < box.x + box.width &&
  boxes[activeBoxIndex] === box
) {
  plop.play();
  score++;
  candies.splice(i, 1); // removing caught candy
}

Even though this snippet is pretty intuitive and short, I actually liked how simple it was to basically add new features to the candy collision. First, it uses boxes.find() to match the candy’s color with the correct box, and then it checks if the candy’s position falls within the box’s bounds. If everything lines up, the plop sound plays, the score goes up, and the candy disappears. Adding additional features as I went was made easy as it was handle largely just in this section.

Problems/Struggles

1. Active Box Collisions:

At first, the collisions were not that accurate. For example, even if a box wasn’t active, it would catch the candy and I didn’t want to leave that in as a bug/feature. In order to fix this, I simply added an additional check in the collision boxes[activeBoxIndex] === box detecting if statement.

2. Obstacles Spawning in Wrong Places:

There was this frustrating issue where multiple, and I mean MULTIPLE candies would spawn when the game is restarted, in a way that would be impossible for the user to catch. To fix this, I cleared the candy spawn interval if a previous interval still existed.

3. Audio and Performance Issues

Towards the end of the project, for some reason, some of my files (e.g. images, audios) would get corrupted, and I had a hard time making sure that everything was all accessible to the sketch.js. This was a relatively simple fix as I just double-checked in my assets folder whether all the necessary images and audios were available.

Here’s the demo:

Demo

Webster – Midterm Project

Introduction and Concept

Welcome to Webster! This game comes from an inspiration of a very dear friend, a pet actually. You know what they say ” Do not kill that spider in the corner of your room, it probably thinks you are it’s roommate.” I saw a spider in the corner of a room we do not usually enter in my house, and I called it Webster.

This project is a labor of love that brings together some really fun game design. The game uses solid physics to simulate gravity and rope mechanics, making our little spider swing through a cave that’s so high it even has clouds! I broke the project into clear, modular functions so every bit of the physics—from gravity pulling our spider down to the rope tension that keeps it swinging—is handled cleanly. This means the spider feels natural and responsive, just like it’s really hanging from a web in a bustling cave (maybe IRL a cave of clouds doesn’t exist but its oki)

On the design side, Webster is all about variety and challenge. The game dynamically spawns clouds, flies, and even bees as you progress, keeping the environment fresh and unpredictable. Randomized placements of these elements mean every playthrough feels unique, and the parallax background adds a nice touch of depth. Inspired by classic spider lore and a bit of Spiderman magic, the game makes sure you’re always on your toes—eating flies for points and avoiding bees like your life depends on it (well, Webster’s life does)

Enjoy swinging with Webster!

 

Sketch!

Code Highlights

// --- Physics-related vars & functions ---
// global vars for gravity, rope stiffness, rope rest length, rope anchor, and damping factor
let gravity, ropeK = 0.5, ropeRestLength, ropeAnchor, damping = 1;

function setup() {
  createCanvas(640, 480);
  gravity = createVector(0, 0.08); // sets constant downward acceleration
}

class Spider {
  constructor(x, y) {
    this.pos = createVector(x, y); // starting pos
    this.vel = createVector(0, 0);   // starting vel
    this.radius = 15;
    this.attached = false; // not attached initially
  }
  update() {
    this.vel.add(gravity); // apply gravity each frame
    if (this.attached && ropeAnchor) {
      let ropeVec = p5.Vector.sub(ropeAnchor, this.pos); // vector from spider to rope anchor
      let distance = ropeVec.mag(); // current rope length
      if (distance > ropeRestLength) { // if rope stretched beyond rest length
        let force = ropeVec.normalize().mult((distance - ropeRestLength) * ropeK); // calculate tension force
        this.vel.add(force); // apply rope tension to velocity
      }
    }
    this.vel.mult(damping); // simulate friction/air resistance
    this.pos.add(this.vel); // update position based on velocity
  }
}

This snippet centralizes all physics computations. Gravity is set as a constant downward acceleration in setup and then applied every frame in the Spider class’s update() method, which makes the spider to accelerate downwards. When attached to a rope, a corrective force is calculated if the rope exceeds its rest length, which simulates tension; damping is applied to slow velocity over time, which mimics friction or air resistance.

 

 

// --- Spawning Elements Functions ---
// spawnObstacles: checks spider's x pos and adds cloud obs if near last obs; random spacing & y pos
function spawnObstacles() {
  if (spider.pos.x + width - 50 > lastObstacleX) { // if spider near last obs, spawn new one
    let spacing = random(200, 500); // random gap for next obs
    let cloudY = random(height - 50 / 2, height + 1 / 2); // random vertical pos for cloud
    obstacles.push({ // add new cloud obs obj
      x: lastObstacleX + 500, // x pos offset from last obs
      y: cloudY,             // y pos of cloud
      w: random(80, 150),    // random width
      h: 20,                 // fixed height
      type: "cloud",         // obs type
      baseY: cloudY,         // store base y for wobble effect
      wobbleOffset: random(100000) // random wobble offset for animation
    });
    lastObstacleX += spacing; // update last obs x pos
  }
}

// spawnWorldElements: calls spawnObstacles then spawns collectibles (flies/webPower) and enemies (bees)
// based on frame count and random chance, spawning them ahead of spider for dynamic environment growth
function spawnWorldElements() {
  spawnObstacles(); // spawn cloud obs if needed
  
  if (frameCount % 60 === 0 && random() < 0.6) { // every 60 frames, chance to spawn collectible
    collectibles.push({
      x: spider.pos.x + random(width, width + 600), // spawn ahead of spider
      y: random(50, height + 500),                   // random vertical pos
      type: random() < 0.7 ? "fly" : "webPower"       // 70% fly, else webPower
    });
  }
  
  if (frameCount % 100 === 0 && random() < 0.7) { // every 100 frames, chance to spawn enemy
    enemies.push({
      x: spider.pos.x + random(width, width + 600), // spawn ahead of spider
      y: random(100, height + 500),                   // random vertical pos
      speed: random(2, 4)                           // random enemy speed
    });
  }
}

This snippet groups all spawning logic for environment elements. The spawnObstacles() function checks if the spider is near the last obstacle’s x coordinate and, if so, adds a new cloud obstacle with randomized spacing, vertical position, and dimensions. Then spawnWorldElements() calls this function and also adds collectibles and enemies (bees) ahead of the spider based on frame counts and random chances, to ensure a dynamic and everchanging environment.

Problems I faced (there were many)

There were quite a few quirky issues along the way. One problem was with collision detection—sometimes the spider would bounce off clouds a bit jitterily or not land smoothly, which made the swing feel less natural.  And then there was that pesky web projectile bug where it would linger or vanish unexpectedly if the input timing wasn’t just right, which threw off the feel of shooting a web.

Another area for improvement is enemy behavior. Bees, for example, sometimes weren’t as aggressive as I’d like like, so their collision detection could be sharpened to ramp up the challenge. I also ran into occasional delays in sound effects triggering properly—especially when multiple actions happened at once—which reminded me that asset management in p5.js can be a bit finicky.

Another hiccup was with the custom font and web projectile behavior. Initially, every character was coming out as a single letter because of font issues. When I changed the font extension from ttf to otf, it worked out for some reason.

I also had a lot of problem with the cloud spawning logic, sometimes they would spawn under the spider itself which prevents it from actually swinging as it wont gain any horizontal velocity, this was a PAIN to solve, because I tried every complicated approach which did not work, but the solution was simple, I only had to add a constant (which i chose to be 500) to the initial spawning x coordinates for the clouds. YES! it was that simple, but that part alone took me around 3 hours.

All in all, while Webster is a fun ride, these little details offer plenty of room to refine the game even further!

 

Week 6 – Midterm

Link to sketch: https://editor.p5js.org/bobbybobbb/sketches/7XBZCIX_C

My project is a calm flower shop experience where users can purchase flowers and make bouquets. Users have 100 dollars to spend and create a bouquet. Once they’ve spent $100 and checked out, they can restart the experience by leaving the store. You can also just leave the store whenever, but until you’ve checked out and spent $100, the experience will not reset.

Implementation

I drew all the elements (from the store front to the flowers) by hand to give it this homey and soft aesthetic. I also chose lo-fi cafe music to set this tone of relaxation. Feedback for actions users make was very important to me when designing this; I wanted them to know exactly how their actions affect the experience. For example, for the clickable objects in the store, hovering over them will create a white mask over them. Another feedback I implemented was the mouse click sound that occurs after the user clicks on something clickable. I also wanted the outside of the store to be inviting and encourage users to click on the screen; every time a user hovers over the screen, the doors to the shop will open, encouraging them to come in and use their mouse. Otherwise, it remains closed.

When users go to check out with a cart full of flowers, I display a bouquet full of the flowers they have. I had to think about how flowers are arranged in angles and randomly translated and rotated each flower to make it seem like they’re situated in a bouquet:

push();
// random positions and orientation of flowers
translate(random(170,240),random(140,210));
rotate(random(-PI/6,PI/6));
// display each flower inside the cart
image(flowerOptionsBouquet[cart[i]],0,0,200,200);
pop();

One thing I had to think about was how to make images clickable because all my elements are displayed using images. Instead of creating hit boxes and defining boundaries, I wanted to just let the images themselves be tracked as clickable things. That’s why I settled on using createImg() instead of the loadImage() and image() functions. createImg() does the same thing, except the image has .show() and .hide() functions for easily turning them on and off. They also have .mouseClicked(), which allows me to call a function once the images are clicked. The downfall of this system is that it doesn’t rely on draw; the images are constantly being displayed unless you hide them. Even if you call background() in draw, the canvas doesn’t reset. That’s why the bulk of my work is done in setup(), but still works and responds to mouse clicks from the user. This method also requires booleans to keep track of which scenes are being displayed so I can turn things on and off. 

doorOpen = createImg("door_open.png","alt");

versus:

redFlowerBouquet = loadImage("red_flower.png");

It was very important to me that I got all the mechanisms, clicking, switching between scenes before creating something beautiful, so here are some examples of what the early prototypes looked like before:I  didn’t  even  spell  flowers  right:

Improvements

One thing I noticed was just how long my file was; I feel like there’s a lot of lines for a fairly simple project. It might be due to the fact that I have to turn on and off image so often. Next time, I’ll try implementing hit boxes instead of hiding/showing images and see if that lends to simpler code. I’d also like to implement a feature where you can buy multiple bouquets at a time given the money you have and be able to display all the bouquets in your collection. As of right now, you can only buy one $100 bouquet, but users might want to split up that $100 and buy 2 or more bouquets.

Images

Inside the store:
Hovering over objects:
Example bouquets:

Midterm Project: Emotional Airport

Concept

For my midterm project, i created an interactive experience at the airport that is a bit different from the usual way we think about travel.  Instead of conventional destinations, the airport’s departure board offers emotional states that users might be seeking: peace, guidance, restart, sign, purpose, direction, and inspiration.

The idea behind this project came from this simple question:
“What if people travel not to visit a place, but to experience a certain emotion?”

Reflecting on my own travels, I realized that my strongest memories aren’t necessarily about locations themselves, but about how those places made me feel. So, once a user chooses their destination, they receive a quote/message they might need to hear.

Project features

In my project, there are three main pages:

  1. Welcome Page:

    • The user is greeted with a sky background and a welcoming message, all while having airport sounds in the background.
    • A “Start” button starts the experience.
  2. Airport Scene:

    • Once the user clicks “Start,” they are transported to an airport background with lofi sounds.
    • They need to click on the departure board to proceed.
  3. Departure Board Page:

    • This page focuses on the departure board, listing different emotional “destinations.”
    • Lofi music plays softly in the background to enhance the experience
    • Then, clicking on a specific “destination” triggers a pop-up message with a quote related to that emotion or the desired state a person wants to achieve.

Problems I ran into

Some ideas I initially planned like zoom-in features and extra interactive elements were difficult to make in the given timeframe. So i had to make the most essential features first.

So, the biggest challenge was finding suitable images online that matched the vision I had in my mind. So, i had to create some elements myself and manually edit the departure board to fit my concept and include the emotional destinations. I also combined multiple images to achieve the desired design for each stage of the experience.

Initially, I tried drawing the entire page from scratch, but I later realized that wouldn’t work well with the multiple pages interactive format I wanted.

Future improvements

If I were to expand this project, I would like to add animations to create smoother transitions between pages and implement a customized quote generator where i take input like the name of the user to make it feels personal.

Despite the challenges, I’m proud of how this project turned out. I put in a lot of time and effort to make it look as close as possible to what I imagined. By making this project, i could represent a concept I resonate with: travel as an emotional journey rather than just a physical one.

Link: https://editor.p5js.org/Ellina_Chermit/full/i1vywV_MT

Midterm Project – Dodge the Droppings!

Link to fullscreen sketch

Concept

The idea behind my game came from a personal incident, and is intended to be funny and inspired by our very own NYUAD campus. The game setting is such that the player is working under the palm trees outside C2 competing with time to complete their assignment due in 5 minutes. Birds above have started attacking the player, who now needs to dodge the droppings falling from above while at the same time finish their assignment as soon as possible. The game is an adaptation of the classic dodge-style game with an added game element. This added element is a progress bar that represents the player’s assignment progress and it gets filled upon the repeated pressing of the space bar. The main goal is to fill up the progress bar (the win condition), while dodging the droppings falling from above without getting hit by any (getting hit is the lose condition).

Game components

The sketch starts by displaying a home page with instructions on how to play the game. Upon pressing the “Enter” button, the game starts. The images of the keyboard buttons on the instructions are taken from flaticon.com.

The main game screen consists of four elements:

  1. bird
  2. dropping
  3. computer
  4. progress bar (yellow long rectangle on the right).

Bird poop (dropping) falls from where the birds are located at the top of the screen, at regular intervals across random columns in different speeds. The computer is situated at the bottom of the screen and can be moved horizontally to different columns using the left and right keyboard buttons. When the space bar is pressed, the progress bar fills up (red indicates the level of assignment progress). At the top right, there is a timer that shows how much time has elapsed in seconds, and the best time  – which is the lowest time taken to complete the assignment, since the goal is to finish the assignment as quickly as possible without getting hit by a dropping – will be tracked across game plays and displayed on the home page.

I drew the images used for the bird, dropping and computer on Procreate and used a background remover application to make the background transparent. The image in the game background with the palm trees is taken from NYUAD’s website.

game screen
screen shown when the assignment is complete
screen shown when hit by / collided with a dropping
home page with the best time displayed

Implementation

Each of the four main elements (bird, dropping, computer, progress bar) are implemented as a class and have their respective functions, including display, fall, move, fill etc. There is also a Game class that manages game state variables, starts/resets the game and checks for collision between computer and the droppings.

One of the design decisions I made was to divide the canvas into (invisible) columns and have the computer move one column at a time instead of moving continuously, and droppings fall randomly from one of the columns instead of from any random pixel. This way, the user can’t “cheat” by placing the computer at a location where droppings rarely fall and it has to constantly dodge droppings, which I personally felt made the game a bit more challenging and fun.

class Computer {
  constructor() {
    this.x = 0;
    this.y = 480;
    this.alive = true;
    // initially static
    this.direction = 0;
    // place the computer at a random column
    this.col = int(random(0, numCols));
  }
  
  move(dir) {
    // if the right arrow is pressed
    if (dir === 1) {
      // numCols - 1 so that the last column is not accessible
      // last column space is reserved for placing the progress bar
      if (computer.col < numCols - 1) {
        computer.col++;
      }
    // left arrow is pressed
    } else {
      if (computer.col > 0) {
        computer.col--;
      }
    }
  }
  
  display() {
    this.x = colWidth * this.col + leftMargin;
    image(computerImg, this.x, this.y, computerWidth, computerHeight);
  }
}

A new dropping is created for every 1/3 second and its column and speed is randomly chosen. I settled on 1/3 second (frameCount % 20 === 0) among other values to make the game just challenging enough without frustrating the user by bombarding them with too many obstacles.

// create a new dropping 
if (frameCount % 20 === 0) {
  droppings.push(new Dropping());
}

for (let i = droppings.length - 1; i >= 0; i--) {
  let dropping = droppings[i];
  
  // check if any dropping has collided with the computer
  if (this.checkCollision(dropping, computer)) {
    // play sound effect
    blopSound.play();
    // remove the dropping from the array
    droppings.splice(i, 1);
    gameOverFail = true;
    break;
  } else {
    dropping.fall();
    dropping.display();
  }
  
  // remove droppings that have went beyond canvas
  if (dropping.y > height) {
    droppings.splice(i, 1);
  }
}

Problems I ran into

One of the biggest challenges was managing the state of the game such that the game can be restarted and the correct screens based on whether the game is in process, lost or won is shown without fail. I was able to do this by keeping track of three boolean variables:

let gameStarted = false;
let gameOverSuccess = false;
let gameOverFail = false;

...

// switch screens
if (!gameStarted) {
  instructions.display();
} else {
  game.run();
}

if (gameOverSuccess) {
  ...
} else if (gameOverFail) {
  ...
}

I track these variables in the draw() function and change what is shown on screen should any of these variables are modified.

Checking for collision between the computer and droppings was not very challenging as I implemented it as a simple function that checked whether any dropping overlaps in any direction with the computer, but it required configuring of values to ensure that collision was detected when the two appear to touch each other to the visible eye (instead of being triggered as soon as they barely touch) . As I had already tested creating a progress bar that fills upon a key press in the previous week as part of my midterm progress, incorporating it into the game took little time.

Future improvements

I quite like how the game turned out, both in terms of visuals and playing experience. However, one thought that I kept having was how nice it would be to be able to choose the difficulty of the game. I didn’t want to make the game too challenging so I settled on values for the speed and frequency of obstacles that would cater to the general audience, but this means that for certain groups of people who enjoy playing games, my game may appear very trivial. Therefore, one of the main improvements I would be interested in making is creating multiple degrees of difficulty (easy, medium, hard) that the user can choose from. I think this would make the game more engaging and potentially have users come back to the game to succeed in each round, instead of just attempting once.

Midterm concept and outline.

Initial concept

I initially wanted to make a top-down shooter game, I designed a lot of stuff, a collision algorithm for the bullets, obstacles, and enemies, a multitude of weapons with special traits, power ups, an infinitely-expanding map, and a bunch of other interactive elements. I got really frustrated with the implementation and gave up on the idea. I came back a day later and didn’t know whether I should abandon the game or not, so I changed to code to make it a horizontal shooter game,and  below is where i reached, before completetly abandoning the idea, even though I had almost all the logic and algorithms implemented, I just couldn’t work with a project a didn’t love.

Hence the delay of submitting this assignment. 

Current concept

I woke up 2 days after, I saw a spider on top in some corner no one goes to in my house with a small net. I decided it to befriend it and I called it Webster.

This sparked an idea in me, which is to make a game of a spider swinging around the world with a web. Then I drew my initial concept.

The most frightening part

This would probably be the implementation of gravity and the physics of the web/rope. I already implemented them though.

class Spider {
  constructor(x, y) {
    // store position & velocity in p5 vectors
    this.pos = createVector(x, y);
    this.vel = createVector(0, 0);
    // spider radius = 15 is used for collisions
    this.radius = 15;
    // track if spider is attached to rope or not
    this.attached = false;
  }

  update() {
    // apply gravity each frame
    this.vel.add(gravity);

    // if spider is attached, we do some rope physics
    if (this.attached && ropeAnchor) {
      // figure out how far spider is from anchor
      let ropeVec = p5.Vector.sub(ropeAnchor, this.pos);
      let distance = ropeVec.mag();

      // only if rope is stretched beyond rest length do we apply the spring force
      if (distance > ropeRestLength) {
        let stretch = distance - ropeRestLength;
        // hooke's law, f = k * x
        let force = ropeVec.normalize().mult(stretch * ropeK);
        // add that force to our velocity
        this.vel.add(force);
      }
    }

    // apply damping (which is basically air resistance)
    this.vel.mult(damping);

    // move spider according to velocity
    this.pos.add(this.vel);
  }

  show() {
    // draw the spider sprite instead of a circle
    push();
    imageMode(CENTER);
    image(spiderImg, this.pos.x, this.pos.y, this.radius * 2, this.radius * 2);
    pop();
  }
}