UP
Xiangyi Zhong & Yanran Deng, Gottfried Haider
2022/12/16
CONCEPTION AND DESIGN
For our final project, we want to build a game like Flappy Bird, but in a more interactive way and different meaning. We want users use the bike pump to control the height of the balloon house, while avoiding obstacles on the way to its destination and cleaning away the haze in the city.
FABRICATION AND PRODUCTION
After we had the general idea of the project we wanted to make, we decided to first build the obstacles and let them move from right side to the left, forming the effect that the house is flying forward. We used an array to build a series of “houses”(rectangles) first, and let the x coordinate reduce. The “houseType” was to give obstacles different colors.
The next step was to let the bike pump control the height of the house— to receive values sent from Arduino through the water flow sensor given by Gottfried, which also proved to work well with air flow later. After building the communication between Arduino and processing, we did a lot of tests to find the most suitable setting to map the value.
Having got the effect of controlling the height of the “house”, which was just a circle at that time, we began to make a “real house” on the screen. (The house was a PNG image.)
Then came the important part of judging the crash into obstacles. To do that, we were introduced to PGraphics, using two layers–one for the background image and the other for the house and obstacles. And then use “get” to tell whether the house is hitting the obstacles which were colored. Also, after hitting the obstacles, a prompt would show up on the screen, telling the user he failed and would have to click on the space to restart the game.
Since we wanted to set two scenes, or game level–one in the countryside with low hills, and one in the city with skyscrapers and haze, we made the first array triangles and added another one of rectangles two imitate the two scenes. We also added two different background images in the scenes. (But it was not until now that I realize we totally forgot to do the fading effect of the background images..)
Afterwards, we started working on the ending. Gottfried suggested to add some birds on the top to make it more fun. So we did the two part on the same day. Yanran worked on letting birds (also png images)appear in random height above the hills, plus the hitting judgment, while I did the transition to the ending scene and the drawing of the birds and the scene of the paradise fall(on the iPad borrowed from Ricci, thanx!)
The last essential part of the game was the haze. Since we already decided to use the lens duster, we needed another same sensor as the bike pump.(Thanks a lot Gottfried for the sensor!!) We were actually hesitating how to make the haze effect, the way Gottfried introduced to us before in office hours were impressive. But after consideration, we decided to make use of the transparent of the fullscreen white rectangle to imitate the haze–because it was supposed to be haze, or smog(caused by urbanization) rather than fog, in the city in our setting. So it goes this way: when the house enters the city, the transparency of the white rectangle is 0 at first and increases with time; every time the user squeeze the duster, the air flow value will be mapped into the reduction of the transparency of the rectangle. Once the transparency reaches 255, a text”LOST IN SMOG” will pop up.
By then, the main body of our game had almost been finished. We then had some problems in restarting the game with a simple click on the space. So we reached out to LAs, and learned to use different status to do different functions, which helped a lot in solving this issue.
In user test, we received a lot of helpful and creative suggestions:
We chose some most doable ones to polish our project:
The tutorial was rather valuable, since a lot of users mistakenly thought the birds were floating coins, and were confused about the function of the duster. But we had no idea about how to make videos. So I asked Ricci for help and learned to make a tutorial like this. Adding some background music (from the movie UP actually) made the game more complete.(not in the tutorial but in the processing code, to have the music while users playing )
The fabrication part bothered us for quite a long time. We just had difficulty in deciding what to make using ether laser cut or 3D printing. Rudi offered a suggestion of using a big pipe to hide every wire and electronics. I tried that but then we both found it doesn’t match our project style very well.
So after another discussion with Gottfried, we then made the decision to build a house, and a frame at the same time, to be put behind the computer, hiding the electronics. Later that day, we went to 823 to laser cut the cardboard, and glued the house. We also needed to solder longer wires and manage the wires in a better-organized way.
On Monday, we blew some balloons as decoration for the house, and borrowed some color sprays from ER. But the next morning in the very cold(Andy suggested us to do the spray outside of the building), we found none of the sprays could work, so we just compromised to use acrylic paint.
CONCLUSIONS:
Our project aims to bring people fun experience of playing a relaxing but also a bit tricky, and energy-consuming game. We also wanted to add some environmental protection elements into the game(the smog), but I don’t think we expressed that part well in the end.
To be honest, the presentation surprised me a lot, especially Rudi’s words. I find myself really enjoying the process of making this project step by step and watching it getting better.(except the part of struggling for an idea at first..) I think our project succeeded in letting users engage in the game in an interactive way as we expected.
There’s still a lot we can do to improve our game. For example, users might wonder: What happens after they reach their destination waterfall? Or consider increasing the difficulty of passing through the gap between obstacles over the course of the game to give the player a challenge and a desire to try again.
ANNEX
Here is our processing code:
import processing.serial.*; import osteele.processing.SerialRecord.*; import processing.video.*; import processing.sound.*; SoundFile file; Movie myMovie; PImage bg; PImage house; PGraphics pg; PImage bird; PImage bg2; PImage bg3; int t = 0; PFont f; Serial serialPort; SerialRecord serialRecord; float offX = 0.0; float avgValue1 = 0.0; float avgValue2 = 0.0; int status = 2; int[] obstaclesX = {380, 460, 650, 950, 1100, 1500, 1700, 1900, 2000, 2140, 2400, 2600, 2700}; int[] birdsX = {400, 680, 920, 1100, 1300, 1600, 1850, 2040, 2200, 2500, 2750, 2800, 3050}; int[] obstaclesX2 = {3300, 3380, 3500, 3600, 3800, 4000, 4300, 4600, 5000, 5100, 5200, 5400, 5700}; int[] houseType = { 1, 0, 2, 0, 2, 1, 0, 1, 2, 0, 1, 2, 0}; float[] houseHeight = new float[13]; float[] birdsHeight = new float[13]; int xPos; void setup() { fullScreen(); noCursor(); file = new SoundFile(this, "married_life.mp3"); file.loop(); myMovie = new Movie(this, "tutorial.mp4"); myMovie.loop(); for (int i=0; i < obstaclesX.length; i++) { //houseHeight[i] = random(150, 350); houseHeight[i] = random(150, 330); birdsHeight[i] = random(981 *3/4, 900); } String serialPortName = SerialUtils.findArduinoPort(); serialPort = new Serial(this, serialPortName, 9600); serialRecord = new SerialRecord(this, serialPort, 2); serialRecord.logToCanvas(false); bg = loadImage("test.jpg"); bird = loadImage("bird.png"); house = loadImage("house.png"); bg2 = loadImage("city.jpg"); bg3 = loadImage("ending.JPG"); pg = createGraphics(1511, 981); } void draw() { //println(status); if (status == 0 ) { play(); } if (status==1) { dead(); } if (status == 2) { tutorial(); } } void play() { background(255); serialRecord.read(); int value1 = serialRecord.values[0]; int value2 = serialRecord.values[1]; float y = map(avgValue1, 0, 16, 0, height); //println(offX); if (offX >= -3200) { tint(255, 105); image(bg, 0, 0, width, height); xPos=0; } if (offX <= -3200 && offX >= -5700) { tint(255, 255); image(bg2, 0, 0, width, height); xPos=0; fill(255, t); rect(0, 0, width, height); avgValue2 = avgValue2 * 0.95 + value2 * 0.05; float m = map(avgValue2, 0, 16, 0, 5); if ( m < 0.5) { t += 2; } else { t -= m; } if (t >= 255) { fill(0); fill(0); f = loadFont("Maku-Bold-30.vlw"); textFont(f, 100); textAlign(CENTER); textSize(80); text("LOST IN SMOG", width/2, height/2-200); } } if (offX < -5700) { xPos=xPos + 9; image(bg2, 0, 0, width, height); if ( xPos >= width) { tint(255, 255); image(bg3, 0, 0, width, height); f = loadFont("Maku-Bold-30.vlw"); textFont(f, 100); fill(#F07E7E); textAlign(CENTER); textSize(48); text("Please click the mouse to restart!", width/2, height/2); } } fill(#5F6064); tint(255, 255); image(house, xPos, height-y-258, 200, 256); pg.beginDraw(); pg.clear(); for (int i=0; i < obstaclesX.length; i++) { if (houseType [i] == 1) { pg.fill(#79F5ED, 200); } if ( houseType[i] == 2) { pg.fill(#2162FF, 200); } if (houseType [i] == 0) { pg.fill(#127174, 200); } pg.noStroke(); pg.triangle( offX + obstaclesX[i], height, offX + obstaclesX[i]+150, height-houseHeight[i], offX + obstaclesX[i]+300, height); } for (int k=0; k < birdsX.length; k++) { pg.noStroke(); pg.image(bird, offX + birdsX[k], height-birdsHeight[k], 64, 48); } for (int j=0; j < obstaclesX.length; j++) { if (houseType [j] == 1) { pg.fill(255); } if ( houseType[j] == 2) { pg.fill(245); } if (houseType [j] == 0) { pg.fill(240); } pg.rect(offX + obstaclesX2[j], height-1.8*houseHeight[j], 120, 1.8*houseHeight[j]); } pg.endDraw(); if (offX <-5) { if (y>120) { offX = offX - 9; } } else { offX = offX - 9; } serialRecord.read(); avgValue1 = avgValue1 * 0.95 + value1 * 0.05; image(pg, 0, 0); for ( int i=60; i <= 180; i++) { for ( int j = floor(height-y-2)-220; j <= floor(height-y-2); j++) { color picker = pg.get(i, j); if (picker != 0) { status =1; myMovie.stop(); } } } } void dead() { background(255); f = loadFont("Maku-Bold-60.vlw"); textFont(f, 100); textAlign(CENTER); textSize(80); fill(#F07E7E); text("HIT!Please click the mouse to restart!", width/2, height/2); } void mousePressed() { myMovie.loop(); status = 2; offX=0; } void tutorial() { if (myMovie.available()) { myMovie.read(); } image(myMovie, 0, 0, width, height); if (keyPressed) { status = 0; } }
Arduino code
#include "SerialRecord.h"
SerialRecord writer(2);
volatile int pulses_pump;
volatile int pulses_fog;
void setup() {
Serial.begin(9600);
pinMode(2, INPUT);
pinMode(3, INPUT);
attachInterrupt(digitalPinToInterrupt(2), count_pulses_pump, RISING);
attachInterrupt(digitalPinToInterrupt(3), count_pulses_fog, RISING);
}
void loop() {
pulses_pump = 0;
pulses_fog = 0;
interrupts();
delay(200);
noInterrupts();
writer[0] = pulses_pump;
writer[1] = pulses_fog;
writer.send();
delay(20);
}
void count_pulses_pump() {
pulses_pump = pulses_pump + 1;
}
void count_pulses_fog() {
pulses_fog = pulses_fog + 1;
}
(I didn’t find a icon of the sensor we used so I just use the potentiometer here…
(These two videos shows how to play the game. We made this picture-in-picture to show the interaction process more clearly. )
Citation:
Up. Directed by Pete Docter, Pixar Animation Studios, 2009.
Giacchino, Michael. “Married Life”. 2009