I just finished the prototype, so I haven’t added the sand on top of the platform yet. I’ll just add the sand on the day of the showcase.
I asked my friends to try it out, and I think they liked it (refer to the video above). I just told them how to control the robot (with arrow keys for now, just for testing, even though I have the mouse interaction already programmed). I think if I could incorporate a joystick in the project, it would really come together, and also its use would be very intuitive. But without mouse/keyboard input, there wouldn’t be any use for p5.js, so I had to keep it the way it is now.
Sometimes they would drive the ball off the edge of the platform, and so they suggested adding edges to the platform. I think the edges are important anyway because there’s going to be sand, and without edges the sand will just be everywhere.
I’m still sticking to my original idea, but I’ve changed my process considerably. Instead of using pulleys and belts, I’ve just decided to make a raised platform and have a moving robot underneath it. The robot will have a magnet, which will move the ball above the platform. It will make no difference to the user (hopefully), but will make the implementation much easier. I realized I would also need lots of other tools to execute the idea the way I had originally planned, and I’d have to purchase them myself. If I use a moving robot, I can just use the stuff that’s around the lab.
“Engage in a thrilling race against time with ‘Drive to Survive,’ where your objective is to outlast the clock for three intense minutes. Can you master the controls, avoid crashes, and emerge victorious? But that’s not all – your ultimate goal is to achieve the maximum distance moved by your high-tech car. Each moment is critical as you strive to set a new distance record”
This is what the user actually experiences in my project. On my side, I wanted my project to simulate the experience of driving a manual car by providing complete control through the p5.js sketch. I added a static car window in the sketch that makes the users feel like they’re actually inside the car while controlling it remotely. Also, the sketch provides a gear box, allowing users to adjust the speed as if they are accelerating in a real car. The cool thing about this project is that it aims to seamlessly bring in a webcam, providing users with a live visual experience on p5.js. This goes beyond physical boundaries, allowing for easy remote control, even if the car is far away.
User Testing:
User 1 Testing: Project was not finalized
User 2 Testing: Final Project
Users Feedback:
The two users generally demonstrated an ability to engage with the project without explicit instructions and they quickly grasped the basic mechanics for car movement.
A good comment was about the game interface which is very simple and friendly. Another good comment was about the filter added to the video, which adds a non-realistic mood that suits the game.
I think only one part of the project required additional explanation which is paying attention to how far is the car because it’s all wired connections so there is a risk of losing connection or laptop fall if it’s put, for example, on a table. For that particular issue, I think I should tell the user about it or maybe adding this instruction in the game sketch as well.
Implementation:
Circuit:
Arduino Circuit
P5JS Code & Sketch:
P5JS code can be described as separate blocks of code which are as follows:
Declaring global variables for the game and car control
Pre-loading all the media used in the project (images, audio, …)
Setting up the sketch and view the game state screen
State1: start which is shown in the sketch below where the user chooses to start the game or show instructions.
State2: Instructions which obviously guide the user
State3: game where you experience driving
State4: end screen where your score is displayed
Arduino Code:
The code basically sets up the car functionality. The code starts with identifying the pins used in the circuit for the motor drivers and the ultrasonic distance sensor. Also, I created a separate function for each direction movement. Based on the data received from the sketch, a function is triggered.
//front left
const int ain1Pin = 13;
const int ain2Pin = 12;
const int pwmAPin = 11;
//back left
const int bin1Pin = 8;
const int bin2Pin = 9;
const int pwmBPin = 10;
//front right
const int cin1Pin = 7;
const int cin2Pin = 6;
const int pwmCPin = 5;
//back right
const int din1Pin = 2;
const int din2Pin = 4;
const int pwmDPin = 3;
const int TRIGGER_PIN = A0;
const int ECHO_PIN = A1;
//global
int speed = 0;
void setup() {
Serial.begin(9600);
pinMode(ain1Pin, OUTPUT);
pinMode(ain2Pin, OUTPUT);
pinMode(bin1Pin, OUTPUT);
pinMode(bin2Pin, OUTPUT);
pinMode(cin1Pin, OUTPUT);
pinMode(cin2Pin, OUTPUT);
pinMode(din1Pin, OUTPUT);
pinMode(din2Pin, OUTPUT);
pinMode (TRIGGER_PIN, OUTPUT);
pinMode (ECHO_PIN, INPUT);
}
void loop() {
// Check if data is available from p5.js
if (Serial.available() > 0) {
// Read the brightness value from p5.js
speed = Serial.parseInt();
int up = Serial.parseInt();
int down = Serial.parseInt();
int right = Serial.parseInt();
int left = Serial.parseInt();
if(up == 1){
moveForward();
}else if(down == 1){
moveBackward();
}else if(right == 1){
moveRight();
}
else if(left == 1){
moveLeft();
}
else{
digitalWrite(ain1Pin, LOW);
digitalWrite(ain2Pin, LOW);
digitalWrite(cin1Pin, LOW);
digitalWrite(cin2Pin, LOW);
digitalWrite(bin1Pin, LOW);
digitalWrite(bin2Pin, LOW);
digitalWrite(din1Pin, LOW);
digitalWrite(din2Pin, LOW);
}
}
unsigned long duration;
float distance;
digitalWrite(TRIGGER_PIN, HIGH);
delayMicroseconds(1000);
digitalWrite(TRIGGER_PIN, LOW);
duration = pulseIn (ECHO_PIN, HIGH);
distance = (duration / 2.0) / 29.0;
Serial.println(distance);
}
void moveForward(){
analogWrite(pwmAPin, speed);
analogWrite(pwmCPin, speed);
analogWrite(pwmBPin, speed);
analogWrite(pwmDPin, speed);
digitalWrite(ain1Pin, HIGH);
digitalWrite(ain2Pin, LOW);
digitalWrite(bin1Pin, HIGH);
digitalWrite(bin2Pin, LOW);
digitalWrite(cin1Pin, HIGH);
digitalWrite(cin2Pin, LOW);
digitalWrite(din1Pin, HIGH);
digitalWrite(din2Pin, LOW);
}
void moveBackward(){
analogWrite(pwmAPin, speed);
analogWrite(pwmCPin, speed);
analogWrite(pwmBPin, speed);
analogWrite(pwmDPin, speed);
digitalWrite(ain1Pin, LOW);
digitalWrite(ain2Pin, HIGH);
digitalWrite(bin1Pin, LOW);
digitalWrite(bin2Pin, HIGH);
digitalWrite(cin1Pin, LOW);
digitalWrite(cin2Pin, HIGH);
digitalWrite(din1Pin, LOW);
digitalWrite(din2Pin, HIGH);
}
void moveRight(){
analogWrite(pwmAPin, 255);
analogWrite(pwmBPin, 255);
analogWrite(pwmCPin, 255);
analogWrite(pwmDPin, 255);
digitalWrite(cin1Pin, HIGH);
digitalWrite(cin2Pin, LOW);
digitalWrite(din1Pin, HIGH);
digitalWrite(din2Pin, LOW);
digitalWrite(ain1Pin, LOW);
digitalWrite(ain2Pin, HIGH);
digitalWrite(bin1Pin, LOW);
digitalWrite(bin2Pin, HIGH);
}
void moveLeft(){
analogWrite(pwmAPin, 255);
analogWrite(pwmBPin, 255);
analogWrite(pwmCPin, 255);
analogWrite(pwmDPin, 255);
digitalWrite(cin1Pin, LOW);
digitalWrite(cin2Pin, HIGH);
digitalWrite(din1Pin, LOW);
digitalWrite(din2Pin, HIGH);
digitalWrite(ain1Pin, HIGH);
digitalWrite(ain2Pin, LOW);
digitalWrite(bin1Pin, HIGH);
digitalWrite(bin2Pin, LOW);
}
long microsecondsToCentimeters(long microseconds) {
return microseconds / 29 / 2;
}
Communication between P5 and Arduino:
The Arduino C code basically receives data from the P5Js sketch and work accordingly. There are 5 variables received that controls the car movement: the four directions (up, down, left, and right) and the speed controlled by the gear box in the sketch. On the other hand, the P5JS sketch receives one value which is the distance of the ultrasonic distance sensor and checks for collisions.
Parts I am proud of:
I am proud of the overall output of my project and that I got some hardware control in my skill set. I am particularly proud of two things: the visual design of my game and creating a gear box to control the car speed. For this project, I spent much time trying to improve the visual elements of my game whether through the static elements (car, gearbox, …) or dynamic video feed. I added a filter to my screen which I think it gives a nice effect that perfectly matches the overall experience. For the gear box, it was very challenging to make it and shift from one gear to another, but eventually, I figured it out. Here is the function that does so.
Future Improvements:
One thing that I want to improve in my project in the wired connection because it somehow limits this fun experience. One way is to use a bluetooth module for sending and receiving data. However, the wired connection was still required for connecting the webcam to the laptop and capturing the car view. I am not sure, but maybe there is a wireless webcam somewhere on Earth…
I aimed to delve into recreating a retro game named “Feed Flynn,” blending the Catcher and spaceship Tyrian games into one fun experience. Inspired by the saying “don’t eat the book” and my deep fondness for food, I wanted to craft a game celebrating my love for food—an arcade-style creation I call “Feed Flynn.”
You get points for Feeding Flynn donuts +10,
Burgers ( full Meal ) + 20
Books -3 (why would you even feed Flynn Books ;( )
Flynn also has the ability to shoot bullets to eliminate the books. It’s a 35-second challenge to compete for the highest score, a way to test your skills and challenge your friends for the top spot!
The game adopts a retro vibe with:
A glitchy character sprite
Pixelated character design
Incorporation of music and sound effects
Include some pictures / video of your project interaction
this is how my project hardware is looking ( retro arcade)
I decided to have my single line of instruction Do NOT eat the books on the box; Condensing complex rules into concise, clear instructions often enhances comprehension and user engagement
How does the implementation work?
Description of interaction design
Interaction design involves creating the interface between a user and a system, aiming to optimize the user’s experience. In the context of “Feed Flynn,” the game utilizes four arcade buttons to facilitate the player’s interaction:
Start/Restart Button: This button initiates the game or restarts it once it’s over. It serves as the gateway to engage with the game, allowing the player to enter the gaming experience.
Right/Left Buttons: These two buttons enable the movement of Flynn, the character, within the game. They provide directional control, allowing Flynn to navigate right or left within the gaming environment, dodging falling objects or positioning to catch desired items.
Bullet Firing Button: This button empowers Flynn to shoot bullets in the game. By pressing this button, players can eliminate books, preventing them from being consumed by Flynn and avoiding point deductions. It adds an element of strategy and skill to the gameplay, requiring players to decide when to fire bullets strategically.
Description of Arduino code and include or link to full Arduino sketch
const int buttonStartPin = 2; // Pin for the start button
const int buttonLeftPin = 3; // Pin for the left button
const int buttonRightPin = 4; // Pin for the right button
const int buttonFirePin = 5; // Pin for the fire button
void setup() {
Serial.begin(9600);
pinMode(buttonStartPin, INPUT_PULLUP);
pinMode(buttonLeftPin, INPUT_PULLUP);
pinMode(buttonRightPin, INPUT_PULLUP);
pinMode(buttonFirePin, INPUT_PULLUP);
}
void loop() {
int startButtonState = digitalRead(buttonStartPin);
int leftButtonState = digitalRead(buttonLeftPin);
int rightButtonState = digitalRead(buttonRightPin);
int fireButtonState = digitalRead(buttonFirePin);
// Invert button states before sending to serial
Serial.print(!startButtonState);
Serial.print(",");
Serial.print(!leftButtonState);
Serial.print(",");
Serial.print(!rightButtonState);
Serial.print(",");
Serial.println(!fireButtonState);
delay(100); // Optional delay to stabilize readings
}
Description of p5.js code and embed p5.js sketch in post
let catcherX, catcherY; // Declaring variables for catcher position
let objects = []; // Array to store falling objects
let objectSpeed; // Variable to control object speed
let gameStarted; // Flag to track game state
let serial; // Serial port communication variable
let points; // Variable to track player points
let startTime; // Start time of the game
let gameDuration; // Duration of the game
let fireButtonState = 0; // State of the fire button
let bullets = []; // Array to store bullets
let backgroundImage; // Variable to hold a background image
let backgroundImage2; // Another background image variable
let backgroundImage3; // Third background image variable
let catcherFrames = []; // Array to store frames of the sprite sheet
let catcherIndex = 0; // Index to track the current frame
let catcherSpeed = 0.2; // Speed of the catcher animation
var gif; // Variable for a GIF element
let catchSound; // Sound variable for catching objects
function preload() { // Loading assets
backgroundImage = createImg("https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExOHlzcmp4MTh1bDJqMTMzbXAyOTAzMHgxcTk0bmUyYXJncXBpd2d4cSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/LV4MGiLrYrNaF3Dpbn/giphy.gif");
backgroundImage2 = loadImage("2.png");
backgroundImage3 = loadImage("6.png");
bungeeFont = loadFont('Bungee-Regular.ttf');
catcherSheet = loadImage('8.png');
books = loadImage("3.png");
donut = loadImage("4.png");
burger = loadImage("5.png");
gif = createImg("https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExNHBldTFuczNob251M3NiNjJ6cGl1aHczM3ZoN2c1em9hdXB5YTJvdSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9cw/96t0nzIf5cgGrCdxFZ/giphy.gif");
gif.hide();
gameStartSound = loadSound('07. STAGE 2 [PC Engine]-1.mp3');
gameOverSound = loadSound('33. GAME OVER [PC-9801]-1.mp3');
catchSound = loadSound('heavy_swallowwav-14682.mp3');
}
// Setup canvas and initial game conditions
function setup() {
createCanvas(889, 500);
catcherX = width / 2;
catcherY = height - 50;
objectSpeed = 2;
gameStarted = 0;
points = 0;
gameDuration = 35;
startTime = millis();
serial = new p5.SerialPort();
serial.open('COM6');
serial.on('data', serialEvent);
}
// Game loop managing different game states
function draw() {
if (gameStarted === 0 ) {
// Display the appropriate background image
backgroundImage.size(889,500);
if (!gameStartSound.isPlaying()) {
gameStartSound.play();
}
} else if (gameStarted === 1) {
backgroundImage.hide();
image(backgroundImage2, 0, 0, width, height);
gif.show();
gif.position(catcherX - 100, catcherY - 60);
gif.size(200,100)
// Draw catcher and game elements
drawGame();
}
}
// Function handling the core game logic and rendering
function drawGame() {
let currentTime = millis();
let elapsedTime = (currentTime - startTime) / 1000; // Elapsed time in seconds
let remainingTime = gameDuration - floor(elapsedTime); // Remaining time in seconds
textSize(16);
fill(0);
textAlign(RIGHT);
text(`Time: ${remainingTime}`, width - 80, 52);
fill("rgba(0,255,0,0)");
noStroke();
// Draw catcher
ellipseMode(CENTER); // Set ellipse mode to CENTER
catcherX = constrain(catcherX, 25, width - 25);
ellipse(catcherX, catcherY, 50, 50); // Draw a circle for the catcher
// Generate falling objects continuously
if (frameCount % 30 === 0) {
objects.push(...generateObjects(3));
}
// Draw falling objects
for (let obj of objects) {
drawObject(obj);
obj.y += objectSpeed;
// Check for catch
if (
obj.y > catcherY - 10 &&
obj.y < catcherY + 10 &&
obj.x > catcherX - 25 &&
obj.x < catcherX + 25
) {
handleCatch(obj);
}
}
fill(0);
// Display points
textSize(16);
text(`Points: ${points}`, 170 , 50);
textFont(bungeeFont);
// Handle bullets
handleBullets();
// Check for game over
if (millis() - startTime >= gameDuration * 1000) {
displayGameOver();
}
}
// Handling keyboard input for catcher movement
function keyPressed() {
const catcherSpeed = 5;
if (keyCode === LEFT_ARROW) {
catcherX -= catcherSpeed;
} else if (keyCode === RIGHT_ARROW) {
catcherX += catcherSpeed;
}
}
// Handling serial port events for game control
function serialEvent() {
let data = serial.readLine();
if (data !== null) {
let states = data.split(',');
let startButtonState = parseInt(states[0]);
let leftButtonState = parseInt(states[1]);
let rightButtonState = parseInt(states[2]);
fireButtonState = parseInt(states[3]);
const catcherSpeed = 10;
if (startButtonState === 1) {
if (gameStarted !== 1) {
gameStarted = 1;
points = 0; // Reset points to zero when the game starts
startTime = millis();
}
}
if (gameStarted) {
if (leftButtonState === 1) {
catcherX -= catcherSpeed;
} else if (rightButtonState === 1) {
catcherX += catcherSpeed;
}
}
}
}
// Generating falling objects
function generateObjects(numObjects) {
let generatedObjects = [];
for (let i = 0; i < numObjects; i++) {
let type;
let rand = random();
if (rand < 0.2) {
type = 'square';
} else if (rand < 0.6) {
type = 'circle';
} else {
type = 'triangle';
}
let obj = {
x: random(width),
y: random(-50, -10),
type: type,
};
generatedObjects.push(obj);
}
return generatedObjects;
}
// Drawing and displaying falling objects
function drawObject(obj) {
fill("rgba(0,255,0,0)");
noStroke();
if (obj.type === 'triangle') {
ellipse(obj.x, obj.y, 30, 30);
image(books, obj.x - 45, obj.y - 35, 90, 55);
} else if (obj.type === 'circle') {
ellipse(obj.x, obj.y, 30, 30);
image(donut, obj.x - 39, obj.y - 22.5, 80, 45);
} else if (obj.type === 'square') {
ellipse(obj.x - 10, obj.y - 10, 30, 30);
image(burger, obj.x - 60, obj.y - 45, 100, 60);
}}
// Handling catcher interaction with falling objects
function handleCatch(obj) {
if (obj.type === 'triangle') {
points -= 3;
} else if (obj.type === 'circle') {
points += 10;
} else if (obj.type === 'square') {
points += 20;
}
catchSound.play(); // Play the sound when the catcher catches an object
objects.splice(objects.indexOf(obj), 1);
}
// Handling bullet mechanics
function handleBullets() {
if (fireButtonState === 1) {
bullets.push({ x: catcherX, y: catcherY });
}
for (let i = bullets.length - 1; i >= 0; i--) {
let bullet = bullets[i];
bullet.y -= 5;
fill(255, 0, 0);
ellipse(bullet.x, bullet.y, 5, 10);
for (let j = objects.length - 1; j >= 0; j--) {
let obj = objects[j];
if (dist(bullet.x, bullet.y, obj.x, obj.y) < 15 && obj.type === 'triangle') {
objects.splice(j, 1);
bullets.splice(i, 1);
points += 5;
}
}
if (bullet.y < 0) {
bullets.splice(i, 1);
}
}
}
// Displaying the game over screen
function displayGameOver() {
gameStartSound.stop();
gameOverSound.play();
fill(0);
// Display game over screen
textFont(bungeeFont);
image(backgroundImage3, 0, 0, width, height);
console.log("Game Over");
textAlign(CENTER);
textSize(24);
fill(0);
text("Game Over", width / 2, height / 2 - 90) ;
text(`Your Score: ${points}`, width / 2, height / 2 );
gameStarted = 2;
gif.hide();
}
Describtion of the code:
Variables: Various variables are declared to manage game elements such as catcher position, falling objects, game state, time, sound, and image assets.
preload(): Preloading assets like images, sounds, and fonts before the game starts.
setup(): Initializing the canvas size, setting initial game conditions like catcher position, object speed, and game duration, as well as initializing the serial port communication.
draw(): The main game loop that manages different game states and calls specific functions based on the game state.
drawGame(): Handles the core game logic and rendering. Manages time, displays game elements, generates falling objects, checks for collisions, and handles points and game over conditions.
keyPressed(): Listens for keypress events to control the catcher’s movement.
serialEvent(): Handles events from the serial port for game control (button presses, etc.).
generateObjects(): Generates falling objects of different types (triangle, circle, square).
drawObject(): Draws and displays the falling objects based on their types (triangle, circle, square) using images.
handleCatch(): Manages the interaction between the catcher and falling objects, updating points and removing caught objects.
handleBullets(): Handles the bullet mechanics, allowing the catcher to shoot at falling objects, awarding points upon successful hits.
displayGameOver(): Displays the game over screen, stops game sounds, shows the final score, and resets game states.
Description of communication between Arduino and p5.js
At first, I struggled a lot with understanding serial communication. It was hard, and it made it tough to communicate well for the project. But when I asked for help and used the p5 .exe desktop app better, things got easier. Learning how to use it properly helped me improve how I communicated for the project.
What are some aspects of the project that you’re particularly proud of?
I’m really happy with the graphics! Being a graphic designer, I put a lot of effort into creating Flynn and the game design. It was super fun to work on this video game, and I don’t think it’ll be my last! This time around, I loved playing with pixels, making animations, and exploring different pixel art styles for that cool retro theme.
I also noticed a visual connection between my Midterm Game “CatsAway” and this project. Both have this joyful vibe and a surreal feel, especially flying around and munching on books, which is part of my art style.
What are some areas for future improvement?
Accessibility: I’m aiming to ensure the game is accessible to a wider audience by making it compatible across different devices or platforms. This improvement will make the game available to more players, which is a goal I’m focused on.
My project is finally coming along! After a lot of debugging, failed laser cutting attempts and design fixes, I finally have something to test with my friends!
My project, a single player, finger version twister game has a wheel that spins based on the User saying start-stop and then displays the colors and the hand they need to use and the users just press the switches. Having all the fingers on the board correctly is a win and any wrong attempt is an immediate end to the game.
Are they able to figure it out? Yes, it was evident from the instructions page on what they had to do.
Where do they get confused and why? There wasn’t anything as confusing that they found as it was easy to navigate. The only part of it that was confusing the first time I tested this were the switches. Since the switches are maroon in color they get mistaken as the red buttons. However my intent is to have colored stickers around each button to show which color it belongs too.
Do they understand the mapping between the controls and what happens in the experience? Yes they were able to understand this as the wheel showed which color and even the hand depicted the color on screen making it easier to follow. Sometimes it got a little tricky but that is part of the game where you need a few seconds to process the color and hand when you see so many together.
What parts of the experience are working well? What areas could be improved?
The colors, switches, scores are working well. My only problem is the speech recognition. It is very conditional as sometimes it worked flawlessly and sometimes it just wouldn’t recognize the words, As you can see in the videos below. Since there isn’t anything wrong with the code, I’m not sure how to fix it so I’m looking for alternate ways to control this without the usage of hands since that is not possible. It can get a little frustrating if the person has to keep saying start and stop to get it right and finally get the wheel moving, it might also be too loud. So I’m thinking of shifting it to just one word thats easily capturable and then the wheel would automatically stop after 2 seconds to slightly make this better. But apart from this I will try to see if i can use any other sensors to fix this issue as I want the experience to be as interactive as it is with the sound.
What parts of your project did you feel the need to explain? How could you make these areas more clear to someone that is experiencing your project for the first time?
The maroon colored switches! I’m not sure if i should paint them as that would ruin the feel of it. Maybe i can try cutting the stickers and pasting them on the switches to make this clearer.
Are they able to figure it out? Where do they get confused and why? Do they understand the mapping between the controls and what happens in the experience?
Some users did not know when to start playing the Skeeball machine after interacting with the p5.js sketch.
Some users also did not know how to play the skeeball machine, and it was a little frustrating for them as they kept missing the targets.
What parts of the experience are working well? What areas could be improved?
Users loved the instant feedback from hitting a target in the skeeball machine, and in general most users loved the p5.js sketch as they liked the art style and animations. They also liked the high score list.
The ball would sometimes get stuck in the machine, so users would be confused when the ball would not return.
What parts of your project did you feel the need to explain? How could you make these areas more clear to someone that is experiencing your project for the first time?
I often had to manually remove the ball from the machine when it got stuck. I could improve this experience by making the ball return mechanism work better, but I was not able to figure out a way of fixing it 100% of the time in the 2 hours I’ve spent trying to fix this thing.
I also had to show many users how to roll the ball, as some users would try to throw the ball which is not the intended way of playing. I could improve this by adding a screen to the front of the targets, so the only way to hit the targets is by rolling the balls up the ramp.
I had two of my friends try the project during user testing. While conducting the test, I connected the p5.js sketch to the Arduino, displayed the sketch in full screen, and left the users to determine how to interact with both the box and the screen. Even though they were mentioned in the instruction that was displayed on the screen, users were uncertain about interacting with the box and screen simultaneously. So I needed to explain that they would receive hints on the screen and input the code using the button and knob on the box.
About the guessing the code, they quickly understood how to use the hints to guess the three digits required to unlock the code. However, they found it confusing regarding how to backtrack and edit an entered code if they wanted to change one of the digits, which is not possible in the game. And that was missing in the instruction.
After user testing, I decided to add instructions on how to enter the code and clarify that editing the already entered code was not an option. Also, I chose to include printed instructions on the box, indicating users should gather hints from the screen to input the digits accordingly.
I wanted to make an arcade machine for my final project, and my initial idea was a pachinko machine. However, I pivoted away from that as I couldn’t think of a good way to integrate P5.js into that project, and decided to make Skeeball instead.
My project is basically skeeball, with a touch of an RPG battle concept, hence the very creative name ‘Skeeball Dungeon’. The player will have to fight 4 increasingly tougher opponents in P5.JS, and they the damage they do is equal to the amount of points they get in skeeball. For each opponent, they are given 5 balls, and they have to defeat the opponent in the number of given balls.
Include some pictures / video of your project interaction
p5.js battle! skeeball !
Description of interaction design
The p5.js sketch is used to display instructions, showing high scores, visualizing the battles and hold the graphics. The Arduino is used to obtain information from the user playing Skeeball, detecting when a target has been hit and how many points was obtained.
Description of Arduino code and include or link to full Arduino sketch
The Arduino was used to detect when targets has been hit in the Skeeball physical component. To detect when a target has been hit, a flex sensor is used for each hole. When the ball passes through the hole, the Arduino pin input that is connected through the flex sensor-voltage divider circuit will have a lower/higher reading. This change is reading is then used to detect when a target has been hit.
Description of p5.js code and embed p5.js sketch in post
https://editor.p5js.org/ojmjunming/sketches/V0ARmZA70 The p5.js code holds all of the battle logic and the screens. A state system is used to transition between each different ‘screen’ to make the code more readable. e.g the first screen, instructions and more.
if (currentScene === "highScore") {
highScoreScreen();
} else if (currentScene === "instructions") {
instructionScreen();
} else if (currentScene === "battle") {
renderBattleScreen();
}
The code also keeps track of the health of the monster, the current monster, the score and the number of balls. The Arduino does not know how many balls are left, it only communicates the ball hits to p5.js. The p5.js sketch also maintains a top 5 list of high scores.
Description of communication between Arduino and p5.js
The Arduino will send a message of length 2 a single time when a target is hit. Otherwise, it sends a garbage message of length 1. The message contains the amount of points that was scored with that hit. The p5.js will continuously handshake with Arduino, most of the time the data is useless (when it’s length 1), and when the length is 2, it means that a target has been hit and the message will be processed, dealing damage to the monster equivalent to the points scored.
What are some aspects of the project that you’re particularly proud of?
There were some really interesting problems that I was proud of solving: 1) Constructing the Skeeball machine. I originally planned to use the laser cutter to build the physical component, but the laser cutter only managed to cut out one piece before it started acting up. I waited 3 hours and the laser cutter was still not working. So, I pivoted to making it with cardboard and wood pieces, using tons of hot glue but it turned out to be surprisingly stable so I’m pretty proud of the machine itself as it took me a lot of time.
2) 7 flex sensors, but only 6 analog inputs on the Arduino Uno.
I was using flex sensors to detect target hits, but I had 7 targets/holes but only 6 analog inputs. Remembering that a digitalPin reads HIGH or LOW voltages (2.5-5V, 0-2.5V), I constructed a voltage divider with 30K resistors, so the analog reading teetered on the edge of 2.4-2.6V (480-530 analogInput) depending on the flex of the flex sensor. Knowing this, I was able to use a digitalPin to read the flex of the sensor ( the other sensors were read with a analogPin, which was much easier ). When the sensor is flexed, the reading would change from HIGH to LOW.
What are some areas for future improvement?
The skeeball machine could have been constructed better, as I did not have time to paint or decorate it well. The ball also sometimes gets stuck while it’s attempting to return to the start tray and you have to manually push it out. I also had to scrap an idea I had of detecting missed ball hits, as I wasn’t able to figure out how to attach a sensor for that, so in the future it would be nice to be able to include that in my game.
For my final project, I stuck closely to the original concept and developed a straightforward yet engaging game. The premise involves safeguarding a car navigating a road from descending obstacles. Users are tasked to protect the car by moving it left and right using the buttons on the Arduino board. Failure to shield the car results is a game over, while successfully safeguarding it from at least 10 obstacles leads to victory. I introduced an element of challenge by ensuring that the car moves at a fast pace, adding a layer of difficulty for users as they skillfully escape the approaching obstacles.
User testing:
I tested the game with my friend Javeria, and given its straightforward design with limited options, she quickly understood the mechanics. During the experience, she said she was able to grasp the functionality effortlessly without any guidance. Her understanding of the game highlighted its user-friendly nature and accessibility.
Challenging part:
The core functionality of the game coding operates smoothly, demonstrating performance without encountering any errors related to its main features. Despite facing multiple challenges, the essential gameplay elements functioned flawlessly. However, the area requiring further improvement lies in the transition between pages. The aspect of navigating between different sections of the game could benefit from some enhancements.
I intentionally excluded the idea of an instruction page, as I, reflecting on my own experiences as a child, preferred the thrill of figuring things out independently. However, recognizing that not everyone shares this perspective, users may need guidance on understanding the fundamental operations of the game.
For the arduino I implemented simple 2 switches coding. Initially my p5 coding was the movement of the car to right and left using the keys of the keyboard. But by utilizing the readSerial() function as the communication link between p5.js and Arduino, the Arduino code is structured to retrieve the statuses of two switches and transmit these values to p5.js.
Arduino code:
const int switch1Pin = 4; // Replace with the actual pin for switch 1
const int switch2Pin = 8; // Replace with the actual pin for switch 2
void setup() {
Serial.begin(9600);
pinMode(switch1Pin, INPUT_PULLUP);
pinMode(switch2Pin, INPUT_PULLUP);
while (Serial.available() <= 0 ){
Serial.println("0,0");
delay(300);
}
}
void loop() {
while(Serial.available()) {
if (Serial.read() == '\n') {
int switch1State = digitalRead(switch1Pin);
delay(5);
int switch2State = digitalRead(switch2Pin);
Serial.print(switch1State);
Serial.print(',');
Serial.println(switch2State);
}
}
// if (switch1State == LOW) {
// // Switch 1 is clicked, set output to 1
// Serial.println("1");
// while (digitalRead(switch1Pin) == LOW) {
// // Wait until switch 1 is released
// }
// } else if (switch2State == LOW) {
// // Switch 2 is clicked, set output to 0
// Serial.println("0");
// while (digitalRead(switch2Pin) == LOW) {
// // Wait until switch 2 is released
// }
// }
}
p5.js code:
let img;
let rock;
let bg;
let car;
let obstacles = [];
let score = 0;
let bgSpeed = 2; // Background scrolling speed
let y1 = 0;
let y2;
let switch1State, switch2State;
let start;
let restart;
let gameStarted = false;
let gameOver = false;
let gameWon = false;
let winThreshold = 5;
let win;
let music;
function preload() {
img = loadImage('pinkcarsss.png');
rock = loadImage('rockss.png');
bg = loadImage('backgroundroad.png');
start = loadImage('startpage123.png');
restart = loadImage('restartpage.png');
win = loadImage('winpage123.png');
music = loadSound('gamemusic.mp3');
}
function setup() {
createCanvas(500, 600);
car = new Car();
y2 = height;
music.play();
}
function draw() {
background(250);
// displaying of pages according to win/lose
if (gameWon) {
// Player wins
drawWinPage();
} else if (gameOver) {
// Player loses
drawLosePage();
} else {
// Display the start page
image(start, 0, 0, width, height);
if (gameStarted) {
drawGame();
}
}
}
function drawWinPage() {
image(win, 0, 0, width, height);
}
function drawLosePage() {
image(restart, 0, 0, width, height);
}
function restartGame() {
gameOver = false;
gameStarted = false;
score = 0;
obstacles = [];
setupGame();
}
function winGame() {
gameWon = true;
gameOver = false;
gameStarted = false;
}
function mousePressed() {
if (gameOver || gameWon) {
if (mouseX > 200 && mouseX < 328 && mouseY > 235 && mouseY < 300) {
restartGame();
}
} else if (!gameStarted) {
if (mouseX > 200 && mouseX < 328 && mouseY > 235 && mouseY < 300) {
gameStarted = true;
setupGame();
}
}
}
function drawGame() {
y1 += bgSpeed;
y2 += bgSpeed;
if (y1 > height) {
y1 = -height;
}
if (y2 > height) {
y2 = -height;
}
// Draw background images
image(bg, 0, y1, width, height);
image(bg, 0, y2, width, height);
car.show();
car.move();
if (frameCount % 80 === 0) {
obstacles.push(new Obstacle());
}
for (let obstacle of obstacles) {
obstacle.show();
obstacle.move();
if (car.hits(obstacle)) {
gameOver = true;
}
if (obstacle.offscreen()) {
score++;
obstacles.shift();
}
}
if (score >= winThreshold) {
winGame();
}
// score
showScore();
}
function setupGame() {
obstacles = [];
score = 0;
y1 = 0;
y2 = height;
car = new Car();
gameStarted = true;
gameOver = false;
gameWon = false;
}
function showScore() {
fill(0);
textSize(17);
text(`Score: ${score}`, 20, 20);
}
class Car {
constructor() {
this.w = 80;
this.h = 90;
this.x = width / 2 - this.w / 2;
this.y = height / 2 - this.h / 2;
}
show() {
fill(0, 255, 0);
image(img, this.x, this.y, this.w, this.h);
}
move() {
// Car moves automatically in the vertical direction
this.y -= 3;
// Reset car's position when it goes off the top
if (this.y < -this.h) {
this.y = height - this.h - 20;
}
}
moveLeft() {
this.x -= 10;
}
moveRight() {
this.x += 10;
}
hits(obstacle) {
return (
this.x < obstacle.x + obstacle.w &&
this.x + this.w > obstacle.x &&
this.y < obstacle.y + obstacle.h &&
this.y + this.h > obstacle.y
);
}
}
class Obstacle {
constructor() {
this.w = 40;
this.h = 50;
this.x = random(width - this.w);
this.y = -this.h;
}
show() {
fill(255, 0, 0);
image(rock, this.x, this.y, this.w, this.h);
}
move() {
this.y += 5;
}
offscreen() {
return this.y > height;
}
As for the p5, it contains the main logic of the game, with four different pages. There are multiple images added and also the elements like moving car and the rock are all png images. A happy music was also implemented in p5.
Looking ahead, I aspire to elevate the game beyond its current straightforward nature, by infusing it with more excitement and thrill. In terms of future enhancements, my goal is to inject more excitement into the game, moving beyond its current straightforward design. I’m also eager to explore and incorporate additional physical elements, further enhancing the interactive and immersive aspects of the gaming experience.
Still in its prototype phase, I did user testing on my project. The video is seen below:
As of now, the project is not complete. The p5.js code needs to be improved (it is a little buggy) and sounds need to be added. The hardware setup is also not final right now – the strip will be attached to the box.
Thing that worked well was that The interface was pretty straightforward for people to figure out. I didn’t really feel the need to explain to people to what to do with the project. I also got positive feedback about the aesthetics of the project!
The main thing that could be improved was that the length of the light sequence was not congruent with the sequence on the screen. This led to people pressing on the button multiple times, but nothing on the lights changing, giving the impression that nothing was happening.