Project Title: Punch Your Stress Out
Designer: Haotong Wu & Alyssa Tong
Instructor: Margaret Minsky
Conception and Design
At the beginning, our project is called “Punch Art”, the installation is a unique combination of boxing and painting, designed to stimulate creativity, physical activity, and provide a novel approach to art and sport. The primary goal of this project is to showcase how boxing can be a tool for creative expression while engaging participants in physical activity.
We first finished the buttons and achieved the drawing effect in the scene. During the User Testing Session, we found that it was very difficult for users to move around by just hitting the buttons, but hitting the buttons we designed felt very good. After some user suggestions, we decided to change the objective of our project, and we decided to make a punch your stress device based on the huge pressure from everyone during the final week. By hitting the button, users can release their stress. At the same time, the effect in the screen is similar to whacking the fruit to release the stress.
Since a single fruit scene would be very boring, we added different scenes for this project, and users can choose their favorite fruit to punch.
For the sensor selection, we used common buttons. We tested different sensors, but in the end, only the common buttons worked best and were stable.
So in the end, we named our project “Punch your stress out” and the theme was a stress relief game.
Fabrication and Production
The first part we created was the fabrication part, which was much harder than we thought at first. The first problem we encountered was how to make a stable button that could withstand a punch, and Professor Andy provided us with a huge wooden board button. The button is made of two wooden boards, with copper tape on both sides of the inside, so when we hit the board, the inner copper tape will make contact and achieve a pathway.
We started by using laser cutting to create the shape our button. Then we created our initial button according to the plan provided by Professor Andy.
Then we used foam adhesive to stick the button on both sides. In the first test, we found it difficult to control the height between the two pieces. The location of the hit will also have an effect on the effectiveness of this button. After we made four buttons to control the direction, we found that they were very unstable. So we gave up this one option.
Then, we started to think about the second option. Is it possible to use a simple button to implement it. In our toolbox, we used the kind of three-in-one button, which means that we do not need to connect additional resistors to protect. We made our first test button and it was much more stable, so we immediately placed an order for many of these buttons and decided to use them to complete our project.
To improve the user’s hitting experience, Alyssa designed the hitting surface of the button. She used cotton and cloth to fill our hitting surface. And she cut out slots in the back panel for the buttons. This makes the buttons very strong and stable.
We batch produced the remaining 4 buttons and tested them. We are very happy with the results of this version compared to the original design.
Then, we encountered a second problem. How to place and arrange these buttons. At first, our ideal effect was a vertical board, so we initially wanted to fix the buttons on one wall. But this was difficult to achieve in the display. So we had to look into other options.
Alyssa came up with the method of fixing it with thick wooden boards. She completed the structure of our project with the help of fellow Kevin. This way, our main fabrication is done.
Then we started to work on the writing of the scenario code.
In the initial version of the demo, we used simple brushes and a single pattern to move around. And a sliding rheostat was used to control the colors. But as the pattern on the painting increases, the color can only change overall. And this version is designed to achieve the effect of button-controlled movement. It was successfully implemented.
Alyssa designed the initial version of the brushes and covers for the replacement patterns.
After the user test, we started to formally design our scenario and design our final demo. We first discussed the scenarios in our game and determined a basic flow structure.
In our project, the initial solution for switching scenes was to use buttons, but since our main section is also made up of buttons, this would make the interaction too homogeneous. So I used the distance sensor to control the scene switching. The effect of turning the page is achieved by waving the user’s hand towards the sensor.
I encountered some difficulties in this sensor setup. It always jumps to some values automatically when I’m not close enough, causing this page turning function to be very unstable.
const int Trig = 13; const int Echo = 7; int distance,time; void setup() { // put your setup code here, to run once: Serial.begin(9600); pinMode(Trig, OUTPUT); pinMode(Echo, INPUT); }
digitalWrite(Trig, LOW); delayMicroseconds(2); digitalWrite(Trig, HIGH); delayMicroseconds(10); digitalWrite(Trig, LOW); time = pulseIn(Echo, HIGH); distance = time / 58-2; if (distance > 15){ button = 0; //Serial.print("11111"); //Serial.print(distance); } else if (distance <= 15 && distance > 0){ button = 1; }
With the help of Professor Andy, we tested which values belonged to the outliers, we avoided the range, and the sensor worked properly. It can pass a value of 1 or 0 from the Arduino and still essentially functions as a button.
In Processing, I started to build the structure of the whole game. First, I wrote a state machine program to switch between different scenarios according to the process I designed at the beginning.
void draw() { getSerialData(); if (currentScene == 0) { scene0(); } else if (currentScene == 1) { chooseBGS(); } else if (currentScene == 2) { gameScene(bgidx); } else if (currentScene == 3) { closescene(); } for (int i=0; i < NUM_OF_VALUES_FROM_ARDUINO; i++) { prev_arduino_values[i] = arduino_values[i]; } }
Alyssa helped me with the functions for scene 0 and the ending scene.
void scene0() { getSerialData(); int button = arduino_values[6]; background(images[0]); //image(images[0], 0, 0, 1366, 1024); if (button != p_b && button ==1) { currentScene = 1; delay(500); //lastTriggered = millis(); } p_b = button; //if (lastTriggered != 0 && millis()-lastTriggered > 1000) { // currentScene = 1; //} }
void closescene() { getSerialData(); int startv = arduino_values[6]; //image(images[3], 0, 0, 1366, 1024); background(images[11]); if (startv == 1 && p_b == 0) { currentScene = 0; //p_b = 0; delay(300); } p_b = startv; }
So, I focused on Scene 1 and Scene 2.
Scene 1 is the choice of the game theme, that is, the choice of fruit. I count by the left and right buttons, and the state of the button is compared with its previous recorded state to prevent a rapid increase. And, when the value reaches the boundaryary, it will automatically become the extreme value on the other side, achieving a circular effect.
void chooseBGS() { getSerialData(); int left6 = arduino_values[3]; int right7 = arduino_values[4]; int startv = arduino_values[6]; background(images[1]); textSize(64); text(bgidx, 900, 330); fill(255, 0, 0); if (startv != 1) { if (arduino_values[3] == 1 && prev_arduino_values[3] == 0) { } if (left6 == 1 && pleft6 == 0) { bgidx = bgidx - 1; } if (right7 == 1 && pright7 == 0) { bgidx = bgidx + 1; } if (bgidx >3) { bgidx = 1; } if (bgidx <1) { bgidx = 3; } pleft6 = left6; pright7 = right7; } else if (startv == 1 && p_b == 0) { currentScene = 2; delay(500); } p_b = startv; }
Scene 2 is the scene of the game content, I go through the index I got in scene 1 to control the different backgrounds and brushes and patterns, they have the same function, just the mapping is different. The data structure of the images is array, so I just need to arrange them in order, then I can implement a function to finish different scenes.
Next, I kept the brush movement from my initial demo. And solved the problem of out of bounds. Whenever the coordinates of the brush exceed the size of the canvas, it will automatically become the value of the canvas limit size. Also, I increased the step length of each move based on user testing suggestions, making the whole game smoother and easier.
For each hit that left a pattern, I gave up the idea of changing the color and instead changed their size. The pattern of each hit will be stored in the corresponding array according to the coordinates and size. I use a for loop to iterate over the data in the array, which is used to save the contents of each round of drawing in the current screen. When the screen switches to the next scene, the stored index will return to 0. Then when the user plays the next game, the data will automatically overwrite the previous one, achieving the effect of not restarting the program.
void gameScene(int bgidx) { getSerialData(); int color1 = arduino_values[0]; int up4 = arduino_values[1]; int down5 = arduino_values[2]; int left6 = arduino_values[3]; int right7 = arduino_values[4]; int center8 = arduino_values[5]; int startv = arduino_values[6]; size1 = map(color1, 0, 1023, 50, 250); //color c1 = color(255, 0, 0); // 红色 //color c2 = color(0, 0, 255); // 紫色 background(images[bgidx+1]); penbrush(x, y, bgidx, size1+10); println(cidx); if (center8 == 1) { if (cidx < xs.length) { xs[cidx] = x; ys[cidx] = y; sizes[cidx] = size1; cidx++; } } for (int i = 0; i < cidx; i++) { pattern(xs[i], ys[i], sizes[i], bgidx); } if (x < 0){ x = 0; } if (x > 1360){ x = 1360; } if (y<0){ y = 0; } if (y> 850){ y= 850; } if (up4 == 1) { y -= 15; } if (down5 == 1) { y += 15; } if (left6 == 1) { x -= 15; } if (right7 == 1) { x += 15; } if (startv == 1 && p_b ==0) { currentScene = 3; cidx = 0; delay(500); } p_b = startv; }
Eventually, I wrote the functions for brushes and patterns. Thus, the whole program is finished.
void penbrush(int cx, int cy, int idx, float size1) { imageMode(CENTER); image(images[idx+4], cx, cy, size1, size1); } void pattern(float cx, float cy, float size1, int idx) { imageMode(CENTER); image(images[idx+7], cx, cy, size1+30, size1+30); }
Alyssa designed all the canvases and patterns for our project. The images were all original to us and took a lot of time.
Finally, Alyssa decorated our installation and it turned out very nice. For the final fixing solution, we turned to Professor Andy, who provided us with four large bricks and a woodworking clamp so that our installation could withstand a lot of punches without shifting.
Conclusion
Our project succeeded in achieving the functionality we wanted at the end. It’s smooth to switch between different scene and very interesting during the main decompression. As for the device, the blow feels very good, the user does not feel pain, and the device can withstand a lot of force. So I think it is very successful.
At the final IMA show, there were many people interested in our installation. Some kids lined up to play, the kids loved the simple operation and the percussion type of game, which made us very happy.
During the production of the project, we also learned some lessons, such as it is better to produce a product first and test it successfully before producing the rest. This way you won’t have to recreate everything because the program failed.
In coding, it is very useful to split a large task into many smaller tasks. This makes the process of fixing bugs much easier, and you only need to focus on one small aspect. At the same time, confirming the flow and structure of a good project can greatly improve efficiency and make ideas and cooperation clearer.
If there is still enough time, we may design more games to enrich the functions of our device. It can be a kind of punch controller that can be used to control many games.
Appendix
Code:
Arduino
int x = 0; int y = 0; int c = 0; int button = 0; const int Trig = 13; const int Echo = 7; int distance,time; void setup() { // put your setup code here, to run once: Serial.begin(9600); pinMode(Trig, OUTPUT); pinMode(Echo, INPUT); } void loop() { // put your main code here, to run repeatedly: int sensor0 = analogRead(A0); //滑动变阻器 int up = digitalRead(8); int down = digitalRead(9); int left = digitalRead(10); int right = digitalRead(11); int center = digitalRead(12); digitalWrite(Trig, LOW); delayMicroseconds(2); digitalWrite(Trig, HIGH); delayMicroseconds(10); digitalWrite(Trig, LOW); time = pulseIn(Echo, HIGH); distance = time / 58-2; if (distance > 15){ button = 0; //Serial.print("11111"); //Serial.print(distance); } else if (distance <= 15 && distance > 0){ button = 1; } //Serial.println(distance); Serial.print(sensor0); Serial.print(","); // put comma between sensor values Serial.print(up); Serial.print(","); // add linefeed after sending the last sensor value Serial.print(down); Serial.print(","); // put comma between sensor values Serial.print(left); Serial.print(","); // add linefeed after sending the last sensor value Serial.print(right); Serial.print(","); // put comma between sensor values Serial.print(center); Serial.print(","); // add linefeed after sending the last sensor value Serial.print(button); Serial.println(); delay(30); }
Processing 4
import processing.serial.*; Serial serialPort; import processing.video.*; import processing.sound.*; SoundFile sound; int NUM_OF_VALUES_FROM_ARDUINO = 7; int arduino_values[] = new int[NUM_OF_VALUES_FROM_ARDUINO]; int prev_arduino_values[] = new int[NUM_OF_VALUES_FROM_ARDUINO]; int x=600; int y=300; float size1 = 0; float[] xs = new float[10000]; float[] ys = new float[10000]; float[] sizes = new float[10000]; int cidx = 0; int currentScene = 0; PImage[] images = new PImage[12]; int currentIndex = 0; float imageSize = 0; int bgidx = 1; int p_S = 0; void setup() { size(1366, 850); printArray(Serial.list()); sound = new SoundFile(this, "song.mp3"); sound.loop(); images[0] = loadImage("mainpage.png"); images[1] = loadImage("p1.jpeg");//zhuomian\ images[2] = loadImage("p2.png");//lanmeizhuomian images[3] = loadImage("p3.png");//juzizhuomian images[4] = loadImage("p4.png");// caomeizhuomian images[5] = loadImage("p5.png");// lanmei bishua images[6] = loadImage("p6.png");//juzi bishua images[7] = loadImage("p7.png");// caomei bishua images[8] = loadImage("pa1.png"); //lanmei pattern images[9] = loadImage("pa2.png"); //juzi pattern images[10] = loadImage("pa3.png"); // caomei patterm images[11] = loadImage("last.png"); //jiewei for (int i = 0; i < 12; i++) { images[i].resize(1366, 850); } serialPort = new Serial(this, "/dev/cu.usbmodem11101", 9600); } void draw() { getSerialData(); if (currentScene == 0) { scene0(); } else if (currentScene == 1) { chooseBGS(); } else if (currentScene == 2) { gameScene(bgidx); } else if (currentScene == 3) { closescene(); } for (int i=0; i < NUM_OF_VALUES_FROM_ARDUINO; i++) { prev_arduino_values[i] = arduino_values[i]; } } int lastTriggered = 0; int p_b = 0; void scene0() { getSerialData(); int button = arduino_values[6]; background(images[0]); //image(images[0], 0, 0, 1366, 1024); if (button != p_b && button ==1) { currentScene = 1; delay(500); //lastTriggered = millis(); } p_b = button; //if (lastTriggered != 0 && millis()-lastTriggered > 1000) { // currentScene = 1; //} } int pleft6 = 0; int pright7 = 0; void chooseBGS() { getSerialData(); int left6 = arduino_values[3]; int right7 = arduino_values[4]; int startv = arduino_values[6]; background(images[1]); textSize(64); text(bgidx, 900, 330); fill(255, 0, 0); if (startv != 1) { if (arduino_values[3] == 1 && prev_arduino_values[3] == 0) { } if (left6 == 1 && pleft6 == 0) { bgidx = bgidx - 1; } if (right7 == 1 && pright7 == 0) { bgidx = bgidx + 1; } if (bgidx >3) { bgidx = 1; } if (bgidx <1) { bgidx = 3; } pleft6 = left6; pright7 = right7; } else if (startv == 1 && p_b == 0) { currentScene = 2; delay(500); } p_b = startv; } void gameScene(int bgidx) { getSerialData(); int color1 = arduino_values[0]; int up4 = arduino_values[1]; int down5 = arduino_values[2]; int left6 = arduino_values[3]; int right7 = arduino_values[4]; int center8 = arduino_values[5]; int startv = arduino_values[6]; size1 = map(color1, 0, 1023, 50, 250); //color c1 = color(255, 0, 0); // 红色 //color c2 = color(0, 0, 255); // 紫色 background(images[bgidx+1]); penbrush(x, y, bgidx, size1+10); println(cidx); if (center8 == 1) { if (cidx < xs.length) { xs[cidx] = x; ys[cidx] = y; sizes[cidx] = size1; cidx++; } } for (int i = 0; i < cidx; i++) { pattern(xs[i], ys[i], sizes[i], bgidx); } if (x < 0){ x = 0; } if (x > 1360){ x = 1360; } if (y<0){ y = 0; } if (y> 850){ y= 850; } if (up4 == 1) { y -= 15; } if (down5 == 1) { y += 15; } if (left6 == 1) { x -= 15; } if (right7 == 1) { x += 15; } if (startv == 1 && p_b ==0) { currentScene = 3; /* for (int i = 0; i < cidx; i++) { sizes[cidx]=0; xs[i]=-100; ys[i]=-100; } */ cidx = 0; delay(500); } p_b = startv; } void closescene() { getSerialData(); int startv = arduino_values[6]; //image(images[3], 0, 0, 1366, 1024); background(images[11]); if (startv == 1 && p_b == 0) { currentScene = 0; //p_b = 0; delay(300); } p_b = startv; } void penbrush(int cx, int cy, int idx, float size1) { imageMode(CENTER); image(images[idx+4], cx, cy, size1, size1); } void pattern(float cx, float cy, float size1, int idx) { imageMode(CENTER); image(images[idx+7], cx, cy, size1+30, size1+30); } void getSerialData() { while (serialPort.available() > 0) { String in = serialPort.readStringUntil( 10 ); // 10 = '\n' Linefeed in ASCII if (in != null) { print("From Arduino: " + in); String[] serialInArray = split(trim(in), ","); if (serialInArray.length == NUM_OF_VALUES_FROM_ARDUINO) { for (int i=0; i<serialInArray.length; i++) { arduino_values[i] = int(serialInArray[i]); } } } } }