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-gardens of butterflies

For this midterm project I chose to create a game of a garden filled with butterflies, nature-inspired environment where colorful butterflies flutter randomly across the screen. I used the spacebar to control a net that follows the cursor, to catch the butterflies making the gameplay intuitive and engaging.

The goal of the game is to catch as many butterflies as possible . Each butterfly has different colors and sizes, . I also added gentle ambient sounds, like a net sound, to create a soothing experience.

in conclusion , I aimed to create a beautiful digital garden that tests hand-eye coordination while offering a calming escape.

Future Improvements:
I believe the game could be further enhanced by adding different levels of difficulty. For example, introducing obstacles or making some butterflies faster and more evasive would add a layer of challenge. I also think that implementing a scoring system with a timer could increase replayability. Another idea is to introduce special butterflies that grant power-ups, like slowing down all butterflies or extending the catch radius of the net. Additionally, I would explore adding dynamic weather effects, such as changing skies or light rain, to create a more immersive environment. Ultimately, my goal is to refine the game by incorporating feedback and exploring new ways to engage players through both gameplay and visuals.

code im proud of:

class GameMenuDisplay {
    show() {
        butterflyMenu.move();
        butterflyMenu.show();

        noStroke();
        fill(255, 255, 255, 200);
        rect(420, 20, 360, 560);

        // How To Play Text
        textSize(32);
        textAlign(CENTER, CENTER);
        fill(0);
        text("Catch The Butterfly", 600, 60);
        
        textSize(20);
        text("Game Instruction: ", 600, 220);
        
        textSize(14);
        text("Move the net with your mouse and\npress SPACE BAR to catch the butterflies.", 600, 280);

        textAlign(CENTER, CENTER);
        text("Click anywhere to start the game.", 600, 520);
    }
}

 

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

Midterm Project – The Cookie Game!

For my midterm project, I decided to channel my love for baking and code a game for baking cookies!

Link to the full screen sketch: https://editor.p5js.org/lr3258/full/e-z5Ur1g3

Concept :

This is a simple game to bake chocolate chip cookies, very direct and fun! The player drags ingredients into a bowl to create the dough, which then bakes into delicious chocolate chip cookies! I wanted it to have a very comfy and cozy vibe, so I chose a colour palette of baby blues and browns. To make the entire game more aesthetically cohesive, I drew the ingredients on Autodesk Sketchbook and made the layouts on Canva. I also drew the egg –> dough sprite sheet since I couldn’t find any suitable ones online.

How it works:

The game isn’t meant to be challenging or competitive, it is more inclined to the process and joy of baking the cookies than being a “game” itself.

First, the user has to click on any key to start the game:

Next, the player has to drag the ingredients into the bowl. Here, when the egg is clicked on, it triggers the animation to display the dough. We can then press the Next button.

After this, we have to wait for 5 seconds, while the cookies “bake” in the oven. After 5 seconds, the screen automatically shifts with a Ding! sound.

Then, the final screen shows the baked cookies! We can then restart the game.

highlights of the code i’m proud of

A part of the game I’m pretty proud of is the sprite sheet animation. It took a lot of time to draw, create and then animate the sprite sheet. It was also the hardest part, and there was a LOT of debugging involved, but in the end I’m happy with how it turned out, even with the ingredients being dragged into the bowl. I’m also very happy with the aesthetic of the game, which really feels like home to me.

class Ingredient {
constructor(name, x, y, img, isEgg) {
    this.name = name;
    this.x = x;
    this.y = y;
    this.img = img;
    this.isEgg = isEgg;
    this.isDragging = false;
    this.offsetX = 0;
    this.offsetY = 0;
    this.isDropped = false;
    this.currentFrame = 0; 
    this.numFrames = 5; 
    this.frameWidth = 150;
    this.frameHeight = 150;
    this.isCracked = false;
    this.frameTimer = 0;
    this.frameSpeed = 6;  
    this.originalX = x;  
    this.originalY = y;  
  }
  
  display() {
    
    if (!this.isDropped && !this.isEgg && bowl.isIngredientInBowl(this)) {
  this.isDropped = true;
  this.x = bowl.x;
  this.y = bowl.y;
}
    
    if (this.isEgg) {
      let sx = this.currentFrame * this.frameWidth;
      image(this.img, this.x, this.y, this.frameWidth, this.frameHeight, sx, 0, this.frameWidth, this.frameHeight);
    } else if (!this.isDropped) {
      image(this.img, this.x, this.y);
    }
  }

  update() {
    if (this.isDragging) {
      this.x = mouseX + this.offsetX;
      this.y = mouseY + this.offsetY;
    }
    
    //dropping ingredient into bowl
    if (!this.isDropped && !this.isEgg && bowl.isIngredientInBowl(this)) {
      this.isDropped = true;
      this.x = bowl.x; 
      this.y = bowl.y;
    }

    // animate the egg spritesheet if it's clicked
    if (this.isEgg && this.isCracked) {
      this.frameTimer++;
      if (this.frameTimer >= this.frameSpeed) {
        this.frameTimer = 0;  // to reset timer
      if (this.currentFrame < this.numFrames - 1) {
          this.currentFrame++;  
        }
      }
    }
  }

  checkDragging() {
    if (
      mouseX > this.x &&
      mouseX < this.x + this.img.width &&
      mouseY > this.y &&
      mouseY < this.y + this.img.height &&
      !this.isDropped
    ) {
      this.isDragging = true;
      this.offsetX = this.x - mouseX;
      this.offsetY = this.y - mouseY;
    }
  }

  checkClicked() {
    if (
      mouseX > this.x &&
      mouseX < this.x + this.img.width &&
      mouseY > this.y &&
      mouseY < this.y + this.img.height &&
      !this.isCracked
    ) {
      this.isCracked = true; //start the animation of egg cracking
      this.currentFrame = 0;
    }
  }

 

challenges and improvements:

When it came to challenges, the main concern was the sprite sheet itself. It was also hard to transition from different states smoothly, but I implemented the concept of state machine we discussed in class, and it went pretty well. In the end, the final problem I had was the autoplay of the background audio, which wasn’t working in the full screen version. For this, I used the p5js built in code, userStartAudio(), which starts the audio on click. Implementing the restart button at the end was also a bit tricky, but overall, I’m pleased with the final result.

If I could make some improvements, I’d add some extra elements, like a timer, or maybe animations for the baking time. I would also like to change the design of the buttons, which are currently very basic in contrast to the rest of the game.

Embedded sketch :

Midterm Project: Interactive Fantasy Game

For this project, I decided to transform a short fantasy story I wrote in primary school into an interactive game using p5.js. This game is played by a single player who guides the protagonist, Eden, on her quest to slay the dargon and save her village. The game has multiple choices that affect the storyline, but in the end, all paths lead to the same outcome. There are a total of 5 key decision points and 14 different screens in the game.

How it works:

The game’s structure is built around a series of screens, each representing a different part of Eden’s journey. The transitions between screens are managed efficiently through the mousePressed() function and buttons. The game utilizes object-oriented programming (OOP) for button management.

Proud Achievements and Technical Decisions
One of the most significant achievements in this project is the implementation of the OOP approach for button management. Initially, built-in functions were used, but switching to OOP proved to be a more flexible and maintainable solution. This decision allows for easier customization of button behavior and appearance across different screens.

Another achievment is the overall structure of thegame, with its multiple screens and decision points. This took a long time and required  careful planning and implementation to ensure smooth transitions and logical flow of the story.

The integration of background images and music also adds to the immersive experience of the game. These elements help bring the fantasy world of EverLand to life for the player.

The buttons code:

//Creating the buttons class
class GameButton {
  //initializing the state of the button
  constructor(label) {
    this.label = label;
    this.x = 0;
    this.y = 0;
    this.width = 105;
    this.height = 30;
    this.visible = false;
  }
  //method to show button at specified coordinates
  show(x, y) {
    this.x = x;
    this.y = y;
    this.visible = true;
    fill("white");
    noStroke();
    rect(this.x, this.y, this.width, this.height);
    fill("black");
    textSize(13);
    textAlign(CENTER, CENTER);
    text(this.label, this.x + this.width / 2, this.y + this.height / 2);
    fill("white") //so trhat the text itself is white
    textSize(15); //so that the text itself has a size of 15
  }
  //method to hide button
  hide() {
    this.visible = false;
  }

  //method to identify if mouse is hovering over button
  isMouseOver() {
    return (
      this.visible &&
      mouseX > this.x &&
      mouseX < this.x + this.width &&
      mouseY > this.y &&
      mouseY < this.y + this.height
    );
  }
}

The screens code:

function draw() {
  //calling the function that displays the screen based on the screen assignment which happens when the mouse is pressed
  if (screen === 0) {
    showStartScreen();
  } else if (screen === 1) {
    showBirthdayScreen();
  } else if (screen === 11) {
    showSuppliesScreen();
  } else if (screen === 12) {
    showWeaponScreen();
  } else if (screen === 111 || screen === 121) {
    showNightScreen();
  } else if (screen === 112 || screen === 122) {
    showMorningScreen();
  } else if (screen === 1111 || screen === 1121 || screen === 1211 || screen === 1221) {
    showRiverScreen();
  } else if (screen === 1112 || screen === 1122 || screen === 1212 || screen === 1222) {
    showForestScreen();
  } else if (screen === 11000 || screen === 12000 || screen === 21000 || screen === 22000) {
    showNextScreen();
  } else if (screen === 5000) {
    showDragonCaveScreen();
  } else if (screen === 5001) {
    showInsideScreen();
  } else if (screen === 5002) {
    showOutsideScreen();
  } else if (screen === 5003) {
    showTrapScreen();
  } else if (screen === 262626) {
    showFinalScreen();
  }
}

//function to hide all buttons which i will use when switching screens so that the previous buttons don't appear on the screen
function hideAllButtons() {
  enterButton.hide();
  suppliesButton.hide();
  weaponButton.hide();
  nightButton.hide();
  morningButton.hide();
  riverButton.hide();
  forestButton.hide();
  fishButton.hide();
  riverspiritsButton.hide();
  next1Button.hide();
  insideButton.hide();
  outsideButton.hide();
  trapButton.hide();
  next2Button.hide();
  forestspiritsButton.hide();
  firefliesButton.hide();
  playButton.hide();
  quitButton.hide();
}

//assigns screen with next number based on the previous screen and the button pressed and also hides all buttons so that they aren't layered on top of one another
function mousePressed() {
  if (screen === 0 && enterButton.isMouseOver()) {
    screen = 1;
    hideAllButtons();
  } else if (screen === 1) {
    if (suppliesButton.isMouseOver()) {
      screen = 11;
      hideAllButtons();
    }else if (weaponButton.isMouseOver()) {
      screen = 12;
      hideAllButtons();
    }
  } else if (screen === 11 || screen === 12) {
    if (nightButton.isMouseOver()) {
      screen = screen * 10 + 1;
      hideAllButtons();
    }else if (morningButton.isMouseOver()) {
      screen = screen * 10 + 2;
      hideAllButtons();
    }
  }else if (screen === 111 || screen === 112 || screen === 121 || screen === 122) {
    if (riverButton.isMouseOver()) {
      screen = screen * 10 + 1;
      hideAllButtons();
    } else if (forestButton.isMouseOver()) {
      screen = screen * 10 + 2;
      hideAllButtons();
    }
  } else if (screen === 1111 || screen === 1121 || screen === 1211 || screen === 1221) {
    if (fishButton.isMouseOver()) {
      screen = 11000;
      hideAllButtons();
    } else if (riverspiritsButton.isMouseOver()) {
      screen = 12000;
      hideAllButtons();
    }
  } else if (screen === 1112 || screen === 1122 || screen === 1212 || screen === 1222) {
    if (firefliesButton.isMouseOver()) {
      screen = 21000;
      hideAllButtons();
    } else if (forestspiritsButton.isMouseOver()) {
      screen = 22000;
      hideAllButtons();
    }
  } else if (screen === 11000 || screen === 12000 || screen === 21000 || screen === 22000) {
    if (next1Button.isMouseOver()) {
      screen = 5000;
      hideAllButtons();
    }
  } else if (screen === 5000) {
    if (insideButton.isMouseOver()) {
      screen = 5001;
      hideAllButtons();
    } else if (outsideButton.isMouseOver()) {
      screen = 5002;
      hideAllButtons();
    } else if (trapButton.isMouseOver()) {
      screen = 5003;
      hideAllButtons();
    }
  } else if (screen === 5001 || screen === 5002 || screen === 5003) {
    if (next2Button.isMouseOver()) {
      screen = 262626;
      hideAllButtons();
    }
  } else if (screen === 262626) {
    if (playButton.isMouseOver()) {
      restartGame();
    } else if (quitButton.isMouseOver()) {
      quitGame();
    }
  }
}

Areas for Improvement:
While the current version of the game is functional, there are several areas for potential improvement:

Expanding the storyline: Adding more options and choices would increase replayability and make the game more interactive. For example, allowing players to pick specific weapons or have more detailed interactions with characters like the spirits could add depth to the game.

Enhanced interactivity: Implementing features like dialogue systems for conversations with spirits or other characters could make the game more engaging. This could involve creating a more complex dialogue management system.

Challenges:

One of the main challenges faced was transitioning from built-in button functions to the OOP approach. This required refactoring a significant portion of the code and it would’ve been so much easier if I just started with OOP.

Moving forward, the focus will be on enriching the game content and enhancing the player’s ability to influence the story through their choices.

The game sketch:

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!

 

Midterm Project – Sweet Treats

For my midterm project, I decided to create a memory game. Given my unhelpable sweet tooth, I based my game around a bakery. I was partly inspired by the game Cooking Mama, which was a game on the Nintendo DS that I played frequently. My game involves the user picking a recipe, then having to memorise and select the right ingredients for that recipe within the given timeframe. The player is then “rewarded” with the dish if they are successful.

I wanted my game to have a cohesive theme to it, so I ended up creating all the graphics myself. This was by far one of the most time consuming aspects, as I was my own worst critic when it came to the quality of the images. Overall though, I am very satisfied with the way the graphics came out; I think they give the game a more polished and homey look.

One aspect I am particularly proud of is the Game class. Taking Professor Mang’s advice, I implemented a state machine to make the flow of the game easier to manage, as my game consisted of multiple “stages” that it would need to flow through in order to work coherently. This helped organise the code into more manageable sections, rather than a mess of code I’d need to sort through if I needed to change anything.

display() {
    background(255); // Set background to white

    if (this.state === "selection") {
      image(shelfImage, 0, 0, width, height); // Display shelf as background
    }

    textAlign(CENTER, CENTER);
    textFont(font);

    this.allIngredients.forEach((ingredient) => ingredient.display());

    stroke(0);
    strokeWeight(1);
    fill(0);

    if (this.state === "start") {
      image(titleImage, 0, 0, width, height); // Display title screen background image
      textSize(48);
      text("Sweet Treats Bakery", width / 2, height / 2 - 50); // Title Text
      textSize(32);
      text("Click to Start", width / 2, height / 2 + 50); // Subtitle Text
    } else if (this.state === "menu") {
      image(menuImage, 0, 0, width, height); // Display menu screen background image
      fill("#332211");
      textSize(64);
      text("MENU", width / 2, 100);

      buttons.forEach((button) => button.display());
    } else if (this.state === "memory") {
      image(recipeImage, 0, 0, width, height); // Display recipe image as background
      textSize(32);
      text("Memorize These Ingredients!", width / 2, 80);
      textFont(honeySaltFont);
      textSize(35);
      this.correctIngredients.forEach((ing, i) =>
        text(ing, width / 2, 300 + i * 50)
      );

      if (millis() - this.memoryTimer > 3000) {
        this.state = "selection";
        this.selectionTimer = millis();
      }
    } else if (this.state === "selection") {
      textSize(32);
      text("Select the Ingredients!", width / 2, 50);
      textSize(24);
      text(
        `Time left: ${Math.max(
          0,
          5 - floor((millis() - this.selectionTimer) / 1000)
        )}s`,
        width / 2,
        110
      );

      if (millis() - this.selectionTimer > 5000) {
        this.state = "baking";
        this.bakingTimer = millis();
      }
    } else if (this.state === "baking") {
      let elapsedTime = millis() - this.bakingTimer;
      let index = floor(elapsedTime / 1500); // Change image every 1.5 seconds

      index = min(index, 2);

      // Play ticking sound only when baking starts
      if (!tickingSound.isPlaying()) {
        tickingSound.loop();
        tickingSound.setVolume(0.8);
      }

      // Display the oven images
      image(ovenImages[index], 0, 0, width, height);

      textSize(32);
      fill("#332211");
      text("Baking...", width / 2, height / 2);

      // Stop ticking and move to result after 4.5 seconds
      if (elapsedTime > 4500) {
        this.state = "result";
        tickingSound.stop();
      }
    } else if (this.state === "result") {
      image(finalImage, 0, 0, width, height); // Display title screen background image
      textSize(40);
      if (this.checkWin()) {
        text(`Your ${this.selectedDish} is now ready!`, width / 2, 200);
        image(
          dishImages[this.selectedDish],
          width / 2 - dishImages[this.selectedDish].width / 2,
          300
        );
      } else {
        text("Oh no! Those were the wrong ingredients.", width / 2, height / 2);
      }
      textSize(20);
      text("Click to Play Again", width / 2, height / 2 + 250);
    }
  }

  handleClick() {
    if (this.state === "start") {
      this.state = "menu";
    } else if (this.state === "menu") {
      buttons.forEach((button) => {
        if (button.checkClick(mouseX, mouseY)) {
          this.selectedDish = button.label;
          this.correctIngredients = {
            // Storing the correct ingredients of the recipes
            Brownie: [
              "Flour",
              "Sugar",
              "Eggs",
              "Butter",
              "Baking Powder",
              "Chocolate",
            ],
            Cupcake: ["Flour", "Eggs", "Sugar", "Milk", "Vanilla"],
            Pie: ["Flour", "Sugar", "Eggs", "Butter", "Apples"],
          }[this.selectedDish];

          this.memoryTimer = millis();
          this.state = "memory";
        }
      });
    } else if (this.state === "selection") {
      this.allIngredients.forEach((ingredient) => {
        if (ingredient.checkClick(mouseX, mouseY)) {
          ingredient.select();
        }
      });
    } else if (this.state === "result") {
      this.resetGame();
    }
  }

The design of the game is fairly simple in concept, though I still struggled when making the intial base of the code. I had started by creating a prototype version, which I experimented on until I was satisfied with the game flow. That version was plain, with no graphics, and circles used as placeholders for the actual ingredients.

I like to think the final game looks better than this.

Not to say I didn’t struggle even after adjusting the prototype. While inserting the backgrounds and graphics, I realised that I had just simply hardcoded values for the recipes on the recipe selection screen, and that I should use buttons instead in my final code.


Making these buttons turned out to be challenging, as I had to make another class for them. I kept facing an error that would not let me adjust the y and x coordinates of the buttons, leading them to overlap. Several lines of code carefully inspected later, I found the error within the display function of the Game class, which was simultaneously calling the buttons alongside the original button class, leading to the code refreshing everytime to use the wrong coordinates for the buttons. After that hiccup, it was mostly smooth sailing.

I also implemented background music (see if you can recognise the instrumental) and a sound effect to my game. The sound effect is a ticking sound that plays for the duration of the “baking…” scene, which I thought would add a nice little touch to make the game a little more interactive.

Overall, I am very satisfied with my midterm project, and it turned out very close to what I had originally envisioned it to be, making the end product all the more rewarding to see.

Link to full screen version


 

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 – Interactive Party

My project is an interactive music visualizer designed to create a party-like experience. It integrates sound, visuals, and interactivity to simulate a dynamic party environment. The motivation behind this project stems from my love for music and aesthetics, aiming to bring both together in a visually engaging way. The experience features different sound modes, including a salsa mode, and reacts dynamically to audio amplitude to enhance the party atmosphere.

Link to Fullscreen:https://editor.p5js.org/Genesisreyes/full/okpJ6jCqf

It features a background DJ image, a GIF animation for the salsa mode, and dynamically changing colors and light effects. OOP is implemented in the LiquidShape class, which generates butterfly-like abstract shapes that move and react to the music’s amplitude. The project also includes button interactions, allowing users to start the party, switch to salsa mode, and switch between songs by clicking, or return to the main page. The use of p5.Amplitude() ensures that elements like background flashing and shape movements are synchronized with the audio intensity, creating a smooth experience.

Key Highlights

1. Object-Oriented Programming (OOP) for Animation

The LiquidShape class is responsible for creating and animating butterfly-like figures that move fluidly across the screen. These shapes:

    • Respond to the music amplitude by changing motion and size.
    • Have two states: floating (trapped) and moving (liberated), which the user can toggle by clicking.
    • Implement procedural animation using noise-based randomization to ensure organic movement.

2. Audio Amplitude Analysis for Visual Synchronization

The p5.Amplitude() function is used to analyze the loudness of the currently playing audio and link it to the visual elements. The butterfly image pulses with the music, making it feel more immersive.

let level = amplitude.getLevel();

let pulseSize = lerp(width * 0.2, width * 0.4, level);
image(butterflyBg, 0, 0, pulseSize, pulseSize);

How this works:

    • amplitude.getLevel() returns a value between 0 (silence) and 1 (loudest part of the track).
    • lerp() smoothly transitions the butterfly image size between 20% and 40% of the screen width based on the music volume.
    • This creates a natural pulsing effect that visually represents the beat.

3. Smooth Audio Transitions Between Tracks

Instead of abruptly switching sounds,  fade-out and fade-in effects were used when transitioning between songs to create a smoother experience.

function mousePressed() {
  if (!started) return;

  let fadeDuration = 1.5; // Fade out in 1.5 seconds
  if (sounds[currentSoundIndex].isPlaying()) {
    sounds[currentSoundIndex].setVolume(0, fadeDuration);
    setTimeout(() => {
      sounds[currentSoundIndex].stop();
      switchTrack();
    }, fadeDuration * 1000);
  } else {
    switchTrack();
  }
}
    • The current song fades out smoothly over 1.5 seconds.
    • After fading, it stops completely and switches to the next track.
    • The new track fades in, avoiding harsh audio jumps.

A major challenge was working with WEBGL, especially in handling 2D elements like buttons alongside 3D rendering. The solution involved creating a separate graphics layer (bgGraphics) for the background image.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight); // Resize the WEBGL canvas
  bgGraphics.resizeCanvas(windowWidth, windowHeight); // Resize 2D graphics buffer
  updateBackground(); // Redraw the background image
}

function updateBackground() {
  bgGraphics.background(0); // Fallback if the image doesn't load

  let imgWidth, imgHeight;
  let canvasAspect = windowWidth / windowHeight;
  let imgAspect = djImage.width / djImage.height;

  if (canvasAspect > imgAspect) {
    imgWidth = windowWidth;
    imgHeight = windowWidth / imgAspect;
  } else {
    imgHeight = windowHeight;
    imgWidth = windowHeight * imgAspect;
  }

  bgGraphics.imageMode(CENTER);
  bgGraphics.image(djImage, windowWidth / 2, windowHeight / 2, imgWidth, imgHeight);
}

One of the best aspects of the project is its ability to capture the energy of a party through interactive visuals and sound. The combination of image overlays, and reactive shapes enhances the user’s engagement.

There are areas that could be improved. One issue is optimizing the responsiveness of visual elements when resizing the window, as some components may not scale perfectly. Another challenge was ensuring seamless audio transitions between different sound modes without abrupt stops. Additionally, adding clear instructions for the users would have been ideal, so they could know that when they click, they can change the music but also collect and liberate the butterflies.