“You forget life has a purpose,
and get lost in its drudgery.
It has ups, it has downs,
Sometimes it makes sense,
Sometimes it doesn’t feel worthwhile.
But if we see life like to has go by our instructions,
It will feel like life has never any purpose.
Your time is there to experience life beyond its difficulty,
There will be time for everyone to see life is a box of so many good things and endless possibilities.
Till then hold on to dear life,
It maybe rough and tough and you feel like a long fight,
But don’t worry — soon you try again and it can be alright.” ——Life is a journey
Our project: A DANDELION’S ADVENTURE——Livvy&Leo
CONCEPTION AND DESIGN:
Inspiration and Initiatives
Our project, “A Dandelion’s Adventure,” serves as fascinating entertainment for people, especially kids, to relax and have fun. The game rule is based on the casual mobile game “Flappy Bird,” developed by the Vietnamese video game artist and programmer Dong Nguyen. In our project, a player will control a flowing dandelion with their hand movements and blowing.
In this semester, we perceived that our peers of the same generation are more likely to express negative emotions, such as feeling really stressed, so we are curious about the stress level of people nowadays. According to a survey conducted in China by Rakuten Insight in May 2022, almost six in ten respondents claimed that they had experienced a higher level of stress or anxiety in the past 12 months. Hence, it is undeniable that people are actually feeling more stressed. So, based on this fact, we aim to create a space for people to totally entertain themselves and isolate themselves from the huge stress in their study or work lives. And from our research, we put our eyes on a popular game once before—”Flappy Bird,” and we found that Curtiss Murphy, founder of GiGi Games, once commented, “You get into this state where you say, ‘I want to do this, I want to accomplish this goal,” Murphy said. “And you strive for it.” In the case of Flappy Bird, people strived for it again and again in search of not only the feeling of triumph at the end of a good run but also the very act of achieving that triumph. It’s an experience marked by the brief amount of time when the world appears to melt away and the players, appear to be hovering over themselves, watching themselves play. This kind of effect aligns perfectly with our purpose, letting them immerse themselves in the game and isolate themselves from the stress outside the world.
Basically, our game rule is similar to “Flappy Bird”. In our design, one or two players control a flowing dandelion moving in a horizontal direction while dodging obstacles vertically. However, what makes our project distinctive from “Fallp Bird” is that:
- First, the mechanism is different. In our game, we design not only one character, namely the dandelion but also a chaser behind the dandelion, meaning that the game ends when either the dandelion touches the obstacle or it gets touched by the chaser.
- Second, the way players communicate with the game character is not through the screen or keyboard. We will use physical input to control the character’s movement. As for the mechanism, we use an ultrasonic sensor to sense the position of the player’s hand and use the map function to map the ultrasonic value to the dandelion’s height in the sketch so that the player can control the dandelion’s movement vertically to avoid the obstacles. We also use the flow sensor for the player to blow to control the character’s moving speed in case they are caught by the chaser. For the chaser, we will design it as a bird-like figure and let the chaser’s default speed be higher than the dandelion’s speed. So, the player has to blow harder to make the dandelion escape from the chaser while being careful of hitting the obstacles.
- From the perspective of aesthetics, in Processing, we design the obstacles as grass bushes, the player as a dandelion, and the chaser as a bird. The whole vibe of the game should be warm-style. For the physical appearance, we intend to make a stage like the natural environment to put in the sensor, where the player can move their hand up and down, using a laser cut box to hide the box with some fake grass around. Also, we managed to instruct the player to hold a fluffy white toy to reinforce the connection and interaction between the sketch on the screen and the real physical stuff.
Above all, the basic “input” designs for the game are hand position testing and the strength of player blowing. Player can decide whether to play the game by themselves or cooperate with others.
Our goals
As mentioned in the first section, we aim to create a peaceful space for the audience to get out of their stress for a while take a breath, and have fun for a little bit. Through this project, we hope our audience will feel more engaged in the game than just playing it on their mobile phones, since by interacting with the character through more physical and intense input like blowing and waving hands, they can build a deeper and more impressive connection with the game, making them more isolated from the stressful environment in their study and work lives. Moreover, The game’s difficulty progresses gradually, maintaining player interest without leading to frustration, which is essential for keeping them in a state of flow. Visually, the game features soothing colors and simple graphics, accompanied by calming sound effects and positive audio feedback that enhance the relaxing quality of the game. A simple yet effective rewards system offers tangible achievements, motivating players to surpass their previous scores and fostering a sense of satisfaction and triumph. By combining these elements, “A Dandelion’s Adventure” aims to detach players from their external pressures and immerse them in a calming, engaging, and satisfying gameplay experience.
Design modification and adjustment
Some major modifications and adjustments were made after the user testing. During user testing, several users tested our project. Through my observation, some main issues occurred, which were:
- Since we directly put the flow sensor and ultrasonic sensor on the table, without clear instructions there, users didn’t know how to interact with them, especially with the ultrasonic sensor, because they always wanted to press the sensor just like interacting with a button, instead of moving their hands.
- At that time, we hadn’t developed any aesthetic decorations for our project and the game just had a blue background which was a little bit mundane.
- I found that the ultrasonic sensor was really unstable, often generating jumping data and making the users’ experience bad.
And other users also gave us loads of insightful suggestions. Following is the note I took:
My friend that I nominated, Daniel, and many others suggested we add an instruction on the hand moving range. And one of my friends also advised us to add an instruction to inform players that they are the dandelion, not the bird and she also suggested we visualize the accelerating process when players blow the flow sensor, “maybe some feathers fall off” she said. Moreover, many reported that we did need to solve the instability of the ultrasonic sensor since it really negatively affects users’ experience. Daniel said we could turn to Professor Rudy for help because he knows ultrasonic sensors well and can use a library called “new ping”. Last but not least, many people also gave us valuable feedbacks from an aesthetic perspective. For instance, the dandelion’s image could be bigger and the chaser character can change to one that makes more sense since we first used an image of an eagle as the chaser, and that made the audience confused. We were also suggested to make the background more colorful since it looked a little bit mundane. They also said we could design some sound effect for character’s movements and different interactions: acceleration and different game endings.
After user testing, I met with my partner, Livvy. Based on all the feedback above, we assessed the feasibility and necessity of each. We thought it would be more feasible for us to decorate the game in a more aesthetic way. Also, adding more explicit instructions on which game characters players are representing and how to control the character by interacting with the sensor, for instance, the hand moving range and how to blow the flow sensor would be necessary as well. Moreover, we determined to turn to Professor Rudy for help with the stability of the ultrasonic sensor since this sensor is the most crucial component of our game’s mechanism and its instability was the biggest challenge that confused our players. From the user testing we saw our audience felt really frustrated as even though they followed our instruction and interacted with the sensor in the right way, they still couldn’t “do well” in the game. This problem deviated us from our goal of relaxing people and entertaining them.
So, we met with Rudy to solve this problem first. He told us the problem stemmed from the sensor’s own limitation that nobody could solve. However, he offered us two solutions. He first gave us two new sensors to detect the distance and then he helped us modify our code with the library “New Ping”. Here is the code:
// --------------------------------------------------------------------------- // Example NewPing library sketch that does a ping about 20 times per second. // --------------------------------------------------------------------------- #include #define TRIGGER_PIN 13 // Arduino pin tied to trigger pin on the ultrasonic sensor. #define ECHO_PIN 12 // Arduino pin tied to echo pin on the ultrasonic sensor. #define MAX_DISTANCE 200 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm. double flow; //Water flow L/Min ] int flowsensor = 2; unsigned long pulse_freq; NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance. void pulse() // Interrupt function { pulse_freq++; } void setup() { pinMode(flowsensor, INPUT); //Serial.begin(9600); attachInterrupt(0, pulse, RISING); // Setup Interrupt Serial.begin(115200); // Open serial monitor at 115200 baud to see ping results. } void loop() { delay(50); // Wait 50ms between pings (about 20 pings/sec). 29ms should be the shortest delay between pings. //Serial.print("Ping: "); Serial.print(constrain(sonar.ping_cm(),5,40)); // Send ping, get distance in cm and print result (0 = outside set distance range) // Serial.println("cm"); flow = (pulse_freq / 7.5); pulse_freq = 0; // Reset Counter //Serial.print(round(smoothed)); // Serial.print(distance); Serial.print(","); Serial.print(round(flow*10)); Serial.println(); }
However, things didn’t go smoothly as well. When we used the new code to test the sensor, we found that it’s still unstable. And I couldn’t find the template code for the new sensor Rudy gave me and he also didn’t know how to use it. So, I decided to find the code to modify the code. Luckily, after a whole afternoon of searching and testing, I found the template to stabilize the sensor and I modified it based on our design. Here’s the code:
#include /** * Is possible set the timeout in the constructor, like: * Ultrasonic ultrasonic(12, 13, 20000UL); */ Ultrasonic ultrasonic(13, 12); double flow; //Water flow L/Min int flowsensor = 2; unsigned long currentTime; unsigned long lastTime; unsigned long pulse_freq; void pulse () // Interrupt function { pulse_freq++; } void setup() { Serial.begin(115200); ultrasonic.setTimeout(40000UL); pinMode(flowsensor, INPUT); attachInterrupt(0, pulse, RISING); // Setup Interrupt currentTime = millis(); lastTime = currentTime; } void loop() { currentTime = millis(); // Every second, calculate and print L/Min if(currentTime >= (lastTime + 100))//can be changed { lastTime = currentTime; // Pulse frequency (Hz) = 7.5Q, Q is flow rate in L/min. flow = (pulse_freq / 7.5); pulse_freq = 0; // Reset Counter Serial.print(ultrasonic.read()); //delay(100); Serial.print(","); Serial.print(flow, DEC); Serial.println(); //Serial.println(" L/Min"); } }
Source: Simoes, Erick (2018) Ultrasonic Timeout.ino. https://github.com/ErickSimoes/Ultrasonic/blob/master/examples/Timeout/Timeout.ino
Here, I contrast the two codes to show the difference, the former one was made before modification and the latter was my modification:
Unstable data testing: Sometimes there would be super big data jumps out
Stable data
After we loaded the image of the characters and the background, it became more stable than this since it got a little stuck. So unexpected…
Apart from solving the instability, we also focused on improving the sound and aesthetic image issues. Livvy and I were responsible for different tasks. For me, I searched the images of birds and dandelions online to replace the eagle since birds that eat dandelion make more sense than the eagle. And then, I modified the code to load the image into the game. And I also designed the background and different endings’ interface on canvas but after Livvy and I both took a look at it. We thought we needed to redesign. So Livvy designed those parts, and I rearranged the pictures text in a more organized way.
I also found some songs for the audio effect and Livvy coded them into the Processing.
Here is our design. Previous one and current one:
To make the instruction clearer, I used cuttle.xyz and laser cutting to design a broad to make a more straightforward instruction. I will describe it in details in the latter section.
Our presentation shows that they were super effective and our audience didn’t look confused anymore. They quickly figured out how to interact with the two sensors.
FABRICATION AND PRODUCTION:
Project plan in project proposal:
This weekend + Monday: Test sensors’ value range(ultrasonic sensor and microphone sensor) + basic game rule development (player’s character moving and interacting with obstacles)
Tuesday + Wednesday + Thursday: Game mechanism development(player’s character interacting with the chaser, setting chaser’s speed) and aesthetic improvement on screen(we are going to make the setting or the background of the game cuter, adding or attaching some cute patterns on the obstacle and the background. Also, we will design the “game over” interface more lovable, maybe a little, cute crowd, says to the player, “The journey ends~ thanks for playing!” )
After User test: Improve the project based on user feedback + improve the physical settings.
Step 0: Preparation
Before we jumped into this project, we originally wanted to use a sensor to detect the volume of people’s voices to control the character because it would be very entertaining and engaging. To confirm our idea, we went to Professor Inmi’s office hour. However, through talking with her and thinking about her feedback, which conveyed that the usage of arduino would be unnecessary though it would be fun since we could also achieve that just using Processing and a laptop, we found that we had to replace the volume sensor with another inputting sensor. Then, we thought the ultrasonic sensor might be better because its function is unique and cannot be replaced by a laptop and it’s more straightforward for players to understand how they interact with the character. We also added the design of another input sensor to accelerate the dandelion in order to make the game more engaging. And we wanted to see if the microphone sensor could work or not.
Then, we assigned different tasks to each of us. For me, I had to first learn Object-Oriented Programming since this coding skill is the basis of our game. And I would code with Livvy for some basic functions and game mechanisms, which was really challenging for me, as well as decorations. Also, I took the responsibility of building up the circuit, testing sensor data, coding the Arduino part, designing the laser cutting board, and cooperating with Livvy in building the aesthetic part. Livvy focused more on the Processing major coding and designing the interface pictures of our game.
Step 1: Basic coding and sensor testing
After I learned about object-oriented programming on YouTube, we started coding the basic mechanism of the game. As the sensors were not completely decided at the moment, we used the mousekey as the input and controller. I coded the player, namely the dandelion moving part, with Livvy, and she coded the obstacles and other parts herself. The code is as follows:
class Player { float x=300; float xspeed; float y=height/2; float s=50; //float ny=map(arduino_values[0], 20, 0, 0, height); float smoothedY = y; // 初始化平滑后的y坐标 float alpha = 0.1; // 平滑因子,可根据实际情况调整 Player() { this.x = x; this.xspeed=xspeed; this.y = y; this.s = s; //this.ny=ny; this.smoothedY = this.y; } void update() { //float ny=map(arduino_values[0], 40, 0, 0, height); // y = constrain(ny, s/2, height - s/2); // y=ny; // println("y: ", arduino_values[0]); float rawY = map(arduino_values[0], 60, 0, 100, height-100); // 从Arduino读取原始值 smoothedY = alpha * rawY + (1 - alpha) * smoothedY; // 应用EMA平滑处理 y = constrain(smoothedY, s/2, height - s/2); // 确保y值在屏幕内 println("Raw Y: ", arduino_values[0], " Smoothed Y: ", smoothedY); float speedx = map(arduino_values[1], 0, 30, 0, 1); x= 300+ speedx; println("speedx:",arduino_values[1]); } void display() { fill(0); circle(x, y, s); } }
class Obstacle { float x = width;//original position float d = 50;//width float h = 300;//the height of the gap float gapBottom = random(200+h, height-h+200);//the bottom position of the gap float gapTop= gapBottom-h; boolean passed = false; Obstacle() { this.x = x; this.d = d; this.h = h; this.gapBottom = gapBottom; this.gapTop = gapTop; } void update() { x-=4; } void display() { fill(0); //rect(x, 0, d, gapTop);//up //rect(x, gapBottom, d, height-gapBottom);//down quad(x,0, ); } boolean collidesWith(Player player) { return (player.x + player.s/2 > x && player.x - player.s/2 < x+d) && (player.y - player.s/2 < gapTop || player.y + player.s/2 > gapBottom); //the right side of player is over the left side of the obstacle //Ans the left side of the player is not over the right side of the obstacle } boolean isPassedBy(Player player) {//add score if the obstacle is passed by the player if (!passed && player.x > x + d) { passed = true; return true; } return false; } }
class Chaser{ float x=0; float y=height/2; float s=100; Chaser(){ this.x=x; this.y=y; this.s=s; } void update(){ x+=0.01; } void display(){ fill(255,0,0); circle(x,y,s); } boolean collidesWith(Player player) { return dist(x,y,player.x,player.y) < (s+player.s)/2; } }
import processing.serial.*; Serial serialPort; int NUM_OF_VALUES_FROM_ARDUINO = 1; /* CHANGE THIS ACCORDING TO YOUR PROJECT */ /* This array stores values from Arduino */ int arduino_values[] = new int[NUM_OF_VALUES_FROM_ARDUINO]; ArrayList<Obstacle> obstacles; Player player; Chaser chaser; int score = 0; void setup() { printArray(Serial.list()); serialPort = new Serial(this, "/dev/cu.usbmodem101", 9600); //size(800,600); fullScreen(); obstacles = new ArrayList<Obstacle>(); player = new Player(); chaser = new Chaser(); } void draw() { getSerialData(); background(255, 255, 255); totalObstacles(); player.update(); player.display(); chaser.update(); chaser.display(); checkCollisions(); displayScore(); } void totalObstacles() { if (frameCount % 150 == 0) { obstacles.add(new Obstacle()); } for (int i = obstacles.size() - 1; i >= 0; i--) { Obstacle obs = obstacles.get(i); obs.update(); obs.display(); if (obs.x< -width) { obstacles.remove(i);//remove the obstacle if it's off screen } else if (obs.isPassedBy(player)) { score++; } } } void checkCollisions() { if (chaser.collidesWith(player)) { endGame("Chaser caught the player"); } for (Obstacle obs : obstacles) { if (obs.collidesWith(player)) { endGame("Collision with obstacle"); } } } void endGame(String message) { fill(150, 0, 0); rect(0, 0, width, height); fill(255); textSize(32); textAlign(CENTER, CENTER); text(message, width/2, height/2); noLoop(); } void displayScore() { fill(0); textSize(40); text("Score: " + score, width - 200, 40); } void getSerialData() { while (serialPort.available() > 0) { String in = serialPort.readStringUntil( 10 ); // 10 = '\n' Linefeed in ASCII if (in != null) { print("From Arduino: " + in); String[] serialInArray = split(trim(in), ","); if (serialInArray.length == NUM_OF_VALUES_FROM_ARDUINO) { for (int i=0; i<serialInArray.length; i++) { arduino_values[i] = int(serialInArray[i]); } } } } }
I also coded the Arduino part:
int triggerPin = 9; int echoPin = 10; long distance; float smoothing = 0.05; float smoothed; double flow; //Water flow L/Min int flowsensor = 2; unsigned long currentTime; unsigned long lastTime; unsigned long pulse_freq; void pulse () // Interrupt function { pulse_freq++; } void setup() { Serial.begin(9600); pinMode(triggerPin, OUTPUT); pinMode(echoPin, INPUT); pinMode(flowsensor, INPUT); Serial.begin(9600); attachInterrupt(0, pulse, RISING); // Setup Interrupt currentTime = millis(); lastTime = currentTime; } void loop() { digitalWrite(triggerPin, LOW); delayMicroseconds(2); digitalWrite(triggerPin, HIGH); delayMicroseconds(10); digitalWrite(triggerPin, LOW); long duration = pulseIn(echoPin, HIGH, 17400); distance = duration / 29 / 2; smoothed = smoothed * (1.0 - smoothing) + distance * smoothing; Serial.println(smoothed); delay(100); currentTime = millis(); // Every second, calculate and print L/Min if(currentTime >= (lastTime + 1000)) { lastTime = currentTime; // Pulse frequency (Hz) = 7.5Q, Q is flow rate in L/min. flow = (pulse_freq / 7.5); pulse_freq = 0; // Reset Counter Serial.print(flow, DEC); Serial.println(" L/Min"); } }
According to our plan, I tested the ultrasonic sensor’s data range and I tested whether the microphone sensor would work. Then, I came across some issues: the ultrasonic sensor’s data reading was not stable, sometimes some values really big jumped out and microphone sensor seemed not quite work.
Here’s how I tested it:
And here is how the game looked like at that time:
When I was struggling with the sensor problem, I saw Professor Andy was there. So I turned to him for help. I asked him about if there was any sensor like the “blow sensor” and he told me maybe flow sensor could work. So, with Andy’s help, I substituted the microphone sensor with a flow sensor. That worked! So, we modified both of our codes and this is how it works:
Step 2: Loading image
Based on our plan, we also had to decorate our project. We first thought we could code some special visual effects to decorate the game, for example, I wanted to use lines and quad functions to draw the bird. However, after we tried for a while, we found it too difficult and inefficient to decorate our project in this way. So, we changed our strategy to find other ways. I tested if the image function could work and modified the code a little bit to add the image function to represent the character. This is my modification:
PImage Dandelion; PImage Eagle; PImage Thorn; ArrayList<Obstacle> obstacles; Player player; Chaser chaser; void setup() { fullScreen(); obstacles = new ArrayList<Obstacle>(); player = new Player(); chaser = new Chaser(); Dandelion = loadImage("Thorn.png"); Eagle = loadImage("Eagle.png"); } void draw() { background(255, 255, 255); totalObstacles(); player.update(); player.display(); chaser.update(); chaser.display(); checkCollisions(); } void totalObstacles() { if (frameCount % 150 == 0) { obstacles.add(new Obstacle()); } for (int i = obstacles.size() - 1; i >= 0; i--) { Obstacle obs = obstacles.get(i); obs.update(); obs.display(); if (obs.x< -width) { obstacles.remove(i); } } } void checkCollisions() { if (chaser.collidesWith(player)) { endGame("Chaser caught the player"); } for (Obstacle obs : obstacles) { if (obs.collidesWith(player)) { endGame("Collision with obstacle"); } } } void endGame(String message) { fill(255, 0, 0); rect(0, 0, width, height); fill(255); textSize(32); textAlign(CENTER, CENTER); text(message, width / 2, height / 2); noLoop(); }
This is our game after I ran the code:
In light of my discovery, we decided to use the image function to decorate. Then, we found some images of thorns, vines, birds, eagles and dandelions on the website called “mksucai”.https://www.mksucai.com/
Livvy and I then coded these images into the game, as the version for the user testing:
And here are the code before the user tests:
Processing:
PImage dandelion; PImage vine; PImage eagle; import processing.serial.*; Serial serialPort; int NUM_OF_VALUES_FROM_ARDUINO = 2; /* CHANGE THIS ACCORDING TO YOUR PROJECT */ /* This array stores values from Arduino */ int arduino_values[] = new int[NUM_OF_VALUES_FROM_ARDUINO]; ArrayList<Obstacle> obstacles; Player player; Chaser chaser; int score = 0; void setup() { dandelion = loadImage("dandelion.png"); vine = loadImage("vine.png"); eagle = loadImage("eagle.png"); printArray(Serial.list()); serialPort = new Serial(this, "COM5", 9600); //size(800,600); fullScreen(); obstacles = new ArrayList<Obstacle>(); player = new Player(); chaser = new Chaser(); } void draw() { getSerialData(); background(214,247,255); totalObstacles(); player.update(); player.display(); chaser.update(); chaser.display(); checkCollisions(); displayScore(); } void totalObstacles() { if (frameCount % 150 == 0) { obstacles.add(new Obstacle()); } for (int i = obstacles.size() - 1; i >= 0; i--) { Obstacle obs = obstacles.get(i); obs.update(); obs.display(); if (obs.x< -width) { obstacles.remove(i);//remove the obstacle if it's off screen } else if (obs.isPassedBy(player)) { score++; } } } void checkCollisions() { if (chaser.collidesWith(player)) { endGame("The bird caught the danlelion"); } for (Obstacle obs : obstacles) { if (obs.collidesWith(player)) { endGame("The danlelion fell on the vine"); } } } void endGame(String message) { fill(214,247,255); rect(0, 0, width, height); fill(0); textSize(32); textAlign(CENTER, CENTER); text(message, width/2, height/2); text("Your score is :"+ score,width/2,height/2+100); noLoop(); } void displayScore() { fill(0); textSize(40); text("Score: " + score, width - 200, 40); } void getSerialData() { while (serialPort.available() > 0) { String in = serialPort.readStringUntil( 10 ); // 10 = '\n' Linefeed in ASCII if (in != null) { print("From Arduino: " + in); String[] serialInArray = split(trim(in), ","); if (serialInArray.length == NUM_OF_VALUES_FROM_ARDUINO) { for (int i=0; i<serialInArray.length; i++) { arduino_values[i] = int(serialInArray[i]); } } } } }
class Chaser{ float x=0; float y=height/2; float s=100; Chaser(){ this.x=x; this.y=y; this.s=s; } void update(){ x+=0.05; } void display(){ //fill(255,0,0); //circle(x,y,s); image(eagle,x,y,s+200,s+200); } boolean collidesWith(Player player) { return dist(x,y,player.x,player.y) < (s+player.s)/2; } }
class Obstacle { float x = width; // original position float d = 50; // width float h = 300; // the height of the gap float gapBottom = random(200+h, height-h+200); // the bottom position of the gap float gapTop = gapBottom - h; boolean passed = false; //color colorA = color(127, 76, 27); //color colorB = color(127, 35, 60); color colorA = color(73, 149, 44); color colorB = color(#1D480C); void update() { x -= 4; } void display() { //divide a single obstacle into several numbers of i to realize the effect of lerpcolor for (int i = 0; i < 30; i++) { float position = map(i, 0, 30, 0, 1); color LerpedColor = lerpColor(colorA, colorB, position); fill(LerpedColor); float sd = d/30;//segment width rect(x + i * sd, 0, sd, gapTop); // up rect(x + i * sd, gapBottom, sd, height - gapBottom); // down } image(vine,x, 0, d, gapTop); image(vine,x, gapBottom, d, height-gapBottom); } boolean collidesWith(Player player) { return (player.x + player.s/2 > x && player.x - player.s/2 < x+d) && (player.y - player.s/2 < gapTop || player.y + player.s/2 > gapBottom); //the right side of player is over the left side of the obstacle //Ans the left side of the player is not over the right side of the obstacle } boolean isPassedBy(Player player) {//add score if the obstacle is passed by the player if (!passed && player.x > x + d) { passed = true; return true; } return false; } }
class Player { float x=300; float xspeed; float y=height/2; float s=50; float smoothedY = y; // 初始化平滑后的y坐标 float alpha = 0.1; // 平滑因子,可根据实际情况调整 Player() { this.x = x; this.xspeed=xspeed; this.y = y; this.s = s; this.smoothedY = this.y; } void update() { float rawY = map(arduino_values[0], 60, 0, 100, height-100); // read raw value from Arduino smoothedY = alpha * rawY + (1 - alpha) * smoothedY; // EMA平滑处理 y = constrain(smoothedY, s/2, height - s/2); //ensure y is in the screen println("Raw Y: ", arduino_values[0], " Smoothed Y: ", smoothedY); float xspeed=map(arduino_values[1], 0, 30, 0, 2); x=x+xspeed; println(xspeed); } void display() { //fill(0); //circle(x, y, s); image(dandelion,x,y,s+50,s+50); } }
Arduino:
#include /** * Is possible set the timeout in the constructor, like: * Ultrasonic ultrasonic(12, 13, 20000UL); */ Ultrasonic ultrasonic(13, 12); double flow; //Water flow L/Min int flowsensor = 2; unsigned long currentTime; unsigned long lastTime; unsigned long pulse_freq; void pulse () // Interrupt function { pulse_freq++; } void setup() { Serial.begin(115200); ultrasonic.setTimeout(40000UL); pinMode(flowsensor, INPUT); attachInterrupt(0, pulse, RISING); // Setup Interrupt currentTime = millis(); lastTime = currentTime; } void loop() { currentTime = millis(); // Every second, calculate and print L/Min if(currentTime >= (lastTime + 100))//can be changed { lastTime = currentTime; // Pulse frequency (Hz) = 7.5Q, Q is flow rate in L/min. flow = (pulse_freq / 7.5); pulse_freq = 0; // Reset Counter Serial.print(ultrasonic.read()); //delay(100); Serial.print(","); Serial.print(flow, DEC); Serial.println(); //Serial.println(" L/Min"); } }
step 3: adjustment
After we got feedback from the user testing, we made some adjustments immediately, as I mentioned in the previous section. Here are some details to add. The two adjustments that I was responsible for were the laser cut part and the audio part. Due to the player’s confusion about hand-moving range, I decided to visualize the hand-moving range instruction on a plywood board by laser cutting. And I also designed some hints on the rules of the game as well as a wooden box to contain the ultrasonic sensor and the circuit. This is my design on cuttle.xyz:
The laser cutting part:
After I designed it and put it on the table, I saw Inmi and she wanted to take a look at our design. Then, I showed her the board I designed. And I got some super valuable feedback: Inmi first said she likes the design, especially the bird which is cute. However, there were also some issues. The most significant issue was the hand-moving range and arrows were too dispersed, making people misunderstand it; Secondly, she said the dandelion was also confusing since it’s not recognizable; at last, she thought the size of the instruction should change a little since the words were all on the same size so people wouldn’t know which one was crucial.
Based on Inmi’s feedback, I adjusted my design, making the title’s font bigger, hand-moving range, the arrows more condensed, and deleting the dandelion. Here’s what it looked like for the final version:
We also searched for some forest-themed music on the internet, which is in line with our project setting of nature. Since our goal is to relax people and entertain them, we selected some soothing music, and we also found some clips of birds chirping and windy sounds as audible output to inform players of the accelerating process and game ending.
Here’s the sound we used:
And the final version of our code for Processing(Arduino is the same as its previous version):
PImage dandelion, vine, sparrow; PImage Introduction, Instructions, Game, Ending1, Ending2; import processing.sound.*; SoundFile sound; SoundFile soundLeaf; SoundFile soundBird; SoundFile soundWind; import processing.serial.*; Serial serialPort; int NUM_OF_VALUES_FROM_ARDUINO = 2; /* This array stores values from Arduino */ int arduino_values[] = new int[NUM_OF_VALUES_FROM_ARDUINO]; ArrayList<Obstacle> obstacles; Player player; Chaser chaser; int score = 0; String gameState = "INTRODUCTION"; int Ending; void setup() { dandelion = loadImage("dandelion.png"); vine = loadImage("vine.png"); sparrow = loadImage("sparrow.png"); Introduction = loadImage("Introduction.png"); Instructions = loadImage("Instructions.png"); Game = loadImage("Game.png"); Ending1 = loadImage("Ending1_Obs.png"); Ending2 = loadImage("Ending2_Bird.png"); sound = new SoundFile(this, "forest.mp3"); sound.loop(); soundLeaf = new SoundFile(this, "leaf.wav"); soundBird = new SoundFile(this, "bird.wav"); soundWind = new SoundFile(this, "wind.mp3"); printArray(Serial.list()); serialPort = new Serial(this, "/dev/cu.usbmodem101", 115200); //size(1920,1080); fullScreen(); obstacles = new ArrayList<Obstacle>(); player = new Player(); chaser = new Chaser(); } void draw() { getSerialData(); background(214, 247, 255); switch (gameState) { case "INTRODUCTION": drawIntroduction(); break; case "INSTRUCTION": drawInstruction(); break; case "GAME": runGame(); break; case "END": if (Ending == 1) { endGame(Ending1); //soundLeaf.loop(); if (soundLeaf.isPlaying() == false) { soundLeaf.play(); } } if (Ending == 2) { endGame(Ending2); //soundBird.loop(); if (soundBird.isPlaying() == false) { soundBird.play(); } } break; } } void keyPressed() { if (gameState.equals("INTRODUCTION") && key == 'i') { gameState = "INSTRUCTION"; } else if (gameState.equals("INSTRUCTION") && key == 's') { gameState = "GAME"; setupGame(); } else if (gameState.equals("END") && key == 'r') { gameState = "INTRODUCTION"; } } void drawIntroduction() { image(Introduction, 0, 0, 1440, 900);//the size of my mac screen //text("Welcome to the Game!\nPress 'I' to go to Instructions.", width / 2, height / 2); } void drawInstruction() { image(Instructions, 0, 0, 1440, 900); //text("Game Instructions:\nPress 'S' to start the game.", width / 2, height / 2); } void setupGame() { fullScreen(); obstacles = new ArrayList<Obstacle>(); player = new Player(); chaser = new Chaser(); image(Game, 0, 0, 1440, 900); soundWind = new SoundFile(this, "wind.mp3"); } void runGame() { image(Game, 0, 0, 1440, 900); totalObstacles(); player.update(); player.display(); chaser.update(); chaser.display(); checkCollisions(); displayScore(); } void totalObstacles() { if (frameCount % 80 == 0) { obstacles.add(new Obstacle()); } for (int i = obstacles.size() - 1; i >= 0; i--) { Obstacle obs = obstacles.get(i); obs.update(); obs.display(); if (obs.x< -width) { obstacles.remove(i);//remove the obstacle if it's off screen } else if (obs.isPassedBy(player)) { score++; } } } void checkCollisions() { if (chaser.collidesWith(player)) { Ending = 2; endGame(Ending2); } for (Obstacle obs : obstacles) { if (obs.collidesWith(player)) { Ending = 1; endGame(Ending1); } } } void endGame(PImage picture) { gameState = "END"; image(picture, 0, 0, 1440, 900); textSize(100); textAlign(CENTER, CENTER); text(score, 150+width/2, height/2+50); } void displayScore() { fill(255); textSize(50); text("Score: " + score, width - 200, 80); } void getSerialData() { while (serialPort.available() > 0) { String in = serialPort.readStringUntil( 10 ); // 10 = '\n' Linefeed in ASCII if (in != null) { print("From Arduino: " + in); String[] serialInArray = split(trim(in), ","); if (serialInArray.length == NUM_OF_VALUES_FROM_ARDUINO) { for (int i=0; i<serialInArray.length; i++) { arduino_values[i] = int(serialInArray[i]); } } } } }
CONCLUSIONS:
The main goal of our project is to provide people with a space for entertainment where they can completely relax both physically and mentally in a tense and high-pressure living environment. For a deeper meaning of this project, we hope to influence people’s values through it. In this era of pursuing meaning and achievement, people always strive to pursue various things, such as grades, money, and power, and use these as standards to judge whether a person’s life is a success or failure. However, the message we hope to convey through this project is that there are no wins or losses in life, and there is no need to chase after anything. Therefore, we designed the game’s music to be healing and gentle, and the entire game style to be rather warm. At the end of the game, we display everyone’s scores along with a slogan: “Your seed has been spread.” So, we want to say that life is merely a journey, and there is no such thing as failure.
At last, I would like to thank my great partner Livvy and my great professor Inmi! Thanks for your patience and guidance to us! It’s my pleasure to work with you guys this semester!
DISASSEMBLY:
APPENDIX
Circuit Diagram:
“Be One with Flappy Bird: The Science of ‘flow’ in Game Design.” Scientific American, 20 Feb. 2024, www.scientificamerican.com/article/be-one-with-flappy-bird-the-science-of-flow-in-game-design/.
“birds chirping calm.” zehendrew. pixabay. 2023. https://pixabay.com/sound-effects/birds-chirping-calm-173695/.
Bawane, Swapnil D. “Life is a journey.” Medium, 2023. https://swapnilbawane.medium.com/life-is-a-journey-2df19925407d.
“howling wind.” Liecio. pixabay. 2022. https://pixabay.com/sound-effects/howling-wind-109590/.
“In the Forest – Ambient Acoustic Guitar Instrumental Background Music (IG Version 60s).” Lesfem. pixabay. 2021. https://pixabay.com/music/solo-guitar-in-the-forest-ambient-acoustic-guitar-instrumental-background-music-ig-version-60s-9646/.
Simoes, Erick. Ultrasonic Timeout.ino. 2018. https://github.com/ErickSimoes/Ultrasonic/blob/master/examples/Timeout/Timeout.ino.
“Wind in Trees.” SoundsForYou. pixiabay. 2022. https://pixabay.com/sound-effects/wind-in-trees-117477/.
Zhang, Wenyi. “China: Share of People Feeling More Stressed 2022.” Statista, 22 July 2022, www.statista.com/statistics/1321615/china-share-of-people-feeling-more-stressed/.