Full Sketch: https://editor.p5js.org/jheel2006/full/zaxMyymUX
Inspiration
When I first started brainstorming this project, I was drawn to the idea of creating something related to pet care. I’ve always thought of pets as a source of joy and responsibility, so I thought a virtual pet could be a fun and meaningful experience. However, I wanted to add a fun twist to the concept. I thought it might be interesting and humorous to make the pet something unexpected, like an inanimate object. This was how Rocky the Pet Rock was born!
I drew and designed Rocky myself, with five distinct mood states: Very Happy, Slightly Happy, Meh, Slightly Sad, and Very Sad. These moods are influenced by the player’s actions, which determine Rocky’s overall well-being. By making Rocky a rock instead of a living creature, I could play with the idea of what it means to care for something that doesn’t typically need attention, and how even the simplest interactions can create an emotional connection. The result is an experience that feels both playful and oddly meaningful.
Concept
The concept behind Rocky the Pet Rock is to create a simple, interactive experience where you take care of a virtual pet rock for a short period of time. Rocky’s mood is initially set to 50 (out of a total of 100). The game has a timer of 100 seconds until which your goal is to keep Rocky as happy as possible.
The game begins with an introduction screen after which the user can reach the instructions screen by clicking anywhere. Clicking on the instructions screen leads to the main game screen.
After reaching the main game screen, there are three ways to change Rocky’s mood:
Feed: The user can choose from five different types of food to feed Rocky. However, he may or may not like the food that is chosen. If he likes the food, his mood goes up, if he doesn’t like the food, his mood goes down and if he is okay with/indifferent to the food, his mood remains unchanged. These food preferences are randomized in every round of the game. Rocky can only be fed twice during the game.
Play: Here, the user can help Rocky play his favorite game – dodgeball. In this mode, balls keep getting generated from the right side of the screen at Rocky’s current position and the user must use the up and down arrow keys to move Rocky and avoid these balls. A ball hitting Rocky results in a mood decrease and playing with Rocky without hitting any obstacles leads to a consistent mood increase.
Relax: In this mode, the user helps Rocky unwind by choosing between three different music tracks (of different genres) to play. Similar to the feed option, Rocky likes one of these tracks, doesn’t like another and is okay with the third. Choosing any of these track affects his mood depending on his preferences (randomized in every round of the game). The relax option can only be chosen once during the game.
The game ends if the timer reaches 0 or if Rocky’s mood decreases to 0. The game then displays game over screen, allowing the user to begin again by clicking on the screen. The game over screen displays Rocky’s final mood with a message. If the user restarts the game, it resets to the original conditions and begins again.
Implementation:
The implementation of the game is built around the three core modes of interaction: Feed, Play, and Relax. Each one of these had its own set of challenges to implement and integrate into the game as a whole.
- One aspect of the Feed mode that I’m particularly proud of is the way the food options are arranged around Rocky. I wanted the interaction to be visually appealing, so I placed the five food choices in a circular pattern around Rocky, making the user’s selections feel more natural and connected to the character.
Randomizing Rocky’s food preferences in each round was another key part of the implementation. Every time the game begins, Rocky likes, dislikes, or feels neutral about different foods, keeping the player on their toes and making each round feel unique. Once the player makes a selection, feedback is immediately displayed on the main screen, showing how Rocky’s mood is affected. It was also essential to disable the feed option after Rocky has been fed twice to prevent over-feeding and keep the game balanced. Implementing this functionality added a strategic element to the game, as players must choose carefully when and what to feed Rocky, knowing their options are limited.Here’s some of the feed functionality code!
function displayFoods() { // Display Rocky at the center rocky.display(); let centerX = rocky.x - rocky.size/8; // Center of the screen (same as Rocky's x) let centerY = rocky.y - rocky.size/8; // Center of the screen (same as Rocky's y) let radius = 170; // Distance from the center to where the foods will be displayed let angleStep = TWO_PI / foods.length; // Angle between each food item for (let i = 0; i < foods.length; i++) { let angle = i * angleStep; // Calculate angle for each food item let foodX = centerX + radius * cos(angle); // X-coordinate based on angle let foodY = centerY + radius * sin(angle); // Y-coordinate based on angle image(foodImages[i], foodX - 40, foodY - 40, 110, 110); // Display food images fill(0); textAlign(CENTER); // Center the text below each food item text(foods[i], foodX+20, foodY + 80); // Display food names under the images }
function setFoodFeedback(food) { let moodEffect = foodPreferences[food]; rocky.mood += moodEffect; // Update Rocky's mood immediately // Set food feedback based on mood effect if (moodEffect > 0) { foodFeedback = `Rocky likes ${food}!`; } else if (moodEffect < 0) { foodFeedback = `Rocky doesn't like ${food}!`; } else { foodFeedback = `Rocky is okay with ${food}.`; } // Clear any previous timeout to avoid overlap if (foodFeedbackTimeout) { clearTimeout(foodFeedbackTimeout); } // Clear the feedback after 3 seconds foodFeedbackTimeout = setTimeout(() => { foodFeedback = ""; }, 3000); }
- The Play mode was an exciting challenge to develop, especially with the random generation of obstacles. I designed the game so that obstacles are generated from the right side of the screen based on Rocky’s current position, ensuring that the game feels dynamic and responsive to the player’s movements. This adds an element of unpredictability, requiring the player to stay alert as they guide Rocky to dodge incoming obstacles.
I liked the idea of how Rocky’s mood was closely tied to how well the player performs during the game. As long as Rocky successfully avoids the obstacles, his mood gradually increases, rewarding careful play. However, if Rocky gets hit by an obstacle, his mood takes an immediate hit.. This balance between mood progression and obstacle avoidance makes the Play mode both engaging and meaningful, as it directly ties the player’s performance to Rocky’s emotional state.Here’s some of the play functionality code!
else if (playing) { if (backgroundMusic.isPaused()){ backgroundMusic.play(); } // Play mode timer.display(); rocky.update(); rocky.display(); moodMeter.display(rocky.mood); // Generate obstacles if (frameCount % 40 == 0) { obstacles.push(new Obstacle()); } for (let i = obstacles.length - 1; i >= 0; i--) { obstacles[i].update(); obstacles[i].display(); // Check for collision if (obstacles[i].hits(rocky)) { hitSound.play(); // Play hit sound on collision rocky.mood -= 10; // Decrease mood on collision obstacles.splice(i, 1); // Remove obstacle after collision continue; // Skip to the next iteration to avoid calling offscreen() on a spliced element } // Remove obstacles that go off-screen if (obstacles[i].offscreen()) { obstacles.splice(i, 1); } } // Check if mood or timer runs out to end the game if (rocky.mood <= 0 || timer.isTimeUp()) { gameState='gameOver'; playing = false; resetGame(); }
These are the Rocky and Obstacle classes, which also have elements of the game functionality:
// Rocky class class Rocky { constructor() { this.mood = 50; this.x = windowWidth / 2; this.y = windowHeight / 2 + 100; this.size = 100; // The size of Rocky (matches the previous ellipse size) this.speed = 5; // Speed for moving up and down } display() { // fill(150); // ellipse(this.x, this.y, 100, 100); // Rocky as an ellipse if (this.mood >=80){ rockyImage = rockyImageHappy; } else if (this.mood >=60){ rockyImage = rockyImageHappyish; } else if (this.mood >=40){ rockyImage = rockyImageMeh; } else if (this.mood >=20){ rockyImage = rockyImageSadish; } else { rockyImage = rockyImageSad; } image(rockyImage, this.x - this.size / 2 - 25, this.y - this.size / 2 , this.size+40, this.size); // Display the image with Rocky's coordinates } // Obstacle class for the play mode class Obstacle { constructor() { this.x = windowWidth; this.size = random(60,75); // Size of obstacle // this.y = random(0, windowHeight - this.size); // Random y-position this.y = rocky.y; // Spawn the obstacle at Rocky's current y position } display() { // fill(255, 0, 0); // rect(this.x, this.y, this.size, this.size); image(obstacleImage, this.x, this.y, this.size, this.size); } update() { this.x -= obstacleSpeed; // Move the obstacle to the left } offscreen() { return this.x < -this.size; // Check if the obstacle has moved off-screen } hits(rocky) { let d = dist(this.x, this.y, rocky.x, rocky.y); return d < this.size / 2 + 50; // Check for collision with Rocky } }
- The Relax mode shares similarities with the Feed mode in terms of randomizing Rocky’s music preferences, but the added challenge here was managing the background music. The player can choose from three different music tracks (a lullaby, a funk track and some lo-fi beats), each representing a different genre. Rocky’s mood responds to the track selection—he likes one, dislikes another, and is indifferent to the third, all of which are randomized each round. This randomness keeps the experience fresh and unpredictable, just like the food preferences.
A technical challenge was ensuring that the background music from the Relax mode would stop properly once the player entered the Relax mode and restarted when the player exited the mode or when the track finished playing. It was also important to return to the main screen after the track ended (after 10 seconds) while displaying the feedback and effect on Rocky’s mood.Here’s some of the relax functionality code!
function randomizeMusicPreferences() { let shuffledTracks = shuffle([10, 0, -10]); // One increases, one decreases, one is neutral for (let i = 0; i < relaxTracks.length; i++) { musicPreferences[i] = shuffledTracks[i]; // Map tracks to mood effects } }
function startRelaxMode() { relaxing = true; playing = false; displayTrackButtons(); // Display buttons for selecting tracks backButton.display(); // Show back button to return to main screen } // Function to display track buttons function displayTrackButtons() { // Display each track button for (let trackButton of trackButtons) { trackButton.display(); } } // Function to play the selected track and show feedback function playRelaxTrack(trackIndex) { if (isRelaxing) { return; // If a track is already playing, prevent any other interaction } isRelaxing = true; // Set to true once a track starts playing selectedTrack = trackIndex; relaxTracks[trackIndex].play(); // Play the selected track setTimeout(function() { relaxTracks[trackIndex].stop(); // Stop the track after 10 seconds showRelaxFeedback(trackIndex); relaxing = false; // Return to the main game state relaxDisabled = true; // Disable the Relax button after it's used isRelaxing = false; rocky.x = windowWidth / 2; rocky.y = windowHeight / 2 + 100; }, 10000); // Play for 10 seconds } // Function to show feedback based on Rocky's preferences function showRelaxFeedback(trackIndex) { let moodEffect = musicPreferences[trackIndex]; // Get mood effect for the selected track rocky.mood += moodEffect; // Adjust Rocky's mood accordingly // Set relax feedback based on the mood effect of the track if (moodEffect > 0) { relaxFeedback = `Rocky likes this track!`; } else if (moodEffect === 0) { relaxFeedback = `Rocky is okay with this track.`; } else { relaxFeedback = `Rocky doesn't like this track!`; } // Clear any previous timeout to avoid overlap if (relaxFeedbackTimeout) { clearTimeout(relaxFeedbackTimeout); } // Clear the feedback after 3 seconds relaxFeedbackTimeout = setTimeout(() => { relaxFeedback = ""; }, 3000); // Clear any previous timeout to avoid overlap }
Reflections and Further Improvements
Overall I’m quite satisfied with how the game turned out, especially considering the many stages it went through – from my vague, initial idea to its final implementation. Some things that I can consider doing to add to it could be making the Feed mode slightly more interactive by having a drag-and-drop feature (to make the user feel like they’re actually feeding Rocky). I could also add some sound feedback when Rocky’s mood changes (for instance, when he ate a food he liked or listened to a track he didn’t like). Similarly, I could also add sound feedback when the game ends, reflecting Rocky’s current mood state.
All in all, I really enjoyed the experience of bringing my idea to life. There were quite a few bugs as well as some unexpected behavior that came up along the way, but I’m pretty happy with the final result :))