okay I’m rewriting this blog because I am psychologically strong as my previous 400-word-thing disappeared for no reason… starting over again awwwhhhh
A. FinFriend – Beatrice & Evelyn – Prof. Andy
B. CONCEPTION AND DESIGN
Our idea is inspired my previous experience of raising a fish. Personally I’ve had many goldfish before but the first of them all died very quickly:( until I had a fish that surprisingly survived and accompanied me for 4 years. The death of the fish could be very sad, but it’s what is happening everyday and the sorrow is inevitable. We want to show how hard it is for people to take good care of a fish friend. The interactions we decided in the first stage(and still applied to the final version) are cleaning(tilt sensor), feeding(with distant sensor) and playing(pressing the button). The user can restart the game by pressing another button.
Initially we intended to design a game that has many stages in accordance with the fish’s lifecycle featuring different specified requirements, but we only designed one stage before user-testing due to the time limitation. However, we thought later that it already worked well with one stage, and to make it more reality-based, the user will get a new fish with different looking after restarting the game. To achieve the same goal, we also designed co-effects like ‘if play with the fish, it will get hungrier’ and ‘if feed the fish, the fish tank gets dirtier’.
During user-testing, over-triggering problem still exist(Anya killed the fish only because she cleaned the tank for 2 times) and the screen looked tilted without supporting from the back. On top of that, the values on the screen as well as the visuals are not intuitive and interactive enough for users to understand whether they had met the requirements.
To solve these problems, we edited the code to make the tilt sensor work well. Additionally, we made it into a box that can hold our Arduino and breadboards. We also changed the value range into 0-15 instead of 5-10 and added color bar to indicate whether the requirement is satisfied.(red and green) To make the fish more visually attractive, when playing with it, we added ‘rotate for one round when playing’ and ‘grow bigger when feeding’. The cleanness is indicated by the transparency of the aquarium.
At the final stage, we added neon pixels to make it more aquarium-like(blue when playing the game, red when the fish died, green when mission completed).
C.FABRICATION AND PRODUCTION
We laser-cut the box and 3D printed the decorations that appears in the ‘frame’ of the aquarium. I selected the semi-transparent looking board instead of the white one and the totally transparent one, as it works best both in terms of hiding the electronics inside and visual attractiveness. It works well with the LED as well.
One major step of our production process is when we switched from testing the buttons and other electronic components to making a real game prototype. The testing really took a whole lot of time for both of us to debug in terms of the value shown on Arduino. And the adjustments that were made after the user-testing session as well-the change in editing the critical values to make the game more approachable to the general audience. It turned out later in the ima show that the audience have an overall 50% winning probability which is satisfying 🙂 adding the sound effects is also a wise choice
D.CONCLUSIONS
This is definitely a giant leap from the midterm-we made progress and gained success and praise more than we expect😄 From my stance it achieved our initial goal-reminding people of the hardness of raising a fish and the happiness of successfully making the fish survive. I remembered in the final IMA Show, Prof. Meloccaro tried our game and the fish tragically died, he praised our project by saying ‘it’s just so close to life…’ haha as well as another prof that I didn’t know. The introduction video is quite clear, so most of the audience acted the way we expected, except for one or two of them wondering if the cleaner was used to clean the fish food that had dropped onto the floor.
This project fits my understanding of interaction as well-the audience saw the fish and the initial values, deciding on what to do next, and by looking at how the fish react they receive instant feedback and the loop of interaction begins. But there’s still room for improvement-if given more time, I would consider making the ‘playing’ part more fun and interactive and maybe adding more fish to the aquarium, making the game into ‘how many fish has survived’ (something like that).
Through the completion of my final project, I have gained valuable insights and takeaways that will benefit my further studies. Firstly, I have honed my skills in user experience design and interactive technologies. Developing the game required a deep understanding of user preferences, intuitive interfaces, and engaging interactions. This hands-on experience has equipped me with practical knowledge that I can apply to future projects and courses in the field of human-computer interaction.
Additionally, the project allowed me to explore the importance of feedback and responsiveness in interactive systems. By implementing features such as visual and auditory cues to indicate fish feeding success or failure, I learned how immediate and meaningful feedback influences user engagement and satisfaction. This understanding will be invaluable as I continue to design and create interactive experiences that are both enjoyable and user-friendly.
Lastly, the completion of the fish feeding interactive game reinforced the significance of user-centered design. By conducting user testing and incorporating feedback throughout the development process, I recognized the importance of considering the needs, preferences, and limitations of the target audience. This user-centric approach ensures that the interactive system is not only functional and visually appealing but also meets the expectations and requirements of the users.
E.DISASSEMBLY
I forgot to take a pic but we have already returned the screen(thank u so much for lending it to us!!)and other electronics not belonged to us.
F. APPENDIX
circuits:
additional photos:
⬇️lav this woman❤️😘💓
documentation:
code:
from Arduino: int val1; // for feeding int prevVal1; int val2; // for playing int prevVal2; int pressureValue; int prepressureValue; const int pressureSensorPin = A0; // cleaning int satietyVal = 5; // number of times int happinessVal = 0; int cleaning = 5; void setup() { Serial.begin(9600); pinMode(2, INPUT); pinMode(3, INPUT); pinMode(pressureSensorPin, INPUT); } void loop() { val1 = digitalRead(2); val2 = digitalRead(3); if (prevVal1 == LOW && val1 == HIGH) { satietyVal = satietyVal + 1; // increase //Serial.println("Feeding triggered!"); } prevVal1 = val1; if (prevVal2 == LOW && val2 == HIGH) { happinessVal = happinessVal + 1; // increase satietyVal = satietyVal - 1; cleaning = cleaning - 1; //Serial.println("Playing triggered!"); } prevVal2 = val2; pressureValue = analogRead(pressureSensorPin); if (pressureValue > 500 && prepressureValue < 500) { // Adjust the threshold based on your sensor readings cleaning++; //Serial.println("Cleaning triggered!"); } prepressureValue = pressureValue; // Serial.print(cleaning); Serial.print(","); Serial.print(satietyVal); Serial.print(","); Serial.print(happinessVal); Serial.println(); delay(10); // if (satietyVal < 0) { // Stop the entire game stopGame(); } if (satietyVal > 10) { stopGame(); } if (cleaning < 0) { stopGame(); } if (cleaning > 10) { stopGame(); } } void stopGame() { // Code to stop the game //Serial.println("YOUR FISH DIED!"); // You might want to add additional code here to gracefully handle the game stop // For example, save scores, clean up resources, etc. // Optionally, you can put the Arduino into a sleep state or loop indefinitely while (true) { // Loop indefinitely or put the Arduino to sleep } } from processing:
import processing.serial.*; import processing.sound.*; SoundFile eating; SoundFile playing; SoundFile clean; SoundFile music; SoundFile music2; SoundFile music3; Serial serialPort; int NUM_OF_VALUES_FROM_ARDUINO = 5; int arduino_values[] = new int[NUM_OF_VALUES_FROM_ARDUINO]; Serial serialPort2; import processing.video.*; Movie moviefood; Movie moviecleaning; Movie movieintroduction; boolean introductionPlaying = true; boolean foodPlaying = false; boolean foodFalling = false; boolean cleaningPlaying = false; boolean game = true; boolean celebration = false; PImage ghost; PImage fish; PImage display; PImage background; float scaleFactor = 1.0;//for fish boolean[] mirror; int q=1; float[] x = new float [q]; float[] y = new float [q]; float[] xspeed = new float [q]; float[] yspeed = new float [q]; int qf=30; float[] xf = new float [qf]; float[] yf = new float [qf]; float[] xspeedf = new float [qf]; float[] yspeedf = new float [qf]; float satiety =5; float cleanness=5; float happiness =0; int gameOverReason = 0; int startTime; float angle= 0; boolean animate = false; int startTimeText; boolean showText= false; int displayTextTime =1000; String satietyText ="satiety+1"; boolean satietytrigger=false; boolean cleaningtrigger=false; boolean playingtrigger=false; PImage happy; PImage sad; PImage[] images = new PImage[5]; int randomIndex; PFont font; int NUM_LEDS = 60; // How many LEDs in your strip? color[] leds = new color[NUM_LEDS]; // array of one color for each pixel void setup() { font = createFont("font.TTF", 30); textFont(font); size(1280, 720, P2D); background(255); printArray(Serial.list()); serialPort = new Serial(this, "/dev/cu.usbmodem1401", 115200); serialPort2 = new Serial(this, "/dev/cu.usbmodem1101", 115200); moviefood = new Movie(this, "food.mov"); moviecleaning = new Movie(this, "cleaning.mov"); movieintroduction = new Movie(this, "introduction.mp4"); movieintroduction.play(); ghost = loadImage("ghost.png"); //fish = loadImage("fish.png"); background = loadImage("1401700540245_.pic.jpg"); //display = fish; for (int i = 0; i < images.length; i++) { images[i] = loadImage("fish" + i + ".png"); } randomIndex = int(random(images.length)); display = images[randomIndex] ; sad = loadImage("sad.png"); happy = loadImage("happy.png"); eating = new SoundFile(this, "eating.mp3"); playing = new SoundFile(this, "playing.mp3"); clean = new SoundFile(this, "clean.mp3"); music = new SoundFile(this, "music.mp3"); music2 = new SoundFile(this, "music2.mp3"); music3 = new SoundFile(this, "music3.mp3"); music.loop(); music2.loop(); music3.loop(); music.amp(0); music2.amp(0); music3.amp(0); mirror = new boolean[q]; for (int i=0; i<q; i++) { x[i] = random(0, width); y[i] = random (0, height); xspeed[i] = random(-3, 3); yspeed[i] = random(-1, 1); mirror[i] = false; } for (int i=0; i<qf; i++) { xf[i] = random(0, width); yf[i] = -10; xf[i] = xf[i] + xspeedf[i]; yf[i] = yf[i] + yspeedf[i]; fill(#F0C879); strokeWeight(1.3); circle (xf[i], yf[i], 15); } } void draw() { background(255); // getSerialData(); if (movieintroduction.available()) { movieintroduction.read(); } image(movieintroduction, 0, 0, width, height); print("movie play"); if (movieintroduction.time() >= movieintroduction.duration()*0.99) { introductionPlaying = false; movieintroduction.stop(); startTime = millis(); } if ( introductionPlaying == false) { if (arduino_values[2] == 1) { animate = true; } if (animate) { angle += 0.9; } if (angle >= TWO_PI) { animate = false; angle = 0; } int elapsedTime = millis() - startTime; int Time = 45000-elapsedTime; imageMode(CORNER); image(background, 0, 0, width, height); float cleannessVal = map(cleanness, 0, 15, 0, 200); fill(255); rect(150, 20, 200, 40, 50); if (cleanness<6) { fill(255, 0, 0); } else { fill(0, 255, 0); } rect(150, 20, cleannessVal, 40, 50); textSize(25); fill(0); text("cleanness:"+ cleanness, 155, 50); float satietyVal = map(satiety, 0, 15, 0, 200); fill(255); rect(550, 20, 200, 40, 50); if (satiety<7) { fill(255, 0, 0); } else { fill(0, 255, 0); } rect(550, 20, satietyVal, 40, 50); textSize(25); fill(0); text("satiety:"+ satiety, 570, 50); //text("happiness:"+ happiness, 950, 20); float timeVal = map(Time, 45000, 0, 200, 0); fill(255); rect(950, 20, 200, 40, 50); if (Time<10000 && Time>0) { fill(255, 0, 0); rect(950, 20, timeVal, 40, 50); textSize(25); fill(0); text("time:"+Time / 1000, 990, 50); } else if (Time>=10000) { fill(0, 255, 0); rect(950, 20, timeVal, 40, 50); textSize(25); fill(0); text("time:"+Time / 1000, 990, 50); } else { textSize(25); fill(0); text("time:0", 990, 50); } for (int i=0; i<q; i++) { x[i] = x[i] + xspeed[i]; y[i] = y[i] + yspeed[i]; if (x[i]>width-80) { xspeed[i]=random(-3, 0); mirror[i] = false; } if (x[i]<40) { xspeed[i]=random(0, 3); mirror[i] = true; } if (y[i]>height-100) { yspeed[i]=random(-1, 0); } if (y[i]<40) { yspeed[i]=random(0, 1); } if (mirror[i] == true) { pushMatrix(); scale(-1, 1); imageMode(CENTER); translate(-x[i], y[i]); rotate(angle); image(display, 0, 0, 90 * scaleFactor, 80 * scaleFactor); popMatrix(); } if (mirror[i] == false) { pushMatrix(); imageMode(CENTER); translate(x[i], y[i]); rotate(angle); image(display, 0, 0, 90 * scaleFactor, 80 * scaleFactor); popMatrix(); } } if (game == false) { float progress2 = music2.position() / music2.duration(); for (int i=0; i < NUM_LEDS; i++) { // loop through each pixel in the strip if (i < progress2 * NUM_LEDS) { // based on where we are in the song leds[i] = color(255, 0, 0); // turn a pixel to red } else { leds[i] = color(0, 0, 0); } } sendColors(); music.amp(0); music2.amp(1); //music3.amp(0); display=ghost; textSize(135); fill(0); filter(GRAY); text("YOUR FISH DIED!", 155, height/2+80); textSize(25); text("CLICK THE RED BUTTON TO RESTART!", 700, 590); // Stop the entire game if (gameOverReason == 1) { textSize(135); fill(0); text("TOO HUNGRY!", 250, height/2-80); } else if (gameOverReason == 2) { textSize(135); fill(0); text("TOO DIRTY!", 280, height/2-80); } else if (gameOverReason == 3) { textSize(135); fill(0); text("TOO CLEAN!", 280, height/2-80); } else if (gameOverReason == 4) { textSize(135); fill(0); text("NOT HAPPY ENOUGH!", 120, height/2-80); } else if (gameOverReason == 5) { textSize(120); fill(0); text("TOO MANY REASONS!", 120, height/2-80); } else if (gameOverReason == 6) { textSize(135); fill(0); text("TOO FULL!", 280, height/2-80); } } if (game == true) { music.amp(1); music2.amp(0); display = images[randomIndex]; //display=fish; float transparency = map(cleanness, 0, 15, 250, 0); //fill(180, 180, 120, transparency); fill(80, 150, 230, transparency); rect(0, 0, width, height); if (mirror[0] == true) { if (happiness>=3) { image(happy, x[0], y[0]-50, 30, 30); } else { image(sad, x[0], y[0]-50, 30, 30); } } if (mirror[0] == false) { if (happiness>=3) { image(happy, x[0], y[0]-50, 30, 30); } else { image(sad, x[0], y[0]-50, 30, 30); } } if (showText==true) { if (satietytrigger==true) { textSize(20); fill(0); text("satiety+1", x[0]-30, y[0]-140); } if (cleaningtrigger==true) { textSize(20); fill(0); text("cleanness+1", x[0]-30, y[0]-120); } if (playingtrigger==true) { textSize(20); fill(0); text("happiness+1", x[0]-30, y[0]-100); text("satiety-0.5", x[0]-30, y[0]-80); } } if ( millis() -startTimeText > displayTextTime) { showText = false; // Hide the text satietytrigger= false; playingtrigger= false; cleaningtrigger= false; } if (arduino_values[1] == 1) { moviefood.play(); foodPlaying = true; foodFalling = true; cleanness = cleanness -0.5; for (int i=0; i<qf; i++) { xspeedf[i] = random(-0.5, 0.5); yspeedf[i] = random(2, 3); } } if (foodPlaying == true) { if (moviefood.available()) { moviefood.read(); image(moviefood, 180, 150, 300, 300); if (moviefood.time() >= moviefood.duration() *0.95) { foodPlaying = false; moviefood.stop(); } } } if (foodFalling == true) { for (int i=0; i<qf; i++) { circle (xf[i], yf[i], 15); fill(#F0C879); strokeWeight(1.3); yf[i] = yf[i] + yspeedf[i]; if (dist(x[0]+40, y[0]+45, xf[i], yf[i]) <= 40) { satiety = satiety +1; showText= true; satietytrigger=true; startTimeText=millis(); yf[i]=-50; yspeedf[i] = 0; scaleFactor *= 1.1; scaleFactor = max(1.0, scaleFactor); eating.play(); } if (yf[i]>height) { yf[i]=-50; yspeedf[i] = 0; } } } if (foodFalling == false) { for (int i=0; i<qf; i++) { xspeedf[i]=0; yspeedf[i]=0; } } if (arduino_values[0] == 1 && showText==false ) { startTimeText=millis(); showText=true; cleaningtrigger=true; cleanness = cleanness +1; clean.play(); moviecleaning.play(); cleaningPlaying = true; } if (cleaningPlaying == true) { if (moviecleaning.available()) { moviecleaning.read(); image(moviecleaning, 900, 420, 500, 500); if (moviecleaning.time() >= moviecleaning.duration() *0.95) { cleaningPlaying = false; moviecleaning.stop(); } } } if (arduino_values[2] == 1 && showText==false) { startTimeText=millis(); showText=true; playingtrigger=true; playing.play(); happiness = happiness +1; satiety = satiety -0.5; } // Stop the entire game if (satiety < 0) { gameOverReason = 1; game =false; } else if (satiety > 15) { game =false; gameOverReason = 6; } else if (cleanness < 0) { gameOverReason = 2; game =false; } else if (cleanness > 15) { cleaningPlaying = false; gameOverReason = 3; game = false; } } if (arduino_values[4] == 1) { startTime = millis(); scaleFactor = 1.0; game = true; satiety =5; cleanness=5; happiness =0; celebration=false; randomIndex = int(random(images.length)); display = images[randomIndex] ; } if (elapsedTime/1000 == 45) { if (satiety >= 7 && happiness >=3 && cleanness >=6) { game = true; celebration = true; } else { game = false; if (satiety < 7 && happiness >=3 && cleanness >=6) { gameOverReason = 1; } else if (happiness <3 && cleanness >=6 && satiety >= 7) { gameOverReason = 4; } else if (cleanness <6 && satiety >= 7 && happiness >=3) { gameOverReason = 2; } else { gameOverReason = 5; } } } // fill(0); //text(frameRate, 50, 50); if (celebration==false && game==true) { float progress = map(frameCount % 100, 0, 99, 0, 1); //float progress = music.position() / music.duration(); for (int i=0; i < NUM_LEDS; i++) { // loop through each pixel in the strip if (i < progress * NUM_LEDS) { // based on where we are in the song leds[i] = color(0, 0, 255); // turn a pixel to red } else { leds[i] = color(0, 0, 0); } } } if (celebration==true && game==true) { float progress3 = map(frameCount % 500, 0, 499, 0, 1); for (int i=0; i < NUM_LEDS; i++) { // loop through each pixel in the strip if (i < progress3 * NUM_LEDS) { // based on where we are in the song leds[i] = color(0, 255, 0); // turn a pixel to red } else { leds[i] = color(0, 0, 0); } } } sendColors(); if (celebration == true) { music3.amp(1); textSize(115); fill(0); text("CONGRATULATIONS!", 145, height/2-60); text("GOAL ACHIEVED!", 200, height/2+60); } else { music3.amp(0); } } } void getSerialData() { // Read the cleaning value from the Arduino while (serialPort.available() > 0) { String in = serialPort.readStringUntil( 10 ); 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]); } } } } } void sendColors() { byte[] out = new byte[NUM_LEDS*3]; for (int i=0; i < NUM_LEDS; i++) { out[i*3] = (byte)(floor(red(leds[i])) >> 1); if (i == 0) { out[0] |= 1 << 7; } out[i*3+1] = (byte)(floor(green(leds[i])) >> 1); out[i*3+2] = (byte)(floor(blue(leds[i])) >> 1); } serialPort2.write(out); }