Train to Dubai by Alex Wuqi Zhang and Ross Jiang

Our final project was inspired by a recent Korean zombie movie called “Train to Busan”. We localized the title and changed the storyline.

One day Ross receives a text message from Alex: Alex is stuck in a train and surrounded by zombies. The player, as Ross, has to pick up a gun and save his friend from dying from zombies.

Alex was mainly in charge of the game mechanics  and Ross was mainly in charge of the art design and hardware.

The making of the game wasn’t easy. We had to keep multiple versions/drafts in the process of making this game because we want to work on the game at the same time on two different machines. After Alex finishes part of the code, he has to copy and paste onto the code on Ross’ computer to make sure it works. Sometimes it can be confusing as a single line of code missing would result in the whole program not working.

Thanks to Aaron’s help with the ps3eye library and the setup of the camera, the computer vision part was not very difficult. We just had to adopt the existing program to our game.

The hardware and communications between arduino and processing proved to be quite frustrating. Sometimes we had to unplug and plug the USB to make sure processing reads input properly. It was probably simply because of the faulty port on Ross’ computer. The button was broken quite a few times as players stepped on the wire or push too hard on the button. It would be ideal if we had longer wires or made it wireless.

The gameplay could be improved as well. If we had more time, we would have designed more levels with different kinds of zombies as well as a more proper ending to the story.

Here are the codes:

class Zombie{
  
  boolean direction;
  int counterImg;
  float locx, locy;
  PImage img1; 

  Zombie(float x, float y, boolean dir){
    locx = x;
    locy = y;
    direction = dir; 
  }
  
  void displayImages(){
    this.update();
    if(frameCount % 10 == 0){
      counterImg++;
    }
    pushMatrix();
    //if(!direction){
    //  translate(img1.width,0);
    //}
    if(gameover == false){
      img1 = zombieImages1[counterImg % 4]; 
      if(!direction){
        scale(-1, 1);
        image(img1,-locx, locy);
      }else{
        //scale(1, -1);
        image(img1, locx, locy);
      }
    }
    popMatrix();
  }
  
  void update(){
    if (frameCount % 10 == 0){
      if(direction){
        this.locx += 10; 
      }else{
        this.locx -= 10;
        
      }
    }
    if (frameCount % 120 == 0){
      this.direction = !direction;
    }
  }
}
class ZombieFast{
  
  boolean direction;
  int counterImg;
  float locx, locy;
  PImage img1; 

  ZombieFast(float x, float y, boolean dir){
    locx = x;
    locy = y;
    direction = dir ; 
  }
  
  void displayImages(){
    this.update();
    if(frameCount % 10 == 0){
      counterImg++;
    }
    pushMatrix();
    //if(!direction){
    //  translate(img1.width,0);
    //}
    if(gameover == false){
      img1 = zombieFastImages[counterImg % 4]; 
      if(!direction){
        scale(-1, 1);
        image(img1,-locx, locy);
      }else{
        //scale(1, -1);
        image(img1, locx, locy);
      }
    }
    popMatrix();
  }
  
  void update(){
    if (frameCount % 10 == 0){
      if(direction){
      this.locx += 40; 
      }else{
        this.locx -= 40;
        
      }
    }
     if(frameCounter % 25 == 0){
       tempVar++;
       println(tempVar);
       if(tempVar % 4 == 0){
         this.direction = !direction; 
       }
     }
  }
}
class ZombiePeek{
  
  boolean direction;
  int counterImg;
  float w, h, locx, locy;
  PImage img1; 

  ZombiePeek(float x, float y, float a, float b, boolean dir){
    locx = x;
    locy = y;
    w = a;
    h = b;
    
    direction = dir; 
  }
  
  void displayImages(){
    this.update(); 
    //img1 = loadImage("zombie4f1.png");
    //image(img1, locx, locy, width/w, height/h);
    if(frameCount % 20 == 0){
      counterImg++;
    }
    pushMatrix();
    if(gameover == false){
      img1 = zombiePeekImages[counterImg % 4]; 
      image(img1, locx, locy, 128/w, 128/h);
    }
    popMatrix();
  }
  
  void update(){
     if(frameCounter % 25 == 0){
       tempVar++;
       println(tempVar);
       if(tempVar % 4 == 0){
         this.direction = !direction; 
       }
     }
     if(direction){
       this.w += 0.05;
       this.h += 0.05; 
     }else{
       this.w -= 0.05; 
       this.h -= 0.05;
     }
  }
}

 

import processing.video.*;
import processing.sound.*;
PFont title;
PFont typewriter;
PFont instructionFont;

Serial myPort; 

int currentButtonState = 0; 
int prevButtonState = 0; 

int menu;
int substr_cnt;
int substr_cnt2;
int substr_cnt3;
boolean msg2Display = false;
boolean msg3Display = false;
boolean moviePlay = false;
boolean SoundPlayed = false;
Movie myMovie;
SoundFile typingSound;
SoundFile vibratingSound;
SoundFile gunshot;
SoundFile slimeSound;
PImage dubai;
PImage instruction;

import java.util.ArrayList;
import processing.serial.*;

int alreadyAdded = 0;
int numOfZombies = 4; 
int numOfJumpingZombies = 6; 
int numOfFastZombies = 4;
int numOfPeekZombies = 1;
//Zombie zombie = new Zombie(100, 100, 1, true); 
Bullet bullet = new Bullet(mouseX,mouseY);
String[] explosionImageNames = {"explosionf1.png", "explosionf2.png", "explosionf3.png", "explosionf4.png", "explosionf5.png", "explosionf6.png", "explosionf7.png", "explosionf8.png"};
PImage[] explosionImages = new PImage[explosionImageNames.length];
String[] zombieImageNames = {"zombie1f1.png", "zombie1f2.png", "zombie1f3.png", "zombie1f4.png"};
PImage[] zombieImages1 = new PImage[zombieImageNames.length];
String[] zombieJumpingImageNames = {"zombie2f1.png", "zombie2f2.png", "zombie2f3.png", "zombie2f4.png"};
PImage[] zombieJumpingImages = new PImage[zombieJumpingImageNames.length];
String[] zombieFastImageNames = {"zombie3f1.png", "zombie3f2.png", "zombie3f3.png", "zombie3f4.png"};
PImage[] zombieFastImages = new PImage[zombieFastImageNames.length];
String[] zombiePeekImageNames = {"zombie4f1.png", "zombie4f2.png", "zombie4f3.png", "zombie4f4.png"};
PImage[] zombiePeekImages = new PImage[zombiePeekImageNames.length];
//Zombie[] zombies = new Zombie[numOfZombies];
ArrayList<Zombie> zombies = new ArrayList<Zombie>(4*numOfZombies);
ArrayList<ZombieJump> zombieJump = new ArrayList<ZombieJump>(numOfJumpingZombies);
ArrayList<ZombieFast> zombieFast = new ArrayList<ZombieFast>(numOfFastZombies);
ArrayList<ZombiePeek> zombiePeek = new ArrayList<ZombiePeek>(numOfPeekZombies);
PImage background; 
PImage cursor; 
boolean skipInstruction = false;

import gab.opencv.*;
import com.thomasdiewald.ps3eye.PS3EyeP5;
import java.awt.Rectangle;
PS3EyeP5 ps3eye;
OpenCV opencv;
int blobSizeThreshold = 1;

void setup(){
  fullScreen(); 
  
  title = createFont("BankGothicBold.ttf",32);
  instructionFont = createFont("BrookeS8.ttf",24);
  textFont(title);
  typewriter = createFont("type.ttf",22);
  menu = 1;
  background(0, 0, 30);
  myMovie = new Movie(this, "intro.mov");
  typingSound = new SoundFile(this, "typewriter.mp3");
  vibratingSound = new SoundFile(this, "vibratingSound.mp3");
  slimeSound = new SoundFile(this, "slimeSound.mp3");
  gunshot = new SoundFile(this,"gunshot.wav");
  dubai = loadImage("dubai.jpg");
  instruction = loadImage("instruction.png");
  
  printArray(Serial.list()); 
  String portname = Serial.list()[4];
  println(portname); 
  myPort = new Serial(this,portname,9600);
  ps3eye = PS3EyeP5.getDevice(this);
  ps3eye.start();
  opencv = new OpenCV(this, 640, 480);
  
  
  background = loadImage("trainbackground.png");
  background(background); 
  
  cursor = loadImage("cursor.png"); 
  image(cursor, width/2, height/2);
  
  //preload bullet images
  for(int i = 0; i < explosionImageNames.length; i++){
    String imageName = explosionImageNames[i];
    explosionImages[i] = loadImage(imageName); 
  }
  
  //preload zombie images
  for(int i = 0; i < zombieImageNames.length; i++){
    String imageName = zombieImageNames[i];
    zombieImages1[i] = loadImage(imageName); 
  }
  for(int i = 0; i < zombieJumpingImageNames.length; i++){
    String imageName = zombieJumpingImageNames[i];
    zombieJumpingImages[i] = loadImage(imageName); 
  }
  for(int i = 0; i < zombieFastImageNames.length; i++){
    String imageName = zombieFastImageNames[i];
    zombieFastImages[i] = loadImage(imageName); 
  }
  for(int i = 0; i < zombiePeekImageNames.length; i++){
    String imageName = zombiePeekImageNames[i];
    zombiePeekImages[i] = loadImage(imageName); 
  }
}


void draw(){
  
  println(currentButtonState);
  
    if (menu == 1 && currentButtonState == 49 ) {
    menu = 2;
    substr_cnt = 0;
    gunshot.play();
  }
  
  
   if (menu == 1) {
    //ellipse (random (width), random (height), random (4), random (4));
    image(dubai,0,0, width, height);
    textAlign(CENTER); 
    textSize(78);
    text ("Train to Dubai", width/2,height/2);

    textSize(32);
    text("Shoot to Continue", 350, 325);
    textSize(20);
    text("Powered by Ross Jiang and Alex Wuqi Zhang",1100,700);
  }
  if (menu == 2) {
    textFont(typewriter);
    background (0, 0, 30);
    fill(255, 255, 255);
    String msg1 = "Today, you find the streets of Dubai unusually quiet ..";
    String msg2 = "Suddenly, your phone starts vibrating ..";
    String msg3 = "It is from your stupid friend Alex ..";
    
    text (msg1.substring(0,constrain(int(substr_cnt/5),0,msg1.length())), 380, 160);
    if(!SoundPlayed){
      typingSound.play();
      SoundPlayed=true;
    }
    substr_cnt++;
      
    if( substr_cnt == 6 * msg1.length() ){
      msg2Display = true;
      vibratingSound.play();
    }
    
    if(msg2Display){
      text (msg2.substring(0,constrain(int(substr_cnt2/5),0,msg2.length())), 295, 230);
      substr_cnt2++;
    }
    
    if( substr_cnt2 == 6 * msg2.length() ){
      msg3Display = true;
      
    }
    
    if(msg3Display){
       text (msg3.substring(0,constrain(int(substr_cnt3/5),0,msg3.length())), 275, 300);
       substr_cnt3++;
    }
    
    if( substr_cnt3 == 6 * msg3.length() ){
      moviePlay = true;
    }
    
    if(moviePlay){
     
      myMovie.play();
      image(myMovie, 0, 0,width,height);
      
    }
    
    if(myMovie.time() == myMovie.duration()){
      menu = 3;      
    }
    
  }
  
  if(menu == 3){
    textFont(instructionFont);
    image(instruction, 0,0, width,height);
    
  }
  
  if(menu ==3 && currentButtonState ==49){
    gunshot.play();
   menu =4; 
    
  }
  
     if ( menu == 4 ) {
      textFont(typewriter);
      background(background);
      textSize(32);
      opencv.loadImage(ps3eye.getFrame());
      opencv.threshold(100);
      noFill();
      stroke(255, 0, 0);
      strokeWeight(3);
  for (Contour contour : opencv.findContours()) {
    //contour.draw();
     Rectangle r = contour.getBoundingBox();
     
    stroke(255, 0, 0);
    fill(255, 0, 0, 150);
    strokeWeight(2);
    println(r.getCenterX()); 
    clickedX = (int)map((float)r.getCenterX(),640,0,width,0);
    clickedY = (int)map((float)r.getCenterY(),480,0, 0, height);
    ellipse(clickedX,clickedY,5,5);

  }
  
  if(currentButtonState == 49){
    
    gunshot.play();
  
   for(int i = 0; i < zombies.size(); i++){
     if(collisionCheck(zombies.get(i))){
       zombies.remove(i);
       kills++; 
       slimeSound.play();
     }
   }
   
   for(int i = 0; i < zombieJump.size(); i++){
     if(collisionCheckJump(zombieJump.get(i))){
       zombieJump.remove(i);
       kills++; 
       slimeSound.play();
     }
   }
   
   for(int i = 0; i < zombieFast.size(); i++){
     if(collisionCheckFast(zombieFast.get(i))){
       zombieFast.remove(i);
       kills++;
      slimeSound.play();
     }
   }
   
   for(int i = 0; i < zombiePeek.size(); i++){
     if(collisionCheckPeek(zombiePeek.get(i))){
       zombiePeek.remove(i);
       kills++; 
       slimeSound.play();
     }
   }
    
   click = true; 
   bullet = new Bullet(clickedX, clickedY);  
  }
  
  //zombie wave 1
  if(wave == 1 && alreadyAdded == 0){
    for(int i = 0; i < numOfZombies; i++){
      zombies.add(new Zombie((210*(i+1)), (650), true)); 
    }
    
    zombieJump.add(new ZombieJump(300, 500)); 
    zombieJump.add(new ZombieJump(500, 500));
    zombieJump.add(new ZombieJump(700, 500));
    
    zombieJump.add(new ZombieJump(300, 300)); 
    zombieJump.add(new ZombieJump(500, 300));
    zombieJump.add(new ZombieJump(700, 300));
    
    
    zombiePeek.add(new ZombiePeek(1050, 200, 0.75, 0.75, true));
    alreadyAdded++;
  }
  
  for(int i = 0; i < zombies.size(); i++){
  zombies.get(i).displayImages(); 
  }
  for(int i = 0; i < zombieJump.size(); i++){
  zombieJump.get(i).displayImages(); 
  }
  for(int i = 0; i < zombieFast.size(); i++){
  zombieFast.get(i).displayImages(); 
  }
  for(int i = 0; i < zombiePeek.size(); i++){
  zombiePeek.get(i).displayImages(); 
  }
  imageMode(CENTER);
  image(cursor, clickedX, clickedY, 320, 320);
  //Bullet bullet = new Bullet(-100,-100);
    if(frameCount % 4 == 0){
    frameCounter++;
    }
  if(click == true){
     bullet.displayBullet();
  }
  deathCounter(zombies.size() + zombieJump.size() + zombieFast.size() + zombiePeek.size()); 
  waveCounter(zombies.size() + zombieJump.size() + zombieFast.size() + zombiePeek.size()); 
  killsCounter(); 
  
  

  //zombie wave 2
  if(wave == 2 && alreadyAdded == 1){
    zombieFast.add(new ZombieFast(100, (100), true)); 
    zombieFast.add(new ZombieFast(100, (300), true)); 
    zombieFast.add(new ZombieFast(100, (500), true)); 
    zombieFast.add(new ZombieFast(100, (700), true)); 
    zombieFast.add(new ZombieFast(1220, (100), false)); 
    zombieFast.add(new ZombieFast(1220, (300), false)); 
    zombieFast.add(new ZombieFast(1220, (500), false)); 
    zombieFast.add(new ZombieFast(1220, (700), false)); 
    alreadyAdded++; 
  }
  
  //zombie wave 3
  if(wave == 3 && alreadyAdded == 2){
    for(int i = 0; i < numOfZombies; i++){
      zombies.add(new Zombie((210*(i+1)), (650), true)); 
    }
    for(int i = 0; i < numOfZombies; i++){
      zombies.add(new Zombie((210*(i+1)), (500), true)); 
    }
    for(int i = 0; i < numOfZombies; i++){
      zombies.add(new Zombie((210*(i+1)), (350), true)); 
    }
    for(int i = 0; i < numOfZombies; i++){
      zombies.add(new Zombie((210*(i+1)), (200), true)); 
    }
    alreadyAdded++; 
  }
  
  gameWin(); 
  gameOver();
     }
}
  
  


void serialEvent(Serial myPort){
  currentButtonState = myPort.read(); 
}


void movieEvent(Movie m) {
     m.read();
}

// reference:
//https://forum.processing.org/two/discussion/26427/how-to-incorporate-the-typewriter-effect-into-my-game

int clickedX, clickedY, kills, tempVar;
int frameCounter = 1;
boolean click = false; 
boolean gameover = false; 
int gameCounter = 6; 
int wave = 1;
boolean exitGame = false; 



class Bullet{
  
  int bulletX, bulletY;
  int bulletCounter = 0; 
  PImage img; 
  int time;
  
  
  Bullet(int x, int y){
    bulletX = x; 
    bulletY = y; 
  }
  
  void displayBullet(){
    if(click == true){
      int frame = (frameCounter % 8) / 1 + 1;
      img = explosionImages[frame-1]; 
      image(img, bulletX, bulletY);
      if(frame == 8){
        click = false;
      }
    }
  }
}

  void deathCounter(int remainingZombies){
    if(gameover == false && frameCount % 60 == 0){
      gameCounter--;
    }
    fill(255); 
    textSize(60); 
    text(gameCounter, width/2-20, 100);
    if(gameCounter == 0 && remainingZombies > 0){
      gameover = true; 
      //textSize(40); 

    }
  }
  
  void killsCounter(){
    fill(255); 
    textSize(60); 
    text("kills: " + kills, width - 300, 100); 
  }
  
  void waveCounter(int remainingZombies){
    if(gameCounter == 0 && remainingZombies == 0){
      wave++; 
      gameCounter = 6; 
    }
  }
  
  void gameWin(){
    if(wave == 4){
      fill(255); 
      textSize(80); 
      text("YOU WIN!!", width/2-150, height/2-100); 
      textSize(40);
      text("Shoot to Play Again", width/2-200, height/2 +200); 
      if(clickedX > 0 && clickedX < width && clickedY > 0 && clickedY < height && click == true){
        println("CLICKEDD");
        //gameover = false;
        wave = 1;
        alreadyAdded = 0;
        kills = 0; 
        frameCounter = 1;
        tempVar = 0;
        gameCounter = 6;
      }
    }
  }
  
  void gameOver(){
    if(gameover == true){
        if(zombies.size() != 0){
          for(int i = 0; i < zombies.size(); i++){
            zombies.remove(i); 
          }
        }
        if(zombieJump.size() != 0){
          for(int i = 0; i < zombieJump.size(); i++){
            zombieJump.remove(i); 
          }
        }
        if(zombieFast.size() != 0){
          for(int i = 0; i < zombieFast.size(); i++){
            zombieFast.remove(i); 
          }
        }
        if(zombiePeek.size() != 0){
          for(int i = 0; i < zombiePeek.size(); i++){
            zombiePeek.remove(i); 
          }
        }
      fill(255); 
      textSize(80); 
      text("game over", width/2-200, height/2-100);
      textSize(40); 
      text("Shoot to Play Again", width/2-200, height/2 + 200); 
      if(clickedX > 0 && clickedX < width && clickedY > 0 && clickedY < height && click == true){
        println("CLICKEDD");
        gameover = false;
        wave = 1;
        alreadyAdded = 0;
        kills = 0; 
        frameCounter = 1;
        tempVar = 0;
        if(zombies.size() != 0){
          for(int i = 0; i < zombies.size(); i++){
            zombies.remove(i); 
          }
        }
        if(zombieJump.size() != 0){
          for(int i = 0; i < zombieJump.size(); i++){
            zombieJump.remove(i); 
          }
        }
        if(zombieFast.size() != 0){
          for(int i = 0; i < zombieFast.size(); i++){
            zombieFast.remove(i); 
          }
        }
        if(zombiePeek.size() != 0){
          for(int i = 0; i < zombiePeek.size(); i++){
            zombiePeek.remove(i); 
          }
        }
        gameCounter = 6;
      }
    }
  }
  
  

  boolean collisionCheck(Zombie z){
    println("used");
    if((z.locx - 56) < clickedX && (z.locx + 56) > clickedX && (z.locy - 56) < clickedY && (z.locy + 56) > clickedY){
       //z = null; 
       println("hit");
       return true;
    }
    return false;
  }
  
  boolean collisionCheckJump(ZombieJump z){
    println("used");
    if((z.locx - 56) < clickedX && (z.locx + 56) > clickedX && (z.locy - 56) < clickedY && (z.locy + 56) > clickedY){
       //z = null; 
       println("hit");
       return true;
    }
    return false;
  }
  
  boolean collisionCheckFast(ZombieFast z){
    println("used");
    if((z.locx - 56) < clickedX && (z.locx + 56) > clickedX && (z.locy - 56) < clickedY && (z.locy + 56) > clickedY){
       //z = null; 
       println("hit");
       return true;
    }
    return false;
  }
  
    boolean collisionCheckPeek(ZombiePeek z){
    println("used");
    if((z.locx - 56) < clickedX && (z.locx + 56) > clickedX && (z.locy - 56) < clickedY && (z.locy + 56) > clickedY){
       //z = null; 
       println("hit");
       return true;
    }
    return false;
  }

 

Train to Dubai – IM final project proposal by Ross Jiang and Alex Wuqi Zhang

Our final project is a first-person shooter game in which the protagonist Ross tries to save his friend Alex from a zombie-infested train headed to Dubai. The player uses a toy gun to aim and shoot in front of projected screen. The player can pull the trigger to shoot or select a button and move the gun around to move the cursor on the screen. We are probably going to use a camera to capture the infrared laser emitted by the gun (we definitely need some technical expertise from Aaron here).

Our game starts with a menu with the title of our game and two options “START” and “ACKNOWLEDGEMENTS”. Click “START”, then a few lines of typewriter effect texts tell the background story. Then a video (or animation) of the last exchange of text messages between Ross and Alex display on the screen (Essentially a cry for help from Alex).

Afterwards, the actual game starts, a train arrives in a station and zombies pop out of windows, doors and top of the train. The player has a certain amount of time to eliminate each zombie by moving the cursor to aim and pulling the trigger to shoot. The player has three lives to start with, and each failure to eliminate a zombie would result in loss of a life. Depending on how much time we have, we might have multiple levels. If the player survives the game (clears all zombies from the train and saves Alex), the player will be congratulated and has the option to restart the game.

 

 

Computer Vision – Response

This is a fairly simple read that demystifies computer vision for me. low-level computer vision algorithms through detecting motion, presence or brightness threshold are actually not that far beyond my knowledge. Moreover, many plug-ins and libraries are available for artists who wish to use computer vision for their multi-media installation.

This reading also gave me some ideas of how to make our first-person shooter game. The player can be given a laser gun and the camera would capture the single brightest point and translate that onto the screen so that by moving the gun in hand, the player can move the cursor on the screen.

Flap like a Flappy Bird!

Have you ever wondered how the bird feels when it tirelessly flaps its wings through the never-ending tube maze? Look no further than this revolutionary interactive new flappy bird playing experience!

Step 1: Strap the Flappy-Bird Controller® tightly onto your hand like this:

Step 2: Flap away! Now you can get addicted to this stupid game all over again!

I made the Flappy-Bird Controller® with a tilt sensor wired in a serial circuit. The tilt censor only returns a 1 or a 0 and arduino sends that number to processing. Processing tracks the previous input and the bird only flaps when the previous number is 0 and the current input is 1. Below are my codes:

Arduino:

int inPin = 2;         // the number of the input pin
 
int reading;           // the current reading from the input pin
int previous = LOW;    // the previous reading from the input pin
 
// the following variables are long because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long time = 0;         // the last time the output pin was toggled
long debounce = 50;   // the debounce time, increase if the output flickers
 
void setup()
{
  Serial.begin(9600);
  pinMode(inPin, INPUT);
}
 
void loop()
{
  int switchstate;
 
  reading = digitalRead(inPin);
 
  // If the switch changed, due to bounce or pressing...
  if (reading != previous) {
    // reset the debouncing timer
    time = millis();
  } 
 
  if ((millis() - time) > debounce) {
     // whatever the switch is at, its been there for a long time
     // so lets settle on it!
     switchstate = reading;
  }

  Serial.write(switchstate);
 
  // Save the last reading so we keep a running tally
  previous = reading;
}

Processing:

main:

import processing.sound.*;
import processing.serial.*;

Serial myPort;

int fly=0;
int previous = 0 ;
SoundFile hit;
SoundFile die;
SoundFile point;
SoundFile wing;

PImage backgroundImg;
PImage img1;
PImage img2;
PImage img3;
PImage img;
PImage tube1;
PImage tube2;
int background =0;
boolean state = false;
boolean gameOver = false;
float speed = 0;
float gravity = 0.1;
int score = 0;
int hSpeed = 2;


float x = 50;
float y = 200;

int tubeLength;

Bird bird;
Tube tube;

ArrayList<Tube> tubeListTop;
ArrayList<Tube> tubeListBot;

void setup(){
 size(600,600);
 printArray(Serial.list());
 String portname=Serial.list()[9];
 println(portname);
  myPort = new Serial(this,portname,9600);
 //size(1440,900);
 backgroundImg = loadImage("bg.png");
 img1 = loadImage("upflap.png");
 img2 = loadImage("midflap.png");
 img3 = loadImage("downflap.png");  
 tube1 = loadImage("tube1.png"); 
 tube2 =loadImage("tube2.png");
 tubeListTop = new ArrayList<Tube>();
 tubeListBot = new ArrayList<Tube>();
 img = img3;
 
 hit = new SoundFile(this, "hit.wav");
 die = new SoundFile(this, "die.wav");
 point = new SoundFile(this,"point.wav");
 wing = new SoundFile(this, "wing.wav");
 

 
 for (int i=0; i<4;i++){
  tubeLength =int(random(120,200));
  tubeListTop.add(new Tube(660+180*i,0,60,tubeLength, hSpeed)); 
}

for (int i=0; i<4;i++){
  tubeLength =int(random(120,200));
  tubeListBot.add(new Tube(660+180*i,505-tubeLength,60,tubeLength,hSpeed));   
}

}


  void draw(){ 
    
  if(!gameOver){
        println(fly+","+previous);

         img = img1;
    if(fly==1 && previous ==0){
        
  img = img3;

  wing.play();
    y = y-30;
    speed = speed *0.02;


    }
     previous = fly;


    image(backgroundImg,0,0);
    
    bird = new Bird(x, y);
    textSize(20);
    fill(0, 102, 153);
    text("FLIP YOUR WING TO FLY", 80,580);
    fill(255,0,0);
    text("Score: " + score, 450, 580);

  
    //if(keyPressed==true){ // use keyboard to move bird
    //  if(key == 'w'){
    //     y--; 
    //  }else if(key== 'a'){
    //     x--; 
    //  }else if(key=='d'){
    //   x++; 
    //  }else if(key=='s'){
    //   y++; 
    //  }
    //}
   for(Tube tube:tubeListTop){
     
     if(tube.locx<-60){
      tube.setX(680); 
     }
     
     tube.draw();
     
   }
    

   for(Tube tube:tubeListBot){
     
     if(tube.locx<-60){
      tube.setX(680); 
     }
     
     if (bird.locx == tube.locx){
      score++; 
      point.play();
     }

     tube.drawInverse();
     
   }
   
   
  }else{
    //    image(backgroundImg,0,0);
    //bird = new Bird(x, y);
    textSize(38);
    text("GAME OVER",180,270);
    textSize(16);
    text("restart the program to try again",170,290);
   }
   
    // Add speed to location.
      y = y + speed;

  // Add gravity to speed.
     speed = speed + gravity;
     
    if (y > height-123) {
    // Multiplying by -0.40 instead of -1 slows the object 
    // down each time it bounces (by decreasing speed).  
    // This is known as a "dampening" effect and is a more 
    // realistic simulation of the real world (without it, 
    // a ball would bounce forever).
    speed = speed * -0.40;
    y = height-123;
  }
  
   bird.draw(img);
   
   for(Tube tube:tubeListTop){
            
        if(bird.onCollision(tube)&&!gameOver){
            gameOver = true;
               hit.play();
               delay(100);
               die.play();
           }
      
    }  
    
     for(Tube tube:tubeListBot){
            
        if(bird.onCollision(tube)&&!gameOver){
            gameOver = true;
               hit.play();
               delay(100);
               die.play();
           }
      
    }  

  }
  
//void keyPressed(){
  
  //img = img1;

  //wing.play();
//}
 
 
 
//void keyReleased(){  
//    y = y-30;
//    speed = speed *0.05;
//    img = img3;
//  }
  
void serialEvent(Serial myPort){
  fly=myPort.read();
}

Bird class:

class Bird{
  
  float locx, locy;

  
  Bird(float x,float y){
    locx = x;
    locy = y;    
  }
  
  //void gravity(){
  //    // Add speed to location.
  //    locy = locy + speed;

  //// Add gravity to speed.
  //   speed = speed + gravity;

    
  //}
  
  void draw(PImage img ){
    
    image(img,locx,locy);
    
  }
    
  boolean onCollision(Tube a) {

  // are the sides of one rectangle touching the other?

  if (locx + 36 >= a.locx &&    // r1 right edge past r2 left
      locx <= a.locx + a.xlen &&    // r1 left edge past r2 right
      locy + 26 >= a.locy &&    // r1 top edge past r2 bottom
      locy <= a.locy + a.ylen) {    // r1 bottom edge past r2 top
        return true;
  }
  return false;
}
  
//http://www.jeffreythompson.org/collision-detection/rect-rect.php
  
}

class Tube{
 int locx, locy, xlen, ylen, hSpeed;
 
 Tube(int x,int y, int xl, int yl, int hs){
   locx = x;
   locy = y;
   xlen = xl;  
   ylen = yl;
   hSpeed = hs;
 }
 
   void draw(){   
    image(tube1,locx,locy,xlen,ylen);
    locx -= hSpeed;
  }
  

  void drawInverse(){
    image(tube2,locx,locy,xlen, ylen);
    locx -= hSpeed;

  }
  
  void setX(int x){
    locx =x;
  }
      
}

 

Physical Computing

I had no concept of what physical computing is prior to this semester. However, now I tend to find a lot of physical computing examples in my daily life.  Even with my limited knowledge, I still try to figure out how would I make the dazzling lights show or big display screen with shiny letters. I also really like the idea of using technology to read human behaviours, analyze it and give feedback to hopefully improve the user experience. I am glad I have taken those IM classes this semester because I feel that my skills in programming now exists beyond just the screen of my computer but also in the real world. I am very proud that people can interact with some projects I have designed. The interaction between arts and technology truly makes physical computing interesting.

Interactive Name Design – Ross Jiang

For my project,I want to display my name and enable user to interact with the design. I watched a few videos on steering behaviours. Basically all “vehicles” behave independently and move towards a certain point on the text. I referenced Aaron’s textLine example on how to turn a text to points and I modified a Vehicle class from a Daniel Shiffman’s example.

If any “vehicle” is within a certain range of the cursor provided the cursor is moving, the target would change from a point on the text to the cursor and that is how user is able to interact and change the text.

the font and full code can be found on:

https://github.com/ross67/IntroToIm/tree/master/interactiveName

 

//Thanks to Daniel Shiffman for explanation on steering behaviors on his Youtube Channel:https://www.youtube.com/user/shiffman

// the vehicle class is modified from his coding example on https://www.youtube.com/watch?v=4zhJlkGQTvU

import geomerative.*;
String fontName = "IndieFlower.ttf";
String textTyped = "Ross Jiang";
RFont font;
RPoint[] pnts;
Vehicle[] vehicles = new Vehicle[600];

void setup(){
    //size(800, 600);
    size(1440,900);
    background(51);
    noFill();
    stroke(255);
   RG.init(this);
  font = new RFont(fontName, 230, RFont.LEFT);

  RCommand.setSegmentLength (10);
  RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
  RGroup grp;
  grp = font.toGroup(textTyped);
  grp = grp.toPolygonGroup();
  pnts = grp.getPoints();
  
//define the vehicles

  for (int i=0; i<150; i++){ //left 
      Vehicle v = new Vehicle(0, i*9.6);
      vehicles[i] = v;   
  }
  
   for (int j=150; j<300; j++){ //right
      Vehicle v = new Vehicle(1440, (j-150)*9.6);
      vehicles[j] = v;   
  }
  
  for (int k=300; k<450; k++){ //top
      Vehicle v = new Vehicle((k-300)*6, 0);
      vehicles[k] = v;   
  }
  
  for (int l=450; l<600; l++){ //bot
     Vehicle v = new Vehicle((l-450)*6, 900);
     vehicles[l] = v;   
  }
      
}

void draw(){
  
     background(51);
    
  int index = 0;
  for (Vehicle v: vehicles){
    
  PVector p = new PVector(pnts[index].x+250,pnts[index].y+400);
  v.arrive(p);
  v.update();
  v.display(); 
  index++;
    
  }
  
}
class Vehicle{

PVector position;
PVector velocity;
PVector acceleration;

boolean mouseClose = false;
float maxForce; //max steering force (how fast it can turn around)
float maxSpeed; // max speed

  Vehicle(float x, float y){
    acceleration = new PVector(0,0);
    velocity = new PVector(0,-2);
    position = new PVector(x,y);
    maxSpeed = 4;
    maxForce = 0.1;
  }
 
    // Method to update position
  void update() {

    // Update velocity
    velocity.add(acceleration);
    // Limit speed
    velocity.limit(maxSpeed);
    position.add(velocity);
    // Reset accelerationelertion to 0 each cycle
    acceleration.mult(0);
   }
   
   void applyForce(PVector force) {
    // We could add mass here if we want A = F / M
    acceleration.add(force);
  }
  
  void arrive(PVector target){
        checkMouse();
        PVector desired = PVector.sub(target,position);  // A vector pointing from the position to the target
     if(mouseClose){
       PVector mouse = new PVector(mouseX,mouseY);
        desired = PVector.sub(mouse,position);  // A vector pointing from the position to the cursor
     }
     float d = desired.mag(); // distance b/w position and target
     
    if (d < 100) { 
      float m = map(d,0,100,0,maxSpeed); // if vehicle is close enough, slow down
      desired.setMag(m);
    } else {
      desired.setMag(maxSpeed); // or else set it to max speed
    }
     
       // Steering = Desired minus Velocity (could potential move backwards)
    PVector steer = PVector.sub(desired,velocity);
    steer.limit(maxForce);  // Limit to maximum steering force
    applyForce(steer);    
  }
  
    void display() {   
    ellipse(position.x, position.y, 6, 6);      
  }
  
    void checkMouse() {
      PVector mouse = new PVector(mouseX,mouseY);
      float d = PVector.sub(mouse,position).mag();
      
    if (d<333 && mouseX!=pmouseX && mouseY!=pmouseY) {
      mouseClose = true;
      println(d);
    }else{
      mouseClose = false;
    }
  }
}

 

Response – Digitalize Everything

To be honest, I did not get much new information from this reading. It talks about the phenomenon of the digitalization of everything in the second Machine Age and how the large and comprehensive database and the almost free nature of reproduction of digital data have completely changed the way we do science, invest in real estates, find our way to work and so much more.

On the other hand, the availability and size of data are so powerful that, if used inappropriately, could be very damaging. For example, many personal data have been used by companies like Cambridge Analytica to swing elections and place ads.

Flappy Bird is a happy bird

I wanted to make a game so I did. My version of flappy bird is really similar to the original one and I am happy with the final version. It is very playable and fun. However, there are a few potential improvements I could have made but simply did not have time.

  1. I did not add a replay function to my game. Ideally, a button is clicked after each death and the player is able to restart the game.
  2. In my code, I should have made a Game class, where initializing and terminating the game takes place. This makes my main file a lot longer than it needs to be.

My full code/images/sounds can be found on

https://github.com/ross67/IntroToIm/tree/master/flappyBird

//main 
import processing.sound.*;
SoundFile hit;
SoundFile die;
SoundFile point;
SoundFile wing;

PImage backgroundImg;
PImage img1;
PImage img2;
PImage img3;
PImage img;
PImage tube1;
PImage tube2;
int background =0;
boolean state = false;
boolean gameOver = false;
float speed = 0;
float gravity = 0.1;
int score = 0;
int hSpeed = 2;


float x = 50;
float y = 200;

int tubeLength;

Bird bird;
Tube tube;

ArrayList<Tube> tubeListTop;
ArrayList<Tube> tubeListBot;

void setup(){
 size(600,600);
 //size(1440,900);
 backgroundImg = loadImage("bg.png");
 img1 = loadImage("upflap.png");
 img2 = loadImage("midflap.png");
 img3 = loadImage("downflap.png");  
 tube1 = loadImage("tube1.png"); 
 tube2 =loadImage("tube2.png");
 tubeListTop = new ArrayList<Tube>();
 tubeListBot = new ArrayList<Tube>();
 img = img3;
 
 hit = new SoundFile(this, "hit.wav");
 die = new SoundFile(this, "die.wav");
 point = new SoundFile(this,"point.wav");
 wing = new SoundFile(this, "wing.wav");
 

 
 for (int i=0; i<4;i++){
  tubeLength =int(random(120,225));
  tubeListTop.add(new Tube(660+180*i,0,60,tubeLength, hSpeed)); 
}

for (int i=0; i<4;i++){
  tubeLength =int(random(120,225));
  tubeListBot.add(new Tube(660+180*i,505-tubeLength,60,tubeLength,hSpeed));   
}

}


  void draw(){ 
    
  if(!gameOver){
    image(backgroundImg,0,0);
    
    bird = new Bird(x, y);
    textSize(20);
    fill(0, 102, 153);
    text("PRESS SPACEBAR TO FLY", 80,580);
    fill(255,0,0);
    text("Socre: " + score, 450, 580);

  
    //if(keyPressed==true){ // use keyboard to move bird
    //  if(key == 'w'){
    //     y--; 
    //  }else if(key== 'a'){
    //     x--; 
    //  }else if(key=='d'){
    //   x++; 
    //  }else if(key=='s'){
    //   y++; 
    //  }
    //}
   for(Tube tube:tubeListTop){
     
     if(tube.locx<-60){
      tube.setX(680); 
     }
     
     tube.draw();
     
   }
    

   for(Tube tube:tubeListBot){
     
     if(tube.locx<-60){
      tube.setX(680); 
     }
     
     if (bird.locx == tube.locx){
      score++; 
      point.play();
     }

     tube.drawInverse();
     
   }
   
   
  }else{
     
    textSize(38);
    text("GAME OVER",180,270);
    textSize(16);
    text("restart the program to try again",170,290);
   }
   
    // Add speed to location.
      y = y + speed;

  // Add gravity to speed.
     speed = speed + gravity;
     
    if (y > height-123) {
    // Multiplying by -0.40 instead of -1 slows the object 
    // down each time it bounces (by decreasing speed).  
    // This is known as a "dampening" effect and is a more 
    // realistic simulation of the real world (without it, 
    // a ball would bounce forever).
    speed = speed * -0.40;
    y = height-123;
  }
  
   bird.draw(img);
   
   for(Tube tube:tubeListTop){
            
        if(bird.onCollision(tube)&&!gameOver){
            gameOver = true;
               hit.play();
               delay(100);
               die.play();
           }
      
    }  
    
     for(Tube tube:tubeListBot){
            
        if(bird.onCollision(tube)&&!gameOver){
            gameOver = true;
               hit.play();
               delay(100);
               die.play();
           }
      
    }  

  }
  
void keyPressed(){
  
  img = img1;

  wing.play();
}
 
 
 
void keyReleased(){  
    y = y-30;
    speed = speed *0.05;
    img = img3;
  }
//classes

class Bird{
  
  float locx, locy;

  
  Bird(float x,float y){
    locx = x;
    locy = y;    
  }
    
  void draw(PImage img ){
    
    image(img,locx,locy);
    
  }
    
  boolean onCollision(Tube a) {

  // are the sides of one rectangle touching the other?

  if (locx + 36 >= a.locx &&    // r1 right edge past r2 left
      locx <= a.locx + a.xlen &&    // r1 left edge past r2 right
      locy + 26 >= a.locy &&    // r1 top edge past r2 bottom
      locy <= a.locy + a.ylen) {    // r1 bottom edge past r2 top
        return true;
  }
  return false;
}
  
//http://www.jeffreythompson.org/collision-detection/rect-rect.php
  
}

class Tube{
 int locx, locy, xlen, ylen, hSpeed;
 
 Tube(int x,int y, int xl, int yl, int hs){
   locx = x;
   locy = y;
   xlen = xl;  
   ylen = yl;
   hSpeed = hs;
 }
 
   void draw(){   
    image(tube1,locx,locy,xlen,ylen);
    locx -= hSpeed;
  }
  

  void drawInverse(){
    image(tube2,locx,locy,xlen, ylen);
    locx -= hSpeed;

  }
  
  void setX(int x){
    locx =x;
  }
      
}

 

Random Squares

The assignment this week is to recreate an old computer art design from an issue of “Computer Graphics and Art”. So the original:

The original design by Bill Kolomyjec

Here are a few instances of my recreation of this design:

instance 1
instance 2
instance 3 with no creator name

The images here don’t seem to be particularly clear, so I uploaded a video as well:

To my understanding of the original design, a few things are controlled, there are a set number of big squares that divide up the screen; all shapes are squares; the size of the smallest square in each big square are all the same; each outer square is of equal distance to its adjacent squares.

On the other hand, there are also random elements to play with, the position of the smallest square, the number of the small squares in each big square. I also added the randomness of adding the project and creator to the first instance where there is only a small square. As a result, there is a possibility in which no project and creator name appears in the image, as in instance 3 listed above. Below is the code I used:


int sqrLength = 180; // the length of each big square
int x,y; // coordinates of each smaller square
int numLines; // how many small squres in big square
int counter = 0; // how many times square with only 1 squre inside appear

fullScreen();

for(int i =0; i<width; i=i+sqrLength){ //draw big squares
for(int j=0; j< height; j=j+sqrLength){
rect(i,j,sqrLength,sqrLength);
}
}

noFill();

for(int i =0; i<height/sqrLength; i++){ //draw vertically
for(int j=0; j<width/sqrLength; j++){ //draw horizontally

numLines = int(random(0,13));
x = int(random(30+180*j,120+180*j));
y = int(random(30+180*i,120+180*i));

if(numLines == 0){
counter++;
if(counter==1){
textSize(20);
fill(0, 102, 153);
text(“RANDOM”, x, y);
pushMatrix();
translate(x,y);
rotate(HALF_PI);
text(“SQUARES”,0,-33);
rotate(HALF_PI);
text(“ROSS”,-30,-33);
rotate(HALF_PI);
text(“JIANG”,-30,-5);
popMatrix();

noFill();
}
}

float xIncre = float(x-180*j)/(numLines+1); //the increment value of each small square
float yIncre = float(y-180*i)/(numLines+1);

for (int k = 0; k<numLines+1; k++){ //draw small squres
float lengthIncre = (float((150-(x-180*j)))/(numLines+1))*k+xIncre*k+30;
rect(x-xIncre*k,y-yIncre*k,lengthIncre,lengthIncre);
}
}
}

 

Lev Manovich – Response

I found reading about the history of the media, new and old, quite interesting. It is also fun to learn about different aspects that define a so-called “new media” and “interactivity”. The Analytical Engine has helped Jacquard loom to achieve universality. Everything( photo, video, text etc. ) is turned into just another set of data that can be easily manipulated due to new media’s modularity.

I found the concept of variability in the reading particularly interesting. Even though now we have greater power than ever to modify what we want to see since everything is programmable. However,a lot of times someone else decides what we see based on our customs and internet history. New media now have the power ranging from selling you stuff you didn’t realize you needed to swing the major elections. That’s why it is imperative to understand new media and not to be manipulated by it.