Categories
Interaction Lab

Final Project Individual Reflection

Project Title: Food except 🐑

Creator: Ran Xu (Charlotte)

Instructor Name: Prof. Marcela Godoy

CONCEPTION AND DESIGN

This project is based on the current situation in Shanghai and aims at people who are locked down in Shanghai and worried about their food supply. People are anxious about whether they can get enough food and things that are normally within reach now become luxuries, for example, fast food and bubble tea. We would like to make a game world where people can fetch the food they want, such as cakes and bubble tea, and through an unrealistic way like “天上掉馅饼”( a saying in China that means getting something without hard work. It can be translated directly as ” pies falling down from the sky without effort”)

We want users to release their stress while playing the game. So we use the microphone to control the beginning of the game. We also use the pressure sensor to control the hunter and hang it on the wall. In that case, users can make some exercise at the same time.

During the user test, we found the difficulty of the game is too high and people fail the game easily since the sheep always fall down with the food and it is hard to avoid. I thought it is not helpful for stressed people during the lockdown time, so we reduce the speed of the sheep. I think this change is effective since the user experience becomes better after changing.

In addition, we change the caught sound of different food. When people catch basic food such as vegetables and meat, the sound is like “duu–“. And when people catch snacks such as cakes and ice creams, the sound is relatively high. When people catch sheep which means positive, the sound is relatively low.

We also add a score detail display to the game. The detail of the score will show in the right-down corner when the hunter catches the food.

 

FABRICATION AND PRODUCTION

Younian and I cooperate to complete this project. Since we don’t live together, we each finished the physical part.

In the coding process, I complete the original version of this game. I control the hunter with the variable resistors at first.I draw the image of the hunter with Processing and let food fall from the top of the screen at different times. But later Younian found that we only need to let the hunter move right and left. Saving it as an image will be better. So we use the image of the hunter rather than the code.Younian helps me to make the falling code more graceful since in the original one every food has a long code.

In addition, she solved the problem of interaction between the hunter and the food and complete the control of the beginning and the end of the game. Then I complete the timer of the game and she finfish the scoring record part. After we talk about the ending, I add a video to the winning part. At last, we talk about the storyline of the video and shoot the video separately. And I edit the video.

I think the most successful part is the coding part. We have never made such a big code and we still have a lot to learn. During this part, we searched for relative information and tutoring videos online and ask professor Marcela for help. We also cooperate to solve each other’s problems and improve the design of this game. Younian has the original idea of this game and at first, this game tends to show the pressure during lockdown time. But after we talked together and think about the aim audience, we found that maybe a light-hearted game is more suitable for people locked down at home. So we change the code accordingly.

From my perspective, the failure of this game is the sensor. Since each of us only has one vibration sensor. We need to give up the original idea of controlling the hunter by punching pillows. But we still want to relieve people’s stress so we use the pressure sensor. But we found it is hard for us to decorate it since it is not very sensitive. I try to stick a rubber decompression toy on it and control the hunter by pressing the toy, but it doesn’t work. So at last we stick the image of sheep on it and people can play the game by pressing the sheep.

CONCLUSION

The goal of our project is to let people who are locked down in Shanghai release their stress while playing the game. The users interact with the project by shouting and pressing the pressure sensor. This is an input process. And the computer takes charge of ” thinking” and gives feedback ( begin the game and move the hunter) This shows the definition of interaction. If we have more time, I will improve the catch sound effect of the game. I would like to design a special effect for different food. And I also want to improve the code to let the score detail appear near the caught food. These elements can let the users understand the mechanics of this game. 

During the process of this project, I learn the importance of user testing. Sometimes it is hard for the designer to find the problem and details that can be improved. We need suggestions to make our project better. 

What’s more, I learned the importance of cooperation. It did can lead to the effect of  1+1 > 2.  In this surreal semester, everyone suffers more pressure. Understanding and communication become more important during this special period and help this project works better. Younian and I divided the work equally and helped each other. That kind of cooperation makes the final weeks not so stressful. 

At last, thanks for Younian making this project with me. I really enjoy cooperation with you 💗💗💗  Thanks for Marcela helping us to complete this project and teaching us this semester.

Hope we can meet in the fall!

TECHNICAL DOCUMENTATION

Arduino

void setup()
{
  Serial.begin(9600);
}

void loop() 
{
  // to send values to Processing assign the values you want to send
  //this is an example
  int sensor1 = analogRead(A0);
  int sensor2 = analogRead(A1);


  // send the values keeping this format
  Serial.print(sensor1);
  Serial.print(",");  // put comma between sensor values
  Serial.print(sensor2);
  Serial.println(); // add linefeed after sending the last sensor value

  // too fast communication might cause some latency in Processing
  // this delay resolves the issue.
  delay(100);

  // end of example sending values
}

Processing 

import processing.serial.*;
import processing.sound.*;
import processing.video.*;
int NUM_OF_VALUES_FROM_ARDUINO = 2;
int sensorValues[];
String myString = null;
Serial myPort;
AudioIn microphone;
Amplitude analysis;
Movie myMovie;

//added this to have a feedback when you catch something
//you can replace the sound
SinOsc sine;
Env env;
float attackTime = 0.001;
float sustainTime = 0.004;
float sustainLevel = 0.5;
float releaseTime = 0.4;

Catcher c;
Dropfruit[] dropf;
Droprefresh[] dropr;
Dropsheep[] drops;
//Apple[] apples;

Timer dropTimer;

int numDrops;
int dropInterval;
int activeDrops;

String gameState;
int score;

Timer countDownTimer;
int timeLeft;
int maxTime;

PImage catcherImage;
int x;
int r =150;




void setup() {
  size(800, 800);
  background(237, 222, 139);
  microphone = new AudioIn(this, 0);
  microphone.start();
  analysis = new Amplitude(this);
  analysis.input(microphone);

  
  sine = new SinOsc(this);
  env  = new Env(this);

 countDownTimer = new Timer(1000);
 maxTime = 140;
 timeLeft = maxTime;
 
  PFont myFont;
  myFont = createFont("Phosphate-Inline", 50);
  textFont(myFont);

  gameState = "TITLE";
  score = 0;

  myMovie = new Movie(this, "Never.mp4");
  
  

  c= new Catcher();
  numDrops = 20;//need to adjust, now the orange will run out after we catch them
  dropf = new Dropfruit[numDrops];
  dropr = new Droprefresh[numDrops];
  drops = new Dropsheep[numDrops];
  //apples = new Apple[numDrops];
  for ( int i =0; i < numDrops; i++) {
    dropf[i] = new Dropfruit();
    dropr[i] = new Droprefresh();
    drops[i] = new Dropsheep();
  }
  activeDrops = 0;
  dropInterval = 1000;
  dropTimer = new Timer(dropInterval);
  dropTimer.start();

  setupSerial();
  x = width/2;
  catcherImage = loadImage("hunter.png");
  catcherImage.resize(r, r);
  //d = new Drop();
}

void draw() {
  background(237, 222, 139);
  //println(analysis.analyze());
  getSerialData();
  //printArray(sensorValues);
  
   
 
  if (gameState =="TITLE") {
    title();
  } else if (gameState == "GAME") {
    //noCursor();
    game();
  } else if (gameState == "WIN") {
    cursor();
    win();
  } else if (gameState == "LOSE") {
    cursor();
    lose();
  } else {
    println("something went wrong with gameState");
  }
}


//h.update();
//h.display();
boolean intersect (Catcher c, Dropfruit d) {
  float distance = dist(c.forkX, c.forkY, d.x, d.y);
  if (distance < 50) {
    return true;
  } else {
    return false;
  }
}
boolean intersect (Catcher c, Droprefresh r) {
  float distance = dist(c.forkX, c.forkY, r.x, r.y);
  if (distance < 50) {
    return true;
  } else {
    return false;
  }
}
boolean intersect (Catcher c, Dropsheep s) {
  float distance = dist(c.forkX, c.forkY, s.x, s.y);
  if (distance < 50) {
    return true;
  } else {
    return false;
  }
}

void game() {
  noStroke();
  background(237, 222, 139);
  c.update();
  c.display();

  //time management
  if ( dropTimer. complete() == true) {
    if (activeDrops < numDrops) {
      activeDrops++;
    }
    dropTimer. start();
  }

  for (int i =0; i<activeDrops; i++) {
    dropf[i].update();
    dropf[i].display();
    dropr[i].update();
    dropr[i].display();
    drops[i].update();
    drops[i].display();
    if (intersect (c, dropf[i])==true) {
      dropf[i].caught();
      score++;
      println("score= "+score);
      fill(1,77,103);
      textSize(36);
      String a = "basics +1";
      text(a, 650, 700);
      sine.play();
      sine.freq(500);
      env.play(sine, attackTime, sustainTime, sustainLevel, releaseTime);
    }
    if (intersect (c, dropr[i])==true) {
      dropr[i].caught();
      score=score+2;
      println("score= "+score);
      fill(1,77,103);
      textSize(36);
      String m ="snack +2";
      text(m, 650, 700);
      sine.play();
      sine.freq(1000);
      env.play(sine, attackTime, sustainTime, sustainLevel, releaseTime);
    }
    if (intersect (c, drops[i])==true) {
      drops[i].caught();
      score=score-10;
      println("score= "+score);
      fill(1,77,103);
      textSize(36);
      String u = "sheep -10";
      text(u, 650, 700);
      sine.play();
      sine.freq(200);
      env.play(sine, attackTime, sustainTime, sustainLevel, releaseTime);
    }
  }
  if (score >=10) {  //need to adjust
    gameState = "WIN";
  }
  if (score < 0) {  //need to adjust
    gameState = "LOSE";
  }
   
  fill(1,77,103);
   textSize(36);
  String p = "LIFE:" + score;
  text(p, 90, 90); 
  
  if (countDownTimer. complete() == true){
    if (timeLeft >1){
      timeLeft--;
      countDownTimer.start();
    }else{
      gameState = "LOSE";   
    }
  }
  String s = "Time Left:" + timeLeft;
  textAlign(LEFT);
  textSize(36);
  fill(1,77,103);
  text(s,50,50);
}//end game


void clearBackground() {
  fill (237, 222, 139);
  rect(0, 0, width, height);
}


void resetGame() {
  for (int i = 0; i< numDrops; i++) {
    dropf[i].reset();
    dropr[i].reset();
    drops[i].reset();
  }
  activeDrops=0;
  score = 0;
  gameState = "GAME";
  
  timeLeft = maxTime;
  countDownTimer.start();
  
  
  
}


void title() {
  background(237, 222, 139);
  fill(1,77,103);
  textSize(36);
  textAlign(CENTER);
  String s = "Shout out FOOD to Play";
  text(s, 400, 200);
  //Sound code
  float volume = analysis.analyze();
  float diameter = map(volume, 0, 1, 0, 1000);
  if (diameter > 20) {
    //startTime = millis();
    gameState = "GAME";
    countDownTimer.start();
  } 
}

void win() {
  background(237, 222, 139);
  fill(1,77,103);
  textSize(36);
  textAlign(CENTER);
  String s = "You win!\n Dancing is better than hunting ";
  text(s, 400, 50);
  float volume = analysis.analyze();
  float diameter = map(volume, 0, 1, 0, 1000);
  if (diameter > 0.1) {
if (myMovie.available()) {
    myMovie.read();
  }
  play();
}
}

void play(){
  myMovie.play();
  image(myMovie, 25, 130);
  
}



void lose() {
  background(237, 222, 139);
  fill(1,77,103);
  textSize(36);
  textAlign(CENTER);
  String s = "You will starve to death\nSHOUT to Play Again";
  text(s, 400, 200);
  float volume = analysis.analyze();
  float diameter = map(volume, 0, 1, 0, 1000);
  if (diameter > 20) {
    resetGame();
  }
  
}

void setupSerial() {
  printArray(Serial.list());
  myPort = new Serial(this, Serial.list()[ 3 ], 9600);
  myPort.clear();
  myString = myPort.readStringUntil( 10 );  // 10 = '\n'  Linefeed in ASCII
  myString = null;

  sensorValues = new int[NUM_OF_VALUES_FROM_ARDUINO];
}

void getSerialData() {
  while (myPort.available() > 0) {
    myString = myPort.readStringUntil( 10 ); // 10 = '\n'  Linefeed in ASCII
    if (myString != null) {
      String[] serialInArray = split(trim(myString), ",");
      if (serialInArray.length == NUM_OF_VALUES_FROM_ARDUINO) {
        for (int i=0; i<serialInArray.length; i++) {
          sensorValues[i] = int(serialInArray[i]);
        }
      }
    }
  }
}

Catcher

class Catcher {
  //properties
  float x, y, w, h, forkX, forkY;

  //constructor
  Catcher() {
    x= width/2;
    y = height-r;
  }

  //methods
  void update() {
    if (sensorValues[0]>600) {
      x=x+20;
    }
    if (sensorValues[1]>600) {
      x=x-20;
    }
    //x = mouseX;
    image(catcherImage, x, y);
    w = catcherImage.width;
    h = catcherImage.height;
    forkX = x+w-30;
    forkY = y+30;

    if (x>width-r) {
      x=width-r;
    }
    if (x<0) {
      x=0;
    }
  }
  void display() {
    image(catcherImage, x, y);
    //you can delete this part, it is just to figure out where the fork is
    fill(0, 0, 0, 0 );
    rectMode(CENTER);
    rect(forkX, forkY, 60, 10);
  }
}

Drop_fruit

PImage cucmber;
PImage greens;
PImage onion;
PImage orange;
PImage steak;
PImage fruit;
class Dropfruit {
  float x, y, w, h;
  float speedY;
  String pix;
  Dropfruit() {
    x = random(width);
    y = -10;
    h = 16;
    w = 16;
    speedY = random(5, 10);
    float r = int(random(1,8));
    if (r <= 2) {
      pix = "cucumber.png";
    } else if (r == 3) {
      pix = "greens.png";
    } else if (r == 4) {
      pix = "onion.png";
    } else if  (r == 5) {
      pix = "orange.png";
    } else if  (r >= 6) {
      pix = "steak.png";
    } 
  }
  void update() {
    y+= speedY;
    if (y > height +h/2) {
      y = -h/2;
    }
  }
  void display() {
    fruit = loadImage(pix);
    fruit.resize(60, 50);
    image(fruit, x, y);
    //orange = loadImage("orange.png");
    //apple = loadImage("apple.png");
    //apple.resize(60, 50);
    //image(apple,x+100, y+60);
  }
  void caught() {
    speedY =0;
    y = 0;
  }
  void reset() {
    y=0;
    speedY = random(5, 10);
  }
}

Drop_refresh

PImage cake;
PImage hamburger;
PImage ice;
PImage refresh;

class Droprefresh {
  float x, y, w, h;
  float speedY;
  String pix;
  Droprefresh() {
    x = random(width);
    y = -10;
    h = 16;
    w = 16;
    speedY = random(5, 10);
    float r = int(random(8,13));
    if (r <= 9) {
      pix = "cake.png";
    } else if (r == 10) {
      pix = "hamburger.png";
    } else if (r >= 11) {
      pix = "ice.png";
    } 
  }
  void update() {
    y+= speedY;
    if (y > height +h/2) {
      y = -h/2;
    }
  }
  void display() {
    refresh = loadImage(pix);
    refresh.resize(60, 50);
    image(refresh, x, y);
  }
  void caught() {
    speedY =0;
    y = 0;
  }
  void reset() {
    y=0;
    speedY = random(5, 10);
  }
}

Drop_sheep

PImage sheep;

class Dropsheep {
  float x, y, w, h;
  float speedY;
  //String pix;
  Dropsheep() {
    x = random(width);
    y = -10;
    h = 16;
    w = 16;
    speedY = random(3, 7);
    //float r = int(random(15,18));
    //if (r == 15||r == 16) {
    //  pix = "sheep.png";
    //}
  }
  void update() {
    y+= speedY;
    if (y > height +h/2) {
      y = -h/2;
    }
  }
  void display() {
    sheep = loadImage("sheep.png");
    sheep.resize(60, 50);
    image(sheep, x, y);
  }
  void caught() {
    speedY =0;
    y = 0;
    
  }
  void reset() {
    y=0;
    speedY = random(5, 10);
  }
}

Timer

 

class Timer {
  int startTime;
  int interval;
  Timer (int timeInterval) {
    interval = timeInterval;
  }
  void start() {
    startTime = millis();
  }
  boolean complete() {
    int elapsedTime = millis()- startTime;
    if (elapsedTime > interval) {
      return true;
    } else {
      return false;
    }
  }
}

Images in the game

 

 

Circuit

Video

The video is too big to upload to this blog. Here is the link to the YouTube video. 

Categories
Interaction Lab

Recitation 10 :Image & Video

 Part 1: Media controller

I decided to make the image of a hunter for our final project and control it with the variable resistors. So I made the code of the hunter. 

void drawMan(float u, float v, float s, color c){
   fill(255);
  noStroke();
  circle(u-450,v-450,s-20);
  
  ellipse(u-450,v-415,s+5,s);
  ellipse(u-433,v-390,s-30,s-40);
  ellipse(u-465,v-390,s-30,s-40);
  
  ellipse(u-475, v-410, s-40, s-20);
  ellipse(u-425, v-425, s-20, s-40); 
  
  
  fill(0);
  noStroke();
  stroke(0);
  line(u-455,v-450,u-445,v-450);
  circle(u-455,v-450,s-45);
  circle(u-445,v-450,s-45);
 
  
  fill(0);
  noStroke();
  rect(u-413,v-450,s-45,s+10);
  rect(u-420,v-450,s-30,s-40);
  rect(u-420,v-470,s-45,s-20);
  rect(u-413,v-470,s-45,s-20);
  rect(u-405,v-470,s-45,s-20);
  
}

I forgot that I need to use an image at that time and change the numbers into variables. And then I realized this problem and save it as a picture.

 

Here is the code.

Arduino

void setup()
{
  Serial.begin(9600);
}

void loop() 
{
  // to send values to Processing assign the values you want to send
  //this is an example
  int sensor1 = analogRead(A0);
  int sensor2 = analogRead(A1);


  // send the values keeping this format
  Serial.print(sensor1);
  Serial.print(",");  // put comma between sensor values
  Serial.print(sensor2);
  Serial.println(); // add linefeed after sending the last sensor value

  // too fast communication might cause some latency in Processing
  // this delay resolves the issue.
  delay(100);

  // end of example sending values
}

Processing

PImage hunter; 
import processing.serial.*;
int NUM_OF_VALUES_FROM_ARDUINO = 2;   /** YOU MUST CHANGE THIS ACCORDING TO YOUR PROJECT **/
int sensorValues[];      /** this array stores values from Arduino **/

String myString = null;
Serial myPort;
void setup(){
  size(600,600);
  background(237,222,139);
  setupSerial(); 
  imageMode(CENTER);
}
void draw(){
  background(237,222,139);
   getSerialData();
  printArray(sensorValues);
 float val1 = sensorValues[0];
 float val2 = sensorValues[1];
 hunter = loadImage("hunter.png");
 hunter.resize(90,90);
 
 image(hunter,val1,val2);

}

void setupSerial() {
  //printArray(Serial.list());
  myPort = new Serial(this, Serial.list()[ 3 ], 9600);

  myPort.clear();
  // Throw out the first reading,
  // in case we started reading in the middle of a string from the sender.
  myString = myPort.readStringUntil( 10 );  // 10 = '\n'  Linefeed in ASCII
  myString = null;
  
    sensorValues = new int[NUM_OF_VALUES_FROM_ARDUINO];
}

void getSerialData() {
  while (myPort.available() > 0) {
    myString = myPort.readStringUntil( 10 ); // 10 = '\n'  Linefeed in ASCII
    if (myString != null) {
      String[] serialInArray = split(trim(myString), ",");
      if (serialInArray.length == NUM_OF_VALUES_FROM_ARDUINO) {
        for (int i=0; i<serialInArray.length; i++) {
          sensorValues[i] = int(serialInArray[i]);
        }
      }
    }
  }
}

 

Part 2: Musical Instrument

At first, I didn’t understand the requirement of the exercise so I asked LA for help. I knew that the functions of envelope and difference are necessary but I don’t know how to use them. I spent a long time solving this problem. 

Code

import processing.video.*; 
import processing.sound.*;
TriOsc triOsc;
Env env;
String[] cameras = Capture.list();
Capture cam;
float attackTime = 0.006;
float sustainTime = 0.002;
float sustainLevel = 0.3;
float releaseTime = 0.4;
int size = 20;
float r, prer;
void setup() {
  size(640, 480);
  cam = new Capture(this, cameras[0]);
  cam.start();
  triOsc = new TriOsc(this); 
  env  = new Env(this);  
}
void draw() {
  if (cam.available()) {
   cam.read(); 
  } 
 
  for (int x=0; x<cam.width; x=x+size) {
     for (int y=0; y<cam.height; y=y+size) {
       color c=cam.get(x,y);
       fill(c);
       noStroke();
       rect(x,y,size,size);
     }
}
  color c = cam.get(width/2, height/2); 
  r = red(c);
  float difference = abs(r-prer);
  if (difference>10) {
    triOsc.play();
    triOsc.freq(map(r, 0, 255, 100, 600));
    env.play(triOsc, attackTime, sustainTime, sustainLevel, releaseTime);
}
prer = r;
}