For my final project, I wanted to create an interactive piece where users can tap El wires that light up and map animations onto the wall depending on where that El wire is installed on the wall. When tapped, a tilt sensor sends a signal to Processing, the sound attached to that wire will play, and the animation for that wire will show up.
I worked with Serial communication between Arduino and Processing in this project and playing with El wires, and projection mapping.
Here is the basic animation:
Here is the prototype:
Problems I ran into:
The main problem I had throughout this project was with the El wires. I spent an entire day or two trying to figure out how to get the el wires wired up so that it would run. At first, I tested it out with just AA batteries and plugging that in with the wires, which worked. I then looked up many videos on how to get the wires to work with Arduino. Many of them told me to hook them up to a relay and other tutorials required Sparkfun sequencers to program the El wires, so I spent a lot of my time trying different methods, but all to no avail. I then found a video on YouTube that went through how to wire up a 12V inverter power, which was very helpful. With that, I was finally able to wire them up correctly.
After wiring them up, I had a problem where one of the lights would not turn on whatsoever, which required me to work on it overnight, but I still couldn’t find out why. After debugging with Aaron, we found out one of the power did not work at all for any of the wires. Luckily I had extra one so that I could still work with three wires.
On the day of the show, another battery was acting up. It kept blinking on and off so when people tapped the orange El wire, the light wouldn’t go off. At this point, I couldn’t change anything, but if I had known the inverters wouldn’t be so reliable, I could have looked for another 12V power source to light the El wires.
I tried to hook up the sound right before the show and for some reason, it wasn’t connecting to my speaker, so there was no sound. I wanted it to play the guitar notes with each wire, but that didn’t happen. There were a few people who suggested me to add some sound to it to make it more interesting and I had to tell them that it was there, but didn’t work right now.
If I had more time:
If I had more time and wire, I would ideally like to have 6 wires displayed and 6 notes playing.
What I would change for next time:
If I had managed my time better, I would ideally have the wires set up on a pole ahead of time (in time for the user testing part of the project), so that I could ask people to test it out. I now see important it is because I didn’t realize that some people might not know what to do with the interactive piece since there are no indicators that it works. The only way to see what the project is doing is by interacting with it, so if no one is touching the El wire, my project is essentially El wires tied onto a pole that does nothing. Next time, I would add some sort of indicator. Maybe when the project is idle, there would be a certain animation, or I could put some text on my Processing sketch to ask the user to play with the wires. I could also make the lights go on and off when no one is interacting with it.
Additionally, I wish I had set up the sound way beforehand to make sure my sound was working. I also wish I had a better projector since it was very light and pixelated.
Overall, I wish I had fixed the small inconsistent bugs and had found a way to get people to come play with the project without me ushering them over since the project looked like nothing if no one interacts with it.
int tiltPin1 = 2;
int tiltPin2 = 3;
int tiltPin3 = 4;
int tiltPin4 = 5;
int tiltPin5 = 6;
int tiltPin6 = 7;
int tiltLED[] = {8, 9, 10, 11, 12, 13};
int tiltLEDState[] = {0, 0, 0, 0, 0, 0};
int interval = 1000;
unsigned long previousMillis = 0;
void setup() {
Serial.begin(9600);
pinMode(tiltPin1, INPUT);
pinMode(tiltPin2, INPUT);
pinMode(tiltPin3, INPUT);
pinMode(tiltPin4, INPUT);
pinMode(tiltPin5, INPUT);
pinMode(tiltPin6, INPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
pinMode(10, OUTPUT);
pinMode(11, OUTPUT);
pinMode(12, OUTPUT);
pinMode(13, OUTPUT);
}
void loop() {
int tiltState1 = digitalRead(tiltPin1);
int tiltState2 = digitalRead(tiltPin2);
int tiltState3 = digitalRead(tiltPin3);
int tiltState4 = digitalRead(tiltPin4);
int tiltState5 = digitalRead(tiltPin5);
int tiltState6 = digitalRead(tiltPin6);
if (tiltState1 == 1) {
Serial.write("1");
tiltLEDState[0] = HIGH;
} else {
Serial.write("0");
}
Serial.write(",");
if (tiltState2 == 1) {
Serial.write("1");
tiltLEDState[1] = HIGH;
} else {
Serial.write("0");
}
Serial.write(",");
if (tiltState3 == 1) {
Serial.write("1");
tiltLEDState[2] = HIGH;
} else {
Serial.write("0");
}
Serial.write(",");
if (tiltState4 == 1) {
Serial.write("1");
tiltLEDState[3] = HIGH;
} else {
Serial.write("0");
}
Serial.write(",");
if (tiltState5 == 1) {
Serial.write("1");
tiltLEDState[4] = HIGH;
} else {
Serial.write("0");
}
Serial.write(",");
if (tiltState6 == 1) {
Serial.write("1");
tiltLEDState[5] = HIGH;
} else {
Serial.write("0");
}
Serial.write("\n");
checkLEDDelay();
}
void checkLEDDelay() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
for (int i = 0; i < 6; i++) {
if (tiltLEDState[i] == HIGH) {
tiltLEDState[i] = LOW;
}
}
}
for (int i = 0; i < 6; i++) {
digitalWrite(tiltLED[i], tiltLEDState[i]);
}
}
Unfortunately, I forgot to record/take a picture of the final piece :/
I am very happy with how the showcase went for what I created! I thought that it was a very interesting user testing session as well. I previously thought that the instructions and flow of the game were intuitive and easily flowed together. However, when people who had zero context of the game or what it was and the physical interface, they sometimes struggled in knowing what to do.
I think that this project really forced me to reflect on what makes an interactive media project, or any entertaining product successful, and I think I realized that people don’t necessarily find the most complex of interactions entertaining, but sometimes these are just simple things with a clear feedback loop. The part of the game that had the most success was probably just people walking up to it and petting the stuffed toy’s head and seeing hearts coming out of the horse or hitting the padded force sensor with a hammer. People understood the deeper meaning of the game that I was attempting to convey, and I got responses like “oh yeah that’s cool,” but they were nowhere near as excited after petting the animal. It now seems so obvious that that interaction would evoke such a strong reaction because of how intuitive and personal it felt.
There were four stages to the game: a forested level, a desert area, an ice area and a wooded area. The idea is that the objective of each other remains the same: the player is able to interact with the animals and harvest resources in order to build a piece of technology. As the player progresses through the game, the animals become increasingly scared of the player and the amount of available resources begins to dwindle, the environment changes and the music becomes more dramatic and tense. The whole idea is that the human’s requests from nature for things to improve their livelihoods are initially meager, like the train and the airplane necessary for movement and communication, but as the human creates these things, their requests become more elaborate and arguably more unnecessary.
All of the assets that I took for the game were from: itch.io and some of the other assets were taken from Minecraft.
I also spent a lot of time on the sound and music used during the game and since this style of the character and assets was reminiscent of older 2D games like Pokemon, I used music from Harvest Moon and Pokemon, as well as sound effects from Minecraft.
Here is a link to the github repository with all of the assets and code:
A few pictures and a video of my project’s setup and someone playing it during the showcase:
It was super awesome to see this all come to life even though it did not turn out like how I envisioned it! Thank you to Aaron and everyone who helped, supported or just simply saw my project, it has been an awesome semester!
So here it is. The end of our project. I honestly do not know where to begin. I guess it started as just a small idea Paulin had, and after discussing it with me our excited minds started to race towards building the game.
When we started planning the blueprint of our game, we knew that our common goal was to make the game as authentic as possible. We really desired the user to have an experience with their body as if they were Iron Man, which is why we had the kinetic camera idea along with the Iron Man glove. Moreover, we also needed the game to be like a “video game”. Not so simple, and not so easy. With that in mind, here is what we have accomplished.
Like most video games, we had a main menu and features inside it.
The menu would have buttons so that each would serve its function: play game, take a look at scoreboard, read the instructions, quit. In case you are wondering about the black screen in the middle, we decided that it would be better to have a demo before first-timers started playing the game. So we implemented a visualization of the camera so that the target could see themselves or specifically their kinetic movements to know that their aiming was supposed to be colored red, and if they pushed their arm forward (color changed to dark red) they knew they were shooting.
Inside the game, there are mainly three features. On the upper left hand side, you can see that the timer is ticking down. We set this to 2 minutes so that everyone during the showcase could have a fair chance and amount of time to play the game. On the upper right hand side, you could see a counter which measured the score. This counted the targets we killed and stored them into the array, then displaying the size of that array. Since the draw() function was looping, this was updating the whole time. On the bottom right hand side, we again had the visualization of the kinetic camera to show how the user was moving in relation to the camera’s view.
We then added targets. These would appear randomly and move in random animations as long as they were inside the screen. When the user aimed and the shooting motion was detected (the shoot was defined as object closest to kinetic camera, which was arm pushed forward) the targets would be hit and disappear. We had a cursor as well to update where the user was moving . The animations at some point were moving very smoothly, but when we tried to add another png image (Thanos) as another target to shoot, things had a bad turn and the whole game slowed down quite intensively. We managed to make it faster by cutting parts that we didn’t need, save as much CPU and memory as possible, but I believe that after a while Paulin’s Macbook Air could not handle an intensive game like this very well. That was certainly one aspect that felt really sore, but in the end it was all the effort worth it to be able to make this sort of game.
After the game, the user would enter their name and their score would appear (from the size of the score array mentioned earlier). Then both the key and value would go into the hash map, the value (score) would be sorted through another array list, and looping through the hash map we would find that score and print out the top five winners. We had to use an array list since hash map did not allow sorting, and hash map was the one of the few data structures that allowed a key:value system that would link two identities of variables together. Either way, numerous people were excited to see their scores being put up in the scoreboard after playing the game.
One of our good friends, Harper, filmed a short clip of our other good friend, Ramon, playing the game.
An earlier version of the game is hard to find since we have been building upon the same file and overwriting the progress as we went. However I do have some earlier code for my scoreboard and my overall menu that I would like to share.
PImage menupic;
PImage bpic;
PImage spic;
PImage ipic;
import controlP5.*;
import java.util.Map;
import java.util.Collections;
int state = 0;
ControlP5 a;
ControlP5 c;
ControlP5 d;
String output;
PFont b;
PFont yo; //for Scoreboard text input
final int menu = 0;
final int game = 1;
final int scoreboard = 2;
final int options = 3;
final int quit = 4;
float score = 10;
int iu = 0;
HashMap<String,Float> hm = new HashMap<String,Float>();
ArrayList<Float> al = new ArrayList<Float>();
int tracker = 0;
int counter = 0;
void setup(){
menupic = loadImage("menu.jpg"); //menu picture
bpic = loadImage("background.jpg"); //background picture
spic = loadImage("scoreboard.jpg"); //scoreboard picture
ipic = loadImage("instructions.png");
size(1920,1030);
//MENU INTERACTIONS
a = new ControlP5(this); //a is for menu
b = createFont("Verdana",30); //font for menu
yo = createFont("Verdana",15); //font for scoreboard form
a.addButton("Play") //name
.setPosition(100,150) //position
.setSize(250,200) //size
.setFont(b) //font
;
a.addButton("Scoreboard")
.setPosition(1500,150)
.setSize(250,200)
.setFont(b)
;
a.addButton("Instructions")
.setPosition(100,750)
.setSize(250,200)
.setFont(b)
;
a.addButton("Quit")
.setPosition(1500,750)
.setSize(250,200)
.setFont(b)
;
//C = Back Button
c= new ControlP5(this); //c is for back button
c.addButton("Back")
.setPosition(1500,750)
.setSize(250,200)
.setFont(b)
;
//D = Scoreboard
d= new ControlP5(this); //d is for scoreboard screen
d.addTextfield("Insert Name Here").setPosition(200,800).setSize(200,50).setAutoClear(false).setFont(yo);
d.addBang("Submit").setPosition(400,800).setSize(200,50).setFont(yo);
}
void draw(){
image(menupic,0,0);
if(state == 0){ //Menu
textSize(40);
text("MENU",50,100);
c.hide();
a.show();
d.hide();
}
else if(state == 1){ //Game
runGame();
score = random(50);
a.hide();
//c.hide();
c.hide();
d.hide();
}
else if(state == 2){ //Scoreboard
image(spic,0,0);
c.show();
a.hide();
d.show();
updateBoard();
}
else if(state ==3){ //Instructions
background(0);
image(ipic,850,20);
textSize(40);
text("1. Click \"Play Game\"",300,400);
text("2. Stand on the designated platform",300,500);
text("3. Use your hand to aim, push your hand towards the kinect camera to shoot",300,600);
text("4. After the game write your name, submit, and see how well you did!",300,700);
c.show();
a.hide();
d.hide();
}
else if(state ==4){ //Quit
exit();
}
}
void runGame(){
image(bpic,0,0);
}
void Play(){
state = 1;
}
void Scoreboard(){
state = 2;
}
void Back(){
state = 0;
}
void Instructions(){
state = 3;
}
void Quit(){
state = 4;
}
void Submit(){ //Submit form for text input in Scoreboard
output = d.get(Textfield.class,"Insert Name Here").getText();
hm.put(output,score);
hm.put("hey",10.0);
al.add(10.0);
hm.put("hi",7.5);
al.add(7.5);
if(score != 0){
al.add(score);
}
//println(output);
}
void updateBoard(){
textSize(60);
text("SCOREBOARD",750,90);
java.util.Collections.sort(al,Collections.reverseOrder());
for(int i = 0; i < al.size(); i++){
//println((float)al.get(i));
if((float)al.get(i) != 0 && i<5){
println((float)al.get(i));
text((float)al.get(i),800, 300+i*60);
}
}
for (Map.Entry me : hm.entrySet()) {
Object test = me.getKey();
if(test != null){
if(al.contains(me.getValue())){
if(tracker<5){
text((String)me.getKey(),400,300+iu*60);
iu+= 1;
tracker+=1;
}
}
else{
continue;
}
}
}
iu = 0;
tracker = 0;
}
Overall, this was very stressful at times but in the end one of the most fun projects I have worked on in my life. Building towards something that I knew other people would marvel at (did you get the pun) and enjoy playing was the true motivation Paulin and I had when we started creating this project. Although the game was lagging a bit during the game, we are still happy that people at the showcase were able to have a fun experience playing our shooting game. I hope you enjoyed reading this as well, and below is the code (main function) of our project. Farewell!
// ======== Final Interactive Media Project Spring 2019 ========= //
//----------Libraries------------------//
import processing.video.*;
import processing.sound.*;
import org.openkinect.freenect.*;
import org.openkinect.freenect2.*;
import org.openkinect.processing.*;
import org.openkinect.tests.*;
import controlP5.*;
import java.util.Map;
import java.util.Collections;
//-------- GLobal Variables----------//
PImage menupic;
PImage bpic;
PImage spic;
PImage ipic;
int state = 0;
ControlP5 a;
ControlP5 c;
ControlP5 d;
String output;
PFont b;
PFont yo; //for Scoreboard text input
final int menu = 0;
final int game = 1;
final int scoreboard = 2;
final int options = 3;
final int quit = 4;
//float score;
int iu = 0;
HashMap<String, Float> hm = new HashMap<String, Float>();
ArrayList<Float> al = new ArrayList<Float>();
int tracker = 0;
int counter = 0;
//Paulin Global Variables
//kinect
PImage img, dImg;
float angle;
Kinect kinect;
boolean shoot;
float minShoot;
float minThresh;
float maxThresh;
//Targets
ArrayList<Targets> targets;
Targets target;
//cursor
Aiming_Box aiming_Circle;
float rx, ry, scale, _rx, _ry;
float _x = 0;
float _y = 0;
//timer
int m, sec;
//score
ArrayList<Integer> score;
//Animations and sound
SoundFile blasterSound;
SoundFile avengers;
Boom boom;
Blaster blaster;
//Movie Intro;
//diframes
ArrayList<Integer> dists;
int numfc;
int threshold;
int randint, pattern;
//Timer
int begin, duration, time;
float prevWidth, prevHeight;
int shootthreshold;
Timer clock;
PImage Imheart;
//-------------------------------------------//
//-------------------------------------------//
//-------------------------------------------//
void setup() {
//size(640, 480);
fullScreen();
menupic = loadImage("menu.jpg"); //menu picture
bpic = loadImage("background.jpg"); //background picture
spic = loadImage("scoreboard.jpg"); //scoreboard picture
ipic = loadImage("instructions.png");
//score = random(50);
//Paulin's SetUP
prevWidth = 1920;
prevHeight = 1030;
//Kinect
kinect = new Kinect(this);
kinect. initDepth();
kinect.initVideo();
img = createImage(kinect.width, kinect.height, RGB);
angle = kinect.getTilt();
shoot = false;
minShoot = 650;
minThresh = 700;
maxThresh = 780;
//Objects
targets = new ArrayList<Targets>();
aiming_Circle = new Aiming_Box();
//Score
score = new ArrayList<Integer>();
//Animation
blasterSound = new SoundFile(this, "shortblast.wav");
avengers = new SoundFile(this, "IMbgmus.wav");
avengers.play();
avengers.loop();
boom = new Boom();
// Intro = new Movie(this,"IMintro.mov");
blaster = new Blaster();
//DiffFrame
numfc=10;
dists = new ArrayList<Integer>(numfc);
threshold = 40;
scale = .1;
//timer
begin = millis();
duration = 120;
time = 180;
_rx =_ry = 0;
shootthreshold = 3840;
clock = new Timer();
//Imheart = loadImage("ironmanheart.png");
//------------------------------------//
//
//MENU INTERACTIONS
a = new ControlP5(this); //a is for menu
b = createFont("Verdana", height*(25/prevHeight)); //font for menu
yo = createFont("Verdana", height*(15/prevHeight)); //font for scoreboard form
a.addButton("Play") //name
.setPosition(width*(100/prevWidth), height*(150/prevHeight)) //position
.setSize(int(width*(250/prevWidth)), int(height*(200/prevHeight))) //size
.setFont(b) //font
;
a.addButton("Scoreboard")
.setPosition(width*(1500/prevWidth), height*(150/prevHeight))
.setSize(int(width*(250/prevWidth)), int(height*(200/prevHeight)))
.setFont(b)
;
a.addButton("Instructions")
.setPosition(width*(100/prevWidth), height*(750/prevHeight))
.setSize(int(width*(250/prevWidth)), int(height*(200/prevHeight)))
.setFont(b)
;
a.addButton("Quit")
.setPosition(width*(1500/prevWidth), height*(750/prevHeight))
.setSize(int(width*(250/prevWidth)), int(height*(200/prevHeight)))
.setFont(b)
;
//C = Back Button
c= new ControlP5(this); //c is for back button
c.addButton("Back")
.setPosition(width*(1500/prevWidth), height*(750/prevHeight))
.setSize(int(width*(250/prevWidth)), int(height*(200/prevHeight)))
.setFont(b)
;
//D = Scoreboard
d= new ControlP5(this); //d is for scoreboard screen
d.addTextfield("Insert Name Here").setPosition(200, 800).setSize(200, 50).setAutoClear(false).setFont(yo);
d.addBang("Submit").setPosition(400, 800).setSize(200, 50).setFont(yo);
}
void draw() {
image(menupic, 0, 0);
if (state == 0) { //Menu
textSize(30);
text("MENU", width*(150/prevWidth), height*(150/prevHeight));
c.hide();
a.show();
d.hide();
img.loadPixels();
SetThresholdsandRecords();// This function goes pixel by pixel looking for the shortest distance to the sensor and will provide the values of rx and ry which will be send to the aiming box
img.updatePixels();
image(img,width/2-width/8 ,height/2-height/8, width/4,height/4);
time = duration;
begin = millis();
for(int i=0; i<score.size();i++){
score.remove(i);
}
} else if (state == 1) { //Game
runGame();
a.hide();
//c.hide();
c.hide();
d.hide();
} else if (state == 2) { //Scoreboard
image(spic, 0, 0);
c.show();
a.hide();
d.show();
updateBoard();
} else if (state ==3) { //Instructions
background(0);
image(ipic, 850, 20);
textSize(40);
text("1. Click \"Play Game\"", width*(300/prevWidth), height*(400/prevHeight));
text("2. Stand on the designated platform", width*(300/prevWidth), height*(500/prevHeight));
text("3. Use your hand to aim, push your hand towards the kinect camera to shoot", width*(300/prevWidth), height*(600/prevHeight));
text("4. After the game write your name, submit, and see how well you did!", width*(300/prevHeight), height*(700/prevHeight));
c.show();
a.hide();
d.hide();
} else if (state ==4) { //Quit
exit();
}
}
//---------------------------Functions---------------------------------//
void runGame() {
image(bpic, 0, 0,width,height);
//image(Intro,0,0,width,height);
img.loadPixels();
//kinect
SetThresholdsandRecords();// This function goes pixel by pixel looking for the shortest distance to the sensor and will provide the values of rx and ry which will be send to the aiming box
img.updatePixels();
image(img,width-width/8 - width/80,height-height/8- height/80, width/8,height/8);
if (shoot==true) {
pushStyle();
fill(255, 0, 0);
popStyle();
}
//Targets
add_target();
target_functions();
//Aiming_box
aiming_Circle.update(rx, ry, shoot);
color TempColor = color_Aiming_Box();
aiming_Circle.display(TempColor, shoot, blasterSound, blaster);
//timer & Score
timer();
printScore();
}
void Play() {
state = 1;
}
void Scoreboard() {
state = 2;
}
void Back() {
state = 0;
}
void Instructions() {
state = 3;
}
void Quit() {
state = 4;
}
void Submit() { //Submit form for text input in Scoreboard
output = d.get(Textfield.class, "Insert Name Here").getText();
//println(output);
if((float)score.size() != 0.0){
hm.put(output, (float)score.size());
al.add((float)score.size());
}
}
void updateBoard() {
textSize(60);
text("SCOREBOARD", 750, 90);
java.util.Collections.sort(al, Collections.reverseOrder());
for (int i = 0; i < al.size(); i++) {
//println((float)al.get(i));
if ((float)al.get(i) != 0 && i<5) {
text((float)al.get(i), 1000, 300+i*60);
println(al.get(i));
}
}
for (Map.Entry me : hm.entrySet()) {
Object test = me.getKey();
if (test != null) {
if (al.contains(me.getValue())) {
//if (tracker<5) {
text((String)test, 700, 300+iu*60);
//println(me.getValue());
iu+= 1;
tracker+=1;
//}
} else {
continue;
}
}
}
iu = 0;
tracker = 0;
}
/*
if(al.contains(me.getValue())){
println(me.getValue());
if(hm.containsKey(me.getKey())){
if(me.getValue() != null){
text((String)me.getKey(),700,500-iu*100);
text((float)me.getValue(),1000, 500-iu*100);
iu+= 1;
}
}
}
}
iu = 0;
*/
//----Paulin's Function----//
void SetThresholdsandRecords() {
PImage dImg = kinect.getDepthImage(); //To get the default kinect image to find manually the thresholds
//image(dImg,0,0);
shoot = false;
int record = 4500;
_x=_y=0;
int totdis;
float total =0;
int[] depth = kinect.getRawDepth();
for (int x = 0; x<kinect.width; x++) {
for (int y =0; y< kinect.height; y++) {
int offset = x + y*kinect.width;
int d = depth[offset];
if (d>=minThresh && d<maxThresh) {
img.pixels[offset] = color(245,72,72);
//if (d<record) {
//record = d;
_x+=x;
_y+=y;
//}
//_x+=x;
//_y+=y;
total++;
} else if (d>=minShoot && d<=minThresh){
img.pixels[offset] = color(255,0,0) ;
shoot=true;
_x+=x;
_y+=y;
total++;
}
else{
img.pixels[offset] = color(0,0,0) ;
}
}
}
if (total>0) {
_x= _x/total;
_y= _y/total;
}
_rx += ((640-_x)-_rx)*scale;
rx = map(_rx, 150, 640-150, 0, width);
_ry += (_y-_ry)*scale;
ry = map(_ry, 110, 480-110, 0, height);
//ellipse(rx,ry,30,30);
//dists.add(record);
//if (dists.size()==numfc)
// dists.remove(0);
//totdis = dists.get(0) - dists.get(dists.size()-1);
////println("totdis -> ",totdis);
//println(totdis);
//if (totdis>shootthreshold) {
// shoot =true;
//}
//println("shoot ->", shoot);
}
//To provide a different color to the aiming box if it is shooting
color color_Aiming_Box() {
color Color;
Color = color(255, 255, 255);
for (int i = 0; i<targets.size(); i++) {
if (aiming_Circle.rx>targets.get(i).x
&&aiming_Circle.rx<(targets.get(i).x+targets.get(i).w)
&&aiming_Circle.ry>targets.get(i).y
&&aiming_Circle.ry<(targets.get(i).y+targets.get(i).h)) {
Color = color(255, 0, 0);
break;
}
}
return(Color);
}
//To move the angle of the kinect
void keyPressed() {
if (key == CODED) {
if (keyCode == UP) {
angle++;
} else if (keyCode == DOWN) {
angle--;
}
angle = constrain(angle, 0, 30);
kinect.setTilt(angle);
}
}
//Set up timer in the right corner
void timer() {
float lettersize = height*.054;
float timerposition = width*.04125;
float Yposition = height*.08125;
float position = timerposition + (width*.125);
//float secondposition = position + (width*.022);
if (time > 0) {
time = duration - (millis() - begin)/1000;
pushStyle();
stroke(188, 23, 23);
strokeWeight(width*.003);
fill(188, 166, 23, 100);
ellipse(timerposition+width*.01, Yposition-lettersize/3.1+height*.01, height*.128, height*.128);
fill(255);
textSize(lettersize);
text(time, timerposition-width*.02, Yposition+height*.01);
popStyle();
}
if (time ==0) {
state = 2;
textSize(40);
text("GAME OVER!: ", 600, 90);
}
}
// add a target every 120 frames
void add_target() {
randint = int(random(3));
pattern = int(random(45));
if(frameCount%10==0){
targets.add(new Targets(shoot, randint, pattern));
}
//if(time<60 && time>30){
//if (frameCount%80==0) {
// targets.add(new Targets(shoot, randint, pattern));
//}
//}
//else if (time>60 && time<90){
// if (frameCount%100==0) {
// targets.add(new Targets(shoot, randint, pattern));
//}
//}
//else if(time>90){
// if (frameCount%100==0) {
// targets.add(new Targets(shoot, randint, pattern));
//}
//}
//else if(time<30){
// if (frameCount%120==0) {
// targets.add(new Targets(shoot, randint, pattern));
// }
//}
}
//All the targets functions
void target_functions() {
for (int i = 0; i<targets.size(); i++) {
targets.get(i).display();
targets.get(i).destroy(shoot, aiming_Circle);
targets.get(i).update();
if (targets.get(i).destroyed == true) {
//println("Object being destroyed -> target size -> ",targets.size());
//blasterSound.play();
boom.display(targets.get(i).x, targets.get(i).y);
targets.remove(i);
score.add(i);
//println(" destroyed -> target size -> ",targets.size());
}
}
}
void printScore() {
float lettersize = height*.054;
float Yposition = height*.08125;
float ScorePos = width*.75;
float PointPos = ScorePos + height*.15;
float numPos = PointPos + height*.03;
pushStyle();
stroke(188, 23, 23);
strokeWeight(width*.003);
fill(188, 166, 23, 100);
ellipse(numPos+width*.09, Yposition-lettersize/3.1+height*.01, height*.128, height*.128);
fill(255);
textSize(lettersize);
//text("Score", ScorePos, Yposition);
//text(":", PointPos, Yposition);
text(score.size(), PointPos+width*.095, Yposition+height*.01);
popStyle();
}
void intro() {
}
Exhibiting my work in the IM Showcase, as well as interacting with everybody’s projects was such an enjoyable experience for me. I did face some issues with the quality of the projector and the lighting after I noticed that the webcam wasn’t picking up a lot of movements because of the dim lighting. But I managed to solve at least on of those issues through setting up my phone’s flashlight next to the webcam, which increased the response of the graphics to the user’s movements. It was very rewarding to see people enjoy interacting with the project, and provide feedback about the experience. I also loved hearing Nick say that he feels like an “airbender” when interacting with the graphics, and my friends running away from the screen screaming “IT’S CHASING ME”.
At first, I had intended to lower the brightness on the laptop display, so that the user can try to figure out how to interact with the project. But unfortunately the colors displayed by the projector were a bit faded, so the graphics weren’t showing in the opacity that I originally intended to display. You can also notice the flashlight placed on the pedestal as well, which drastically improved the speed of the interaction, since it allowed the Webcam to detect figures more easily. I enjoyed observing how people tried out a range of movements with the bodies and limbs, in order to try to see the extent of the effect their movements have on the motion graphics. I’ve had people run, jump up and down and flail their arms around to see if the projection will follow their movements, so that was fun to watch.
Graphics Code:
import java.util.Calendar;
import ch.bildspur.postfx.builder.*;
import ch.bildspur.postfx.pass.*;
import ch.bildspur.postfx.*;
import oscP5.*;
import netP5.*;
PostFX fx;
//PVector center;
Particles particles;
float centerX, centerY;
float oscX, oscY;
OscP5 oscP5;
NetAddress myRemoteLocation;
void setup(){
//size(displayWidth, displayHeight, P3D);
fullScreen(P3D);
smooth(8);
background(0);
// init form
centerX = width/2;
centerY = height/2;
//center = new PVector(width/2, height/2);
fx = new PostFX(this);
particles = new Particles(centerX, centerY);
background(0);
oscP5 = new OscP5(this,12000);
myRemoteLocation = new NetAddress("127.0.0.1",1234);
}
void draw(){
// floating towards mouse position
particles.run(oscX*50, oscY*50);
float ratio = 0.5;
float intensity = 10;
//diffuse
fx.render()
.blur(
round(map(ratio, 0, 1, 0, intensity)),
map(ratio, 1, 0, 0, intensity))
.compose();
//decay
noStroke();
fill(0, 15);
rect(0, 0, width, height);
}
// timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}
/* incoming osc message are forwarded to the oscEvent method. */
void oscEvent(OscMessage theOscMessage) {
/* print the address pattern and the typetag of the received OscMessage */
print("### received an osc message.");
print(" addrpattern: "+theOscMessage.addrPattern());
print(" message 1: "+theOscMessage.get(0).floatValue());
print(" message 2: "+theOscMessage.get(1).floatValue());
println(" typetag: "+theOscMessage.typetag());
oscX = theOscMessage.get(0).floatValue();
oscY = theOscMessage.get(1).floatValue();
}
I’ve probably said this on numerous occasions, but my main interest in interactive media lies in finding the intersection between art and technology. I wanted to find a way to combine everything we’ve learned in class about coding and using processing as a creative platform, with my passion for creating art and aesthetically appealing interactive installations. For this project, I wanted the user to be able to use their bodies as a medium or as a brush of some sorts, to paint on a canvas, through interacting with installation. I used a particle system, where I created an array of A LOT of objects or particles which are added in random positions along the radius of a circle revolving in the center of the screen. The interactive aspect of it, is that the webcam detects any movement on an x, y, z axis and creates movement in the graphics that mimic the user’s moves. This happens through adding a new center or circle in every place the user moves, and this circle has an intensity of blur that makes it appear to be moving smoothly with the user.
My friends having too much fun:
Despite the users enjoying the overall interaction, I noticed several aspects I could improve and build upon:
The purpose of the project wasn’t clear at first, and some of the users were confused. This could be improved through using a large projection of the screen so that passersby can easily observe the effect of their movements on the graphics.
Some users said that I should work on increasing the blur or changing the movement of the main circle, since they could see the trail of circles being added as they move.
Adding more movement specific effects, in the sense that a smaller movement (moving an arm) would produce a different effect than moving the entire body.
My final project idea was to create a magical book with my drawings on some of the pages, and have the drawings come to life through animation. Since a few people have asked me in the showcase, I came up with this idea mainly because of the first drawing that I used. It’s a pen sketch of one of the rooms in the Shakespeare and company bookstore, which I visited in Paris. I did my class research on the bookstore, and so found that artists, writers, and travelers were free to stay at the bookstore and sleep in the beds as long as they wished for, as along as they read one book a day, wrote a one page autobiography for the bookstore’s archives, and helped out a little around the shop. Another thing about the bookstore was that people would leave notes in a box, that the book-keepers would place into random books, to be found by readers. So looking at this drawing always made me imagine the people who stayed there, and the books they read. It all seemed very magical to me, so I decided to bring the drawing to life, with stop motion animation. I’ve been intrigued by storytelling through art lately, so this was the perfect way to experiment with it.
Disclaimer: as you’ll see in this project, I’m a big Harry Potter fan.
I started with the idea of using AR markers by placing them on the side of the page and having the drawing projected onto each page but then I decided that I really want to focus on creating the feeling of each of the drawings coming to life, so I was determined to find a way to use my drawing itself as the marker. Through research and trial and error, I finally got it working properly.
I tried out different things and learnt that AR markers actually don’t need to be in square form, only the edge needs to be a square. So I inserted the final drawing marker into the data folder and changed the code, edge percentage, and positioning, and size. I used example AR code that displayed a cube on top of the marker, and changed the code so as to have a video file play on top of the marker. While this worked perfectly fine on the screen, when projected, I found out there was a big issue I hadn’t considered. That if the video was projected on the same place that the camera was facing, the camera would pick up the projection and so the projector would project that, and it created an endless loop of a mirror effect. In order to fix this, I decided to have the projector project on the drawing on one page, and the camera pick up the marker on the other page, which would be either a drawing or a page number. I eventually went with the page number so as to not distract from the animated drawings.
Having the several drawings was much more complicated than having just one drawing, so I tried different methods, such as arraylist, array, and for loop. It feels amazing to be able to go through the code and actually understand what everything is doing, because a few months ago coding was so foreign to me.
While setting up, I placed the tripods as far away from the book as possible, so as to create the illusion that they are not there, and allow the audience to be immersed into the ‘magic’. I accidentally printed the drawings in black and white, and then decided that it looked better this way, because it would make the style of the different drawings match more. I also printed a specific quote for each page, that created sort of story behind each drawing, to relate it to magic.
I user tested my product and found that everything was working fine, except that the projection wasn’t going white when the marker was not detected, and instead showed what the camera was picking up continuously.
This was something that I was able to fix easily, with adding just one more line of code, but it felt great because a few weeks ago, I would have not at all been able to navigate my way through code, and now I could.
After fixing the issue, I did another user test;
This time everything worked perfectly fine. However I felt that I want it to be a bit longer, and have some sort of finish, since people kept going to the next page, so I decided to add one more animation, that would make it a bit longer, create a sense of a finish, and bring it all back to the theme of the book; magic.
I displayed this one in color, and without a background drawing, so as to make it stand out as the ending (animation of a pickup truck leaving a note that says “It’s magic” before driving away, as though to say how the book operates).
The showcase was a lot of fun because I got to see people interact with my product. I loved that it made them smile. Several of the people actually jumped back when they saw the drawings were moving, which was so entertaining to watch. People kept looking around to find out how it’s working, and the funny thing was that a few of them looked directly at the projector and camera but did not notice the drawing was being projected through them. When they would ask me how it works, I would tell them it will be revealed at the end (the note that said “it’s magic!” – which Aaron said was cheesy, but I find hilarious – before telling them how it really worked).
I was a bit worried that when turning the page, people might hold the place where the marker (page number) is, and the animation wouldn’t appear, but thankfully out of everyone maybe only two people did this, who I had to explain it to. It was really interesting to see how people interacted with it. Some people were not sure if they should open it, others kept flipping the pages back and forth to see if the animations would still appear on each drawing, which they did, some people really spent time taking in the quotes, while others were too distracted by the drawing to read the quotes, some people kept touching the number because they thought it’s some sort of button or pressure sensor, and some people kept waving their hand over the drawing to see what would happen. It also made me happy how some people took videos of it, or went to get someone else to come see it as well. I was also happy because a friend of mine who basically does nothing but criticize me was very amused by it, and was finally impressed!
*ps the person in this photo isn’t the friend I’m talking about
I’m really glad that my product had the effect on people that I wanted. I can’t believe how far I’ve come considering I could not understand any coding before this semester. Coding was such a foreign language to me and I’ve learnt a lot. In the beginning of this semester I could not even understand what the example code was saying, but now I can go through this entire code and know what’s going on and what each line is doing. I’m really thankful for your patience and support Aaron. And to the entire class and instructors, because everyone was always really supportive. Thank you.
I user tested my product and found that everything was working fine, except that the projection wasn’t going white when the marker was not detected, and instead showed what the camera was picking up continuously.
This was something that I was able to fix easily, with adding just one more line of code, but it felt great because a few weeks ago, I would have not at all been able to navigate my way through code, and now I could.
After fixing the issue, I did another user test;
This time everything worked perfectly fine. However I felt that I want it to be a bit longer, and have some sort of finish, since people kept going to the next page, so I decided to add one more animation, that would make it a bit longer, create a sense of a finish, and bring it all back to the theme of the book; magic.
I’m glad I added the last animation because I think it really helped create an ending. A lot of little challenges arised during this project and I’m so happy that it turned out how I envisioned it.
I have always been fascinated with using nature as a medium for interaction and a source for new creation, especially in the area of music.
WaterBox is a musical loop station where depending on the user’s physical interaction with water plays different loops either individually or simultaneously. And, the track type changes depending on the roughness of the waves and ripples of the water surface. The final product is created using Kinect v2 to capture the waves of the water surface by its depth, and Touche Advanced Touch Sensor with Arduino for the capacitative sensor and interaction with water. Through the WaterBox, I wanted to share the rich feeling of interacting with water and fun of creating different music with physical motion of your hands in the water.
In terms of technical side, the project entails the use of Arduino Uno, a Kinect v2, a container to contain shallow level of water, and a stand that will hold the Kinect in place above the container pointing towards the water. The container was created with transparent acrylic with 18cm x 18cm x 15cm in dimension where the bottom was colored blue for clear detection of the Kinect v2.
The ideal instance of a user interaction would be where users would play around with the water, creating different waves and ripples on the surface. And, depending on the number of fingers (or, to be more precise, the surface area of your hand), different loops will be played on top of one another. Meanwhile, the kinect will capture the depth of the water, and depending on the roughness of the waves, it will change the track of the loops played.
Here are some of user-testing footages:
Development Stages
Initial Stage of using water with Kinect & Sound
Connecting Touche Sensor with Water & Sound
<Source Code>
Processing*
Main Code
Graph Class Library (Touche Advance Sensor)
Serial Link (Touche Advanced Sensor)
import org.openkinect.processing.*;
import processing.sound.*;
int trackType = 6;
int trackCount = 4;
int currentTrackType = 0;
int counter = 0;
String trackName;
/*
============ Touche Advanced Touch Sensor ==============
Source: https://github.com/Illutron/AdvancedTouchSensing
========================================================
*/
Graph MyArduinoGraph = new Graph(150, 80, 500, 300, color (200, 20, 20));
float[][] gesturePoints = new float[4][2];
float[] gestureDist = new float[4];
String[] names = {"Nothing", "One Finger", "Two Finger", "Hand In Water"};
/* ===================================================== */
/* ==== Music Loops ==== */
SoundFile[][] soundPlayers = new SoundFile[trackType][trackCount];
//array to hold the volumes/rate for each track
float[] volumes = new float[trackCount];
//array to hold the volume/rate destinations, to smoothly fade in and out
float[] volumeDestinations = new float[trackCount];
/* ===================== */
/* ==== Kinect Code ==== */
Kinect2 kinect2;
// Min & Max Threshold for Calibration
float minThresh = 570;
float maxThresh = 690;
boolean isSwitched;
PImage img, video;
/* ====================== */
void setup() {
size(1000, 900);
// ==== Touche Setup Code ==== //
PortSelected=8;
SerialPortSetup();
// ============================ //
for (int j=0; j < 6; j++)
{
// ==== NAMING EACH TRACK IN THE SOUND PLAYER 2D ARRAY ==== //
if (j==0)
{
trackName = "deep";
}
else if (j==1)
{
trackName = "edm";
}
else if (j==2)
{
trackName = "hiphop";
}
else if (j==3)
{
trackName = "jazz";
}
else if (j==4)
{
trackName = "latin";
}
else {
trackName = "slow";
}
for (int i=0; i < soundPlayers[j].length; i++)
{
String name = trackName;
name += str(i);
name += ".wav";
println(name);
soundPlayers[j][i] = new SoundFile(this, name);
soundPlayers[j][i].loop();
soundPlayers[j][i].amp(0.001);
volumes[i]=0;
volumeDestinations[i]=0;
}
}
// Kinect Initialization
kinect2 = new Kinect2(this);
kinect2.initRegistered();
kinect2.initDepth();
kinect2.initDevice();
img = createImage(kinect2.depthWidth, kinect2.depthHeight, RGB);
isSwitched = false;
}
void draw() {
background(255);
int pixelCount = 0;
float minDepthLevel = maxThresh;
// Getting soundIndex for Touche Sensor
int soundIndex = advancedTouch();
/* ===================================================== */
img.loadPixels();
video = kinect2.getRegisteredImage();
int[] depth = kinect2.getRawDepth();
// Detect Pixels using Kinect
for (int x = 0; x < kinect2.depthWidth; x++)
{
for (int y = 0; y < kinect2.depthHeight; y++)
{
int index = x + y * kinect2.depthWidth;
int d = depth[index];
if (d > minThresh
&& d < maxThresh
&& red(video.pixels[index]) < 100
&& green(video.pixels[index]) > 180
&& blue(video.pixels[index]) > 170
// Boundaries to capture only the box on the screen
&& 215 < x
&& 350 > x
&& 125 < y
&& 265 > y
) {
pixelCount++;
img.pixels[index] = color(red(video.pixels[index]), green(video.pixels[index]), blue(video.pixels[index]));
if (d < minDepthLevel)
{
minDepthLevel = d;
}
}
else
{
img.pixels[index] = color(0);
}
}
}
/* ========DEBUG CODE======== */
int mouseIndex = mouseX%kinect2.depthWidth + mouseY%kinect2.depthHeight * kinect2.depthWidth;
text(red(video.pixels[mouseIndex]), 600, 600);
text(green(video.pixels[mouseIndex]), 600, 650);
text(blue(video.pixels[mouseIndex]), 600, 700);
text(mouseX, 600, 750);
text(mouseY-480, 680, 750);
text(pixelCount, 600, 780);
text(minDepthLevel, 680, 780);
text(currentTrackType, 880, 780);
/* ========================== */
img.updatePixels();
// Uncomment below if you want to see whether the Kinect is catching the water pixels
//image(img, 0, 480);
noStroke();
/* ========== Calibration Needed depending on space / light ========== */
// If the catched pixcelCount is in between 300 and 400 & the whole hand is in the water,
// then change the track type.
if (
pixelCount >= 300
&& pixelCount <= 400
&& minDepthLevel <= 590
&&soundIndex == 3
&& !isSwitched)
{
counter++;
if(counter > 800) {
isSwitched = true;
currentTrackType++;
currentTrackType %= 6;
}
}
else if (isSwitched)
{
isSwitched = false;
counter = 0;
}
/* =================================================================== */
if (soundIndex > 1)
{
for (int i=1; i< soundIndex+1; i++)
{
volumeDestinations[i-1] = 1;
if (soundIndex == 3)
{
volumeDestinations[i] = 1;
}
}
}
else if (soundIndex == 1)
{
volumeDestinations[0] = 1;
}
for (int j=0; j<soundPlayers.length; j++)
{
for (int i=0; i<soundPlayers[j].length; i++)
{
if (j != currentTrackType)
{
// set other tracks amplitude to zero
soundPlayers[j][i].amp(0);
}
else
{
//set volume
volumes[i]=smoothing(volumes[i], volumeDestinations[i]);
soundPlayers[j][i].amp(volumes[i]);
//continuously fade volume out
volumeDestinations[i]-=.1;
//constrian the fade out to 0
volumeDestinations[i] = constrain(volumeDestinations[i],0,1);
}
}
}
}
//smoothing for fading in and out
float smoothing(float current, float destination) {
current += (destination-current)*.5;
return current;
}
int advancedTouch() {
/* ===============================================================
Touche Advanced Touch Sensor Code
=============================================================== */
if ( DataRecieved3 ) {
pushMatrix();
pushStyle();
MyArduinoGraph.yMax=600;
MyArduinoGraph.yMin=-200;
MyArduinoGraph.xMax=int (max(Time3));
//MyArduinoGraph.DrawAxis();
MyArduinoGraph.smoothLine(Time3, Voltage3);
popStyle();
popMatrix();
/* ====================================================================
Gesture compare
==================================================================== */
float totalDist = 0;
int currentMax = 0;
float currentMaxValue = -1;
for (int i = 0; i < 4;i++) {
//println("Index: " + i);
//println(gesturePoints[i][0], gesturePoints[i][1]);
// Calibration for each category
if (mousePressed && mouseX > 750 && mouseX<800 && mouseY > 100*(i+1) && mouseY < 100*(i+1) + 50)
{
fill(255, 0, 0);
gesturePoints[i][0] = Time3[MyArduinoGraph.maxI];
gesturePoints[i][1] = Voltage3[MyArduinoGraph.maxI];
}
else
{
fill(255, 255, 255);
}
//calucalte individual dist
gestureDist[i] = dist(Time3[MyArduinoGraph.maxI], Voltage3[MyArduinoGraph.maxI], gesturePoints[i][0], gesturePoints[i][1]);
totalDist = totalDist + gestureDist[i];
if(gestureDist[i] < currentMaxValue || i == 0)
{
currentMax = i;
currentMaxValue = gestureDist[i];
}
}
totalDist=totalDist/3;
for (int i = 0; i < 4;i++)
{
float currentAmount = 0;
currentAmount = 1-gestureDist[i]/totalDist;
if(currentMax == i) {
fill(0,0,0);
//text(names[i],50,450);
fill(currentAmount*255.0f, 0, 0);
}
else {
fill(255,255,255);
}
stroke(0, 0, 0);
rect(750, 100 * (i+1), 50, 50);
fill(0,0,0);
textSize(30);
text(names[i],810,100 * (i+1)+25);
fill(255, 0, 0);
rect(800,100* (i+1), max(0,currentAmount*50),50);
}
return currentMax;
}
return 0;
}
/* =================================================================================
The Graph class contains functions and variables that have been created to draw
graphs. Here is a quick list of functions within the graph class:
Graph(int x, int y, int w, int h,color k)
DrawAxis()
Bar([])
smoothLine([][])
DotGraph([][])
LineGraph([][])
=================================================================================*/
class Graph
{
float maxY = 0;
float maxX = 0;
int maxI = 0;
boolean Dot=true; // Draw dots at each data point if true
boolean RightAxis; // Draw the next graph using the right axis if true
boolean ErrorFlag=false; // If the time array isn't in ascending order, make true
boolean ShowMouseLines=true; // Draw lines and give values of the mouse position
int xDiv=5, yDiv=5; // Number of sub divisions
int xPos, yPos; // location of the top left corner of the graph
int Width, Height; // Width and height of the graph
color GraphColor;
color BackgroundColor=color(255);
color StrokeColor=color(180);
String Title="Title"; // Default titles
String xLabel="x - Label";
String yLabel="y - Label";
float yMax=1024, yMin=0; // Default axis dimensions
float xMax=10, xMin=0;
float yMaxRight=1024, yMinRight=0;
Graph(int x, int y, int w, int h, color k) { // The main declaration function
xPos = x;
yPos = y;
Width = w;
Height = h;
GraphColor = k;
}
void DrawAxis() {
/* =========================================================================================
Main axes Lines, Graph Labels, Graph Background
========================================================================================== */
fill(BackgroundColor);
color(0);
stroke(StrokeColor);
strokeWeight(1);
int t=60;
rect(xPos-t*1.6, yPos-t, Width+t*2.5, Height+t*2); // outline
textAlign(CENTER);
textSize(18);
float c=textWidth(Title);
fill(BackgroundColor);
color(0);
stroke(0);
strokeWeight(1);
rect(xPos+Width/2-c/2, yPos-35, c, 0); // Heading Rectangle
fill(0);
text(Title, xPos+Width/2, yPos-37); // Heading Title
textAlign(CENTER);
textSize(14);
text(xLabel, xPos+Width/2, yPos+Height+t/1.5); // x-axis Label
rotate(-PI/2); // rotate -90 degrees
text(yLabel, -yPos-Height/2, xPos-t*1.6+20); // y-axis Label
rotate(PI/2); // rotate back
textSize(10);
noFill();
stroke(0);
smooth();
strokeWeight(1);
//Edges
line(xPos-3, yPos+Height, xPos-3, yPos); // y-axis line
line(xPos-3, yPos+Height, xPos+Width+5, yPos+Height); // x-axis line
stroke(200);
if (yMin<0) {
line(xPos-7, // zero line
yPos+Height-(abs(yMin)/(yMax-yMin))*Height, //
xPos+Width,
yPos+Height-(abs(yMin)/(yMax-yMin))*Height
);
}
if (RightAxis) { // Right-axis line
stroke(0);
line(xPos+Width+3, yPos+Height, xPos+Width+3, yPos);
}
/* =========================================================================================
Sub-devisions for both axes, left and right
========================================================================================== */
stroke(0);
for (int x=0; x<=xDiv; x++) {
/* =========================================================================================
x-axis
========================================================================================== */
line(float(x)/xDiv*Width+xPos-3, yPos+Height, // x-axis Sub devisions
float(x)/xDiv*Width+xPos-3, yPos+Height+5);
textSize(10); // x-axis Labels
String xAxis=str(xMin+float(x)/xDiv*(xMax-xMin)); // the only way to get a specific number of decimals
String[] xAxisMS=split(xAxis, '.'); // is to split the float into strings
text(xAxisMS[0]+"."+xAxisMS[1].charAt(0), // ...
float(x)/xDiv*Width+xPos-3, yPos+Height+15); // x-axis Labels
}
/* =========================================================================================
left y-axis
========================================================================================== */
for (int y=0; y<=yDiv; y++) {
line(xPos-3, float(y)/yDiv*Height+yPos, // ...
xPos-7, float(y)/yDiv*Height+yPos); // y-axis lines
textAlign(RIGHT);
fill(20);
String yAxis=str(yMin+float(y)/yDiv*(yMax-yMin)); // Make y Label a string
String[] yAxisMS=split(yAxis, '.'); // Split string
text(yAxisMS[0]+"."+yAxisMS[1].charAt(0), // ...
xPos-15, float(yDiv-y)/yDiv*Height+yPos+3); // y-axis Labels
/* =========================================================================================
right y-axis
========================================================================================== */
if (RightAxis) {
color(GraphColor);
stroke(GraphColor);
fill(20);
line(xPos+Width+3, float(y)/yDiv*Height+yPos, // ...
xPos+Width+7, float(y)/yDiv*Height+yPos); // Right Y axis sub devisions
textAlign(LEFT);
String yAxisRight=str(yMinRight+float(y)/ // ...
yDiv*(yMaxRight-yMinRight)); // convert axis values into string
String[] yAxisRightMS=split(yAxisRight, '.'); //
text(yAxisRightMS[0]+"."+yAxisRightMS[1].charAt(0), // Right Y axis text
xPos+Width+15, float(yDiv-y)/yDiv*Height+yPos+3); // it's x,y location
noFill();
}
stroke(0);
}
}
/* =========================================================================================
Bar graph
========================================================================================== */
void Bar(float[] a, int from, int to) {
stroke(GraphColor);
fill(GraphColor);
if (from<0) { // If the From or To value is out of bounds
for (int x=0; x<a.length; x++) { // of the array, adjust them
rect(int(xPos+x*float(Width)/(a.length)),
yPos+Height-2,
Width/a.length-2,
-a[x]/(yMax-yMin)*Height);
}
}
else {
for (int x=from; x<to; x++) {
rect(int(xPos+(x-from)*float(Width)/(to-from)),
yPos+Height-2,
Width/(to-from)-2,
-a[x]/(yMax-yMin)*Height);
}
}
}
void Bar(float[] a ) {
stroke(GraphColor);
fill(GraphColor);
for (int x=0; x<a.length; x++) { // of the array, adjust them
rect(int(xPos+x*float(Width)/(a.length)),
yPos+Height-2,
Width/a.length-2,
-a[x]/(yMax-yMin)*Height);
}
}
/* =========================================================================================
Dot graph
========================================================================================== */
void DotGraph(float[] x, float[] y) {
for (int i=0; i<x.length; i++) {
strokeWeight(2);
stroke(GraphColor);
noFill();
smooth();
ellipse(
xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width,
yPos+Height-(y[i]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height,
2, 2
);
}
}
/* =========================================================================================
Streight line graph
========================================================================================== */
void LineGraph(float[] x, float[] y) {
for (int i=0; i<(x.length-1); i++) {
strokeWeight(2);
stroke(GraphColor);
noFill();
smooth();
line(xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width,
yPos+Height-(y[i]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height,
xPos+(x[i+1]-x[0])/(x[x.length-1]-x[0])*Width,
yPos+Height-(y[i+1]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height);
}
}
/* =========================================================================================
smoothLine
========================================================================================== */
void smoothLine(float[] x, float[] y) {
float tempyMax=yMax, tempyMin=yMin;
if (RightAxis) {
yMax=yMaxRight;
yMin=yMinRight;
}
int xlocation=0, ylocation=0;
// if(!ErrorFlag |true ){ // sort out later!
beginShape();
strokeWeight(6);
stroke(GraphColor);
noFill();
smooth();
maxY = 0;
//find max
for (int i=0; i<x.length; i++) {
if (maxY < y[i])
{
maxY =y[i];
maxI = i;
}
}
for (int i=0; i<x.length; i++) {
/* ===========================================================================
Check for errors-> Make sure time array doesn't decrease (go back in time)
===========================================================================*/
if (i<x.length-1) {
if (x[i]>x[i+1]) {
ErrorFlag=true;
}
}
/* =================================================================================
First and last bits can't be part of the curve, no points before first bit,
none after last bit. So a streight line is drawn instead
================================================================================= */
if (i==0 || i==x.length-2)line(xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width,
yPos+Height-(y[i]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height,
xPos+(x[i+1]-x[0])/(x[x.length-1]-x[0])*Width,
yPos+Height-(y[i+1]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height);
/* =================================================================================
For the rest of the array a curve (spline curve) can be created making the graph
smooth.
================================================================================= */
curveVertex( xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width,
yPos+Height-(y[i]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height);
/* =================================================================================
If the Dot option is true, Place a dot at each data point.
================================================================================= */
if (i == maxI)
{
ellipse(
xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width,
yPos+Height-(y[i]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height,
20, 20
);
}
if (Dot)ellipse(
xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width,
yPos+Height-(y[i]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height,
2, 2
);
/* =================================================================================
Highlights points closest to Mouse X position
=================================================================================*/
if ( abs(mouseX-(xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width))<5 ) {
float yLinePosition = yPos+Height-(y[i]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height;
float xLinePosition = xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width;
strokeWeight(1);
stroke(240);
// line(xPos,yLinePosition,xPos+Width,yLinePosition);
strokeWeight(2);
stroke(GraphColor);
ellipse(xLinePosition, yLinePosition, 4, 4);
}
}
endShape();
yMax=tempyMax;
yMin=tempyMin;
float xAxisTitleWidth=textWidth(str(map(xlocation, xPos, xPos+Width, x[0], x[x.length-1])));
if ((mouseX>xPos&mouseX<(xPos+Width))&(mouseY>yPos&mouseY<(yPos+Height))) {
if (ShowMouseLines) {
// if(mouseX<xPos)xlocation=xPos;
if (mouseX>xPos+Width)xlocation=xPos+Width;
else xlocation=mouseX;
stroke(200);
strokeWeight(0.5);
fill(255);
color(50);
// Rectangle and x position
line(xlocation, yPos, xlocation, yPos+Height);
rect(xlocation-xAxisTitleWidth/2-10, yPos+Height-16, xAxisTitleWidth+20, 12);
textAlign(CENTER);
fill(160);
text(map(xlocation, xPos, xPos+Width, x[0], x[x.length-1]), xlocation, yPos+Height-6);
// if(mouseY<yPos)ylocation=yPos;
if (mouseY>yPos+Height)ylocation=yPos+Height;
else ylocation=mouseY;
// Rectangle and y position
stroke(200);
strokeWeight(0.5);
fill(255);
color(50);
line(xPos, ylocation, xPos+Width, ylocation);
//int yAxisTitleWidth=int(textWidth(str(map(ylocation, yPos, yPos+Height, y[0], y[y.length-1]))) );
rect(xPos-15+3, ylocation-6, -60, 12);
textAlign(RIGHT);
fill(GraphColor);//StrokeColor
// text(map(ylocation,yPos+Height,yPos,yMin,yMax),xPos+Width+3,yPos+Height+4);
text(map(ylocation, yPos+Height, yPos, yMin, yMax), xPos -15, ylocation+4);
if (RightAxis) {
stroke(200);
strokeWeight(0.5);
fill(255);
color(50);
rect(xPos+Width+15-3, ylocation-6, 60, 12);
textAlign(LEFT);
fill(160);
text(map(ylocation, yPos+Height, yPos, yMinRight, yMaxRight), xPos+Width+15, ylocation+4);
}
noStroke();
noFill();
}
}
}
void smoothLine(float[] x, float[] y, float[] z, float[] a ) {
GraphColor=color(188, 53, 53);
smoothLine(x, y);
GraphColor=color(193-100, 216-100, 16);
smoothLine(z, a);
}
}
import processing.serial.*;
int SerialPortNumber=2;
int PortSelected=2;
/* =================================================================================
Global variables
=================================================================================*/
int xValue, yValue, Command;
boolean Error=true;
boolean UpdateGraph=true;
int lineGraph;
int ErrorCounter=0;
int TotalRecieved=0;
/* =================================================================================
Local variables
=================================================================================*/
boolean DataRecieved1=false, DataRecieved2=false, DataRecieved3=false;
float[] DynamicArrayTime1, DynamicArrayTime2, DynamicArrayTime3;
float[] Time1, Time2, Time3;
float[] Voltage1, Voltage2, Voltage3;
float[] current;
float[] DynamicArray1, DynamicArray2, DynamicArray3;
float[] PowerArray= new float[0]; // Dynamic arrays that will use the append()
float[] DynamicArrayPower = new float[0]; // function to add values
float[] DynamicArrayTime= new float[0];
String portName;
String[] ArrayOfPorts=new String[SerialPortNumber];
boolean DataRecieved=false, Data1Recieved=false, Data2Recieved=false;
int incrament=0;
int NumOfSerialBytes=8; // The size of the buffer array
int[] serialInArray = new int[NumOfSerialBytes]; // Buffer array
int serialCount = 0; // A count of how many bytes received
int xMSB, xLSB, yMSB, yLSB; // Bytes of data
Serial myPort; // The serial port object
/* =================================================================================
A once off serail port setup function. In this case the selection of the speed,
the serial port and clearing the serial port buffer
=================================================================================*/
void SerialPortSetup() {
// text(Serial.list().length,200,200);
portName= Serial.list()[PortSelected];
ArrayOfPorts=Serial.list();
println(ArrayOfPorts);
myPort = new Serial(this, portName, 115200);
//delay(50);
//myPort.clear();
//myPort.buffer(20);
}
/* ============================================================
serialEvent will be called when something is sent to the
serial port being used.
============================================================ */
void serialEvent(Serial myPort) {
while (myPort.available ()>0)
{
/* ============================================================
Read the next byte that's waiting in the buffer.
============================================================ */
int inByte = myPort.read();
myPort.write(0);
if (inByte==0)serialCount=0;
if (inByte>255) {
println(" inByte = "+inByte);
exit();
}
// Add the latest byte from the serial port to array:
serialInArray[serialCount] = inByte;
serialCount++;
Error=true;
if (serialCount >= NumOfSerialBytes ) {
serialCount = 0;
TotalRecieved++;
int Checksum=0;
// Checksum = (Command + yMSB + yLSB + xMSB + xLSB + zeroByte)%255;
for (int x=0; x<serialInArray.length-1; x++) {
Checksum=Checksum+serialInArray[x];
}
Checksum=Checksum%255;
if (Checksum==serialInArray[serialInArray.length-1]) {
Error = false;
DataRecieved=true;
}
else {
Error = true;
// println("Error: "+ ErrorCounter +" / "+ TotalRecieved+" : "+float(ErrorCounter/TotalRecieved)*100+"%");
DataRecieved=false;
ErrorCounter++;
println("Error: "+ ErrorCounter +" / "+ TotalRecieved+" : "+float(ErrorCounter/TotalRecieved)*100+"%");
}
}
if (!Error) {
int zeroByte = serialInArray[6];
// println (zeroByte & 2);
xLSB = serialInArray[3];
if ( (zeroByte & 1) == 1) xLSB=0;
xMSB = serialInArray[2];
if ( (zeroByte & 2) == 2) xMSB=0;
yLSB = serialInArray[5];
if ( (zeroByte & 4) == 4) yLSB=0;
yMSB = serialInArray[4];
if ( (zeroByte & 8) == 8) yMSB=0;
// println( "0\tCommand\tyMSB\tyLSB\txMSB\txLSB\tzeroByte\tsChecksum");
// println(serialInArray[0]+"\t"+Command +"\t"+ yMSB +"\t"+ yLSB +"\t"+ xMSB +"\t"+ xLSB+"\t" +zeroByte+"\t"+ serialInArray[7]);
// >=====< combine bytes to form large integers >==================< //
Command = serialInArray[1];
xValue = xMSB << 8 | xLSB; // Get xValue from yMSB & yLSB
yValue = yMSB << 8 | yLSB; // Get yValue from xMSB & xLSB
// println(Command+ " "+xValue+" "+ yValue+" " );
/*
How that works: if xMSB = 10001001 and xLSB = 0100 0011
xMSB << 8 = 10001001 00000000 (shift xMSB left by 8 bits)
xLSB = 01000011
xLSB | xMSB = 10001001 01000011 combine the 2 bytes using the logic or |
xValue = 10001001 01000011 now xValue is a 2 byte number 0 -> 65536
*/
/* ==================================================================
Command, xValue & yValue have now been recieved from the chip
================================================================== */
switch(Command) {
/* ==================================================================
Recieve array1 and array2 from chip, update oscilloscope
================================================================== */
case 1: // Data is added to dynamic arrays
DynamicArrayTime3=append( DynamicArrayTime3, (xValue) );
DynamicArray3=append( DynamicArray3, (yValue) );
break;
case 2: // An array of unknown size is about to be recieved, empty storage arrays
DynamicArrayTime3= new float[0];
DynamicArray3= new float[0];
break;
case 3: // Array has finnished being recieved, update arrays being drawn
Time3=DynamicArrayTime3;
Voltage3=DynamicArray3;
// println(Voltage3.length);
DataRecieved3=true;
break;
/* ==================================================================
Recieve array2 and array3 from chip
================================================================== */
case 4: // Data is added to dynamic arrays
DynamicArrayTime2=append( DynamicArrayTime2, xValue );
DynamicArray2=append( DynamicArray2, (yValue-16000.0)/32000.0*20.0 );
break;
case 5: // An array of unknown size is about to be recieved, empty storage arrays
DynamicArrayTime2= new float[0];
DynamicArray2= new float[0];
break;
case 6: // Array has finnished being recieved, update arrays being drawn
Time2=DynamicArrayTime2;
current=DynamicArray2;
DataRecieved2=true;
break;
/* ==================================================================
Recieve a value of calculated power consumption & add it to the
PowerArray.
================================================================== */
case 20:
PowerArray=append( PowerArray, yValue );
break;
case 21:
DynamicArrayTime=append( DynamicArrayTime, xValue );
DynamicArrayPower=append( DynamicArrayPower, yValue );
break;
}
}
}
redraw();
// }
}
Arduino* (Touche Advanced Sensor)
//****************************************************************************************
// Illutron take on Disney style capacitive touch sensor using only passives and Arduino
// Dzl 2012
//****************************************************************************************
// 10n
// PIN 9 --[10k]-+-----10mH---+--||-- OBJECT
// | |
// 3.3k |
// | V 1N4148 diode
// GND |
// |
//Analog 0 ---+------+--------+
// | |
// 100pf 1MOmhm
// | |
// GND GND
#define SET(x,y) (x |=(1<<y)) //-Bit set/clear macros
#define CLR(x,y) (x &= (~(1<<y))) // |
#define CHK(x,y) (x & (1<<y)) // |
#define TOG(x,y) (x^=(1<<y)) //-+
#define N 160 //How many frequencies
float results[N]; //-Filtered result buffer
float freq[N]; //-Filtered result buffer
int sizeOfArray = N;
void setup() {
TCCR1A=0b10000010; //-Set up frequency generator
TCCR1B=0b00011001; //-+
ICR1=110;
OCR1A=55;
pinMode(9,OUTPUT); //-Signal generator pin
pinMode(8,OUTPUT); //-Sync (test) pin
Serial.begin(115200);
for(int i=0;i<N;i++) //-Preset results
results[i]=0; //-+
}
void loop()
{
unsigned int d;
int counter = 0;
for(unsigned int d=0;d<N;d++) {
int v=analogRead(0); //-Read response signal
CLR(TCCR1B,0); //-Stop generator
TCNT1=0; //-Reload new frequency
ICR1=d; // |
OCR1A=d/2; //-+
SET(TCCR1B,0); //-Restart generator
results[d]=results[d]*0.5+(float)(v)*0.5; //Filter results
freq[d] = d;
}
PlottArray(1,freq,results);
TOG(PORTB,0); //-Toggle pin 8 after each sweep (good for scope)
}
byte yMSB=0, yLSB=0, xMSB=0, xLSB=0, zeroByte=128, Checksum=0;
void SendData(int Command, unsigned int yValue,unsigned int xValue){
/* >=================================================================<
y = 01010100 11010100 (x & y are 2 Byte integers)
yMSB yLSB send seperately -> reciever joins them
>=================================================================< */
yLSB=lowByte(yValue);
yMSB=highByte(yValue);
xLSB=lowByte(xValue);
xMSB=highByte(xValue);
/* >=================================================================<
Only the very first Byte may be a zero, this way allows the computer
to know that if a Byte recieved is a zero it must be the start byte.
If data bytes actually have a value of zero, They are given the value
one and the bit in the zeroByte that represents that Byte is made
high.
>=================================================================< */
zeroByte = 128; // 10000000
if(yLSB==0){ yLSB=1; zeroByte=zeroByte+1;} // Make bit 1 high
if(yMSB==0){ yMSB=1; zeroByte=zeroByte+2;} // make bit 2 high
if(xLSB==0){ xLSB=1; zeroByte=zeroByte+4;} // make bit 3 high
if(xMSB==0){ xMSB=1; zeroByte=zeroByte+8;} // make bit 4 high
/* >=================================================================<
Calculate the remainder of: sum of all the Bytes divided by 255
>=================================================================< */
Checksum = (Command + yMSB + yLSB + xMSB + xLSB + zeroByte)%255;
if( Checksum !=0 ){
int inByte = Serial.read();
Serial.write(byte(0)); // send start bit
Serial.write(byte(Command)); // command eg: Which Graph is this data for
Serial.write(byte(yMSB)); // Y value's most significant byte
Serial.write(byte(yLSB)); // Y value's least significant byte
Serial.write(byte(xMSB)); // X value's most significant byte
Serial.write(byte(xLSB)); // X value's least significant byte
Serial.write(byte(zeroByte)); // Which values have a zero value
Serial.write(byte(Checksum)); // Error Checking Byte
}
}
void PlottArray(unsigned int Cmd,float Array1[],float Array2[]){
SendData(Cmd+1, 1,1); // Tell PC an array is about to be sent
delay(1);
for(int x=0; x < sizeOfArray; x++){ // Send the arrays
SendData(Cmd, round(Array1[x]),round(Array2[x]));
//delay(1);
}
SendData(Cmd+2, 1,1); // Confirm arrrays have been sent
}
My final project was meant to be a very loosely based game on collecting water and watching a plant grow and having a real life replica of the plant mimicking this movement.
Initial screen:
The first stage:
The second stage:
Final screen:
The layout at the showcase:
After the user-testing I did in class and outside of it, I realized some fundamental problems with the game. Firstly, the motive of the game was not really clear to the people interacting with it until after they started the game; however, by that point it was pretty intuitive that they had to move the plant in real life to see the change on screen. Also, I felt as if labelling the project a ‘game’ implied that there was a way to win (getting a certain number of drops within a certain number of time); therefore, when there was no real indication if people won or loss, they kind of didn’t know what to do. This was an error on my part as I should’ve made a very clear winning/losing situation or just a win-win situation but with a more obvious ending. For this, I wanted a flower to grow to portray the final stage of growth but I didn’t know how I could implement this in the short amount of time I had considering I would have to change a lot of the code.
I think what did work was the implementation of handles on the pot, this was a clear indicator that people had to hold it from both sides. The arrows on the board it rested on, also helped. Moreover, once people did start, a lot of them appreciated the calmness they felt in doing the act and a lot go gasps of excitement were heard when the plant did grow on screen. A few people commented on the good color palette/’clean’ look of the whole experiment and I felt very proud of that because neatness is pretty important to me personally. Another viewer also commented how the movement of this game closely resembled activities that are used with patients that are exercising mobility of body parts and how this could extend to that application which I found very, very cool.
Personally, although a few aspects of the project did not end up looking like I had hoped they would, I have never felt more proud being present around people looking at my work and smiling/laughing because of it — it felt amazing. It especially felt great when Craig and Sarah told me I should be proud of myself and I was! — I worked on this code for 2 weeks straight (with help) but also did most of it by myself which I never thought I would be able to say about something that looked like this. I think that’s something that this course taught me, there’s a lot of stuff I CAN do, I just need to stop telling myself that I can’t. So overall, I know there’s a lot to be improved upon but I think I’m still pretty happy with the result. With special thanks to Aaron, Jack, Ali, Yousra, the UNIX lab and Daniel Shiffman.
Code:
Game
import processing.video.*;
import jp.nyatla.nyar4psg.*;
Capture cam;
MultiMarker nya;
Catcher catcher;
Timer timer;
Drop[] drops;
//End[] ends = new End[10];;
int totalDrops = 0;
int numCaught = 0;
PImage flowers;
PImage bFlowers;
PImage background;
PImage black;
//PImage end;
int level;
// 1: First plant stage
// 2: Second plant stage
int time;
String timeString = "00";
//String counter = "00";
int initialTime;
int interval = 1;
int totalTime = 60000;
float x=0;
int value = 32;
int intTimeString;
int gameScreen;
// 0: Initial Screen
// 1: Game Screen
// 2: Game-over Screen
void setup() {
fullScreen(P3D);
//size(640,480,P3D);
fill(0);
background = loadImage("background.jpg");
catcher = new Catcher(60); // Create the catcher with a radius of __
drops = new Drop[2500];
timer = new Timer(300);
flowers = loadImage("flowers.png");
bFlowers = loadImage("bFlowers.png");
//end = loadImage("end.png");
initialTime = millis();
//for (int = i; i < ends.length; i++) {
// ends[i] = new End(100+i*100, 300, random(32,72));
//}
cam=new Capture(this, 1440, 900);
nya=new MultiMarker(this, 1440, 900, "camera_para.dat", NyAR4PsgConfig.CONFIG_PSG);
nya.addNyIdMarker(0, 80);
cam.start();
}
void draw() {
background(0);
if (gameScreen == 0) {
initScreen();
} else if (gameScreen == 1) {
if (cam.available() !=true) {
}
gameScreen();
cam.read();
nya.detect(cam);
pushStyle();
imageMode(CORNER);
//nya.drawBackground(cam);
popStyle();
if ((!nya.isExist(0))) {
} else {
x += (nya.object2ScreenCoordSystem(0, 0, 0, 0).x-x)*.1;
//println(x);
}
catcher.setLocation(width-x);
catcher.display();
} else if (gameScreen == 2) {
gameOverScreen();
}
}
void initScreen() {
background(255);
image(bFlowers, width/2, height/3);
imageMode(CENTER);
fill(50, 100, 150);
textSize(18);
textAlign(CENTER);
text("MOVE THE PLANT SLOWLY TO COLLECT AS MANY WATER DROPS AS YOU CAN", width/2, height/2);
text("PRESS SPACEBAR TO START", width/2, height/1.7);
}
void keyPressed() {
if (value == 32) {
startGame();
time = 0;
}
}
void startGame() {
gameScreen=1;
}
void gameScreen() {
imageMode(CORNER);
image(background, 0, 0);
catcher.display();
if (timer.isFinished()) {
drops[totalDrops] = new Drop();
totalDrops ++ ;
if (totalDrops >= drops.length) {
totalDrops = 0;
}
timer.start();
}
for (int i = 0; i < totalDrops; i++ ) {
drops[i].move();
drops[i].display();
if (catcher.intersect(drops[i])) {
drops[i].caught();
numCaught++;
}
}
if (numCaught==15) {
level = 1;
} else if (numCaught==25) {
level = 2;
}
if (millis() - initialTime > interval)
{
time += 1;
timeString = nf(time, 2);
initialTime = millis();
}
intTimeString = parseInt(timeString)/60;
timeString = Integer.toString(intTimeString);
text("TIME: " + timeString, width/9, height/8);
textSize(22);
if ((time>=totalTime) || (numCaught>=30)) {
gameOverScreen();
}
}
void gameOverScreen() {
cam.stop();
background(255);
fill(50, 100, 150);
textAlign(CENTER);
textSize(22);
text("GAME OVER", width/2, height/2.3);
textSize(20);
text("PRESS THE SCREEN TO REPLAY", width/2, height/2);
imageMode(CORNER);
//or (int = i; i < ends.length; i++)
//ends[i].ascend();
//ends[i].display();
//ends[i].top();
}
void mousePressed() {
gameScreen = 1;
//loop();
Catcher class
class Catcher {
float r; // radius
color col; // color
float x, y; // location
int w = 200;
int h = 580 ;
Catcher(float tempR) {
r = tempR;
col = color(50, 10, 10, 150);
x = 0;
y = height - 200;
}
void setLocation(float tempX) {
x = tempX;
}
void display() {
stroke(0);
fill(col);
int startPoint = 200*level;
PImage flower = flowers.get(startPoint, - height/3, w, h);
image(flower, x, y);
imageMode(CENTER);
}
// A function that returns true or false based on if the catcher intersects a raindrop
boolean intersect(Drop d) {
// Calculate distance
float distance = dist(x, y + 120, d.x, d.y);
if (distance < r + d.r) {
return true;
} else {
return false;
}
}
}
Drops class
class Drop {
float x, y; // Variables for location of raindrop
float speed; // Speed of raindrop
color c;
float r; // Radius of raindrop
Drop() {
r = 8; // All raindrops are the same size
x = random(width); // Start with a random x location
y = -r*4; // Start a little above the window
speed = random(3, 6); // Pick a random speed
c = color(50, 100, 150); // Color
}
// Move the raindrop down
void move() {
// Increment by speed
y += speed;
}
// Check if it hits the bottom
boolean reachedBottom() {
// If we go a little beyond the bottom
if (y > height + r*4) {
return true;
} else {
return false;
}
}
// Display the raindrop
void display() {
// Display the drop
fill(c);
noStroke();
for (int i = 2; i < r; i++ ) {
ellipse(x, y + i*4, i*2, i*2);
}
}
// If the drop is caught
void caught() {
// Stop it from moving by setting speed equal to zero
speed = 0;
// Set the location to somewhere way off-screen
y = -1000;
}
}
Timer class
class Timer {
int savedTime; // When Timer started
int totalTime; // How long Timer should last
Timer(int tempTotalTime) {
totalTime = tempTotalTime;
}
// Starting the timer
void start() {
// When the timer starts it stores the current time in milliseconds.
savedTime = millis();
}
boolean isFinished() {
// Check how much time has passed
int passedTime = millis()- savedTime;
if (passedTime > totalTime) {
return true;
} else {
return false;
}
}
int getTime() {
return millis()/600;
}
}
A class of flowers I was considering ending at the end screen: