CONCEPTION AND DESIGN
This project started with my previous experience when playing video games, especially visual novels that rely solely on mouse-clicking for option selection and story progression (i.e. my favorite game Phoenix Wright: Ace Attorney Trilogy). What if the player can physically present the evidence just like the characters do in the game? Building upon my previous experience during the midterm project and my understanding of the thinking stage of interactivity explained by Crawford in The Art of Interactive Design, I decided to build an arcade machine-like project, using Arduino sensors and buttons as the input, and Processing pictures and music as the output. The player uses the button to progress and present the magnet-attached evidence to react to the characters’ dialogues and make objections at different stages of the game. During the user-testing session, due to the fact that I spent too much time coding and didn’t get to build a physical working prototype, most advice I got was on the practical functioning of the sketches. In addition to that, some users suggested adding more sound effects and making the opening section shorter for a better experience for the players. I made the changes accordingly and changed the Arduino input sensor design, which I will talk more about in the production part.
FABRICATION AND PRODUCTION
Considering that my project requires the user to present a specific item according to the story (i.e. presenting the correct evidence to advance the plot), the actuators I use must be able to detect what exactly the user is presenting, and the only thing that can achieve this correspondence is RFID (Radio Frequency Identification) tags and readers – at least that’s what I thought at that time. I bought the RFID kit online and made sure the reader and the tags all worked using the RC522 module. However, the problem arose when I tried to use serial communication to send the data read by the RFID reader to Processing as I couldn’t figure out what exactly was detected from the RFID tags and didn’t have enough time to figure it out. Later, I tried to switch to using joysticks to imitate the mouse-clicking and different sections of the screen to represent different evidence, but it looked way too much like the original mouse-clicking and the readings of the joystick weren’t stable enough, so I gave up on that idea. During the user-testing session, I was
inspired by Kevin’s feedback and decided to use reed switches and magnets to achieve the original effects, except this time different reed switches represent different evidence, and placing the evidence (magnet attached) next to the reed sensor close the circuit. The digital readings of the reed switches were stable and the serial communication worked out.
After making sure the key interaction component of my project worked, I moved on to the Processing coding and material fabrication. For the screen components, I (aggressively) rewrote the original storyline and made sure its originally 4-hour-long content could fit into the 10-minute game, and used character stand-ups and background scenes from the original game. I used arrays for images so that each time the user pushed a button the program showed one image, and added different soundtracks from the original game at different stages of the project using example codes from the class. I used 3D printing for the evidence models and used existing files
shared by other creators on Thingiverse for the more complex models (i.e. the attorney’s badge, the Thinker, and the glass shards). For the physical structure, I designed the arcade machine model blueprints and used MakerCase to generate the open box with grids that would be used to hold the evidence models.
CONCLUSIONS
In the end, my project basically achieved what I had hoped for: users who had already played the original game had fun with the new interactive experience of presenting physical evidence and smashing the button to object, and those who hadn’t played were able to engage with the different
phases of the game and become interested in the original game. However, some users proposed that the evidence given to the player at the start of the game could be obtained by players searching and investigating on their own, which was actually part of the gameplay of the original game (and got cut in my version due to the time-restriction). If given more time, I would work more on the RFID mechanism and figure out how the readers and tags work, and add another phase of the game with the user searching the crime scene (3D printed floor plans of the recreated crime scenes) to get crucial evidence.
This is my first time designing and building a complete project from scratch on my own, so I ran into a lot LOT of problems throughout the process. However, I also got a much clearer understanding of how serial communication works and how to use Processing to provide visual and auditory feedback to users. As a perfectionist with a low tolerance for frustration and OCD (which forced me to recount all 243 images for Processing three times in a row every time I adjusted the array until I drove myself crazy), I have learned how to remain calm when encountering a problem to troubleshoot and change tactics when necessary to find a more maneuverable direction, and I believe that these skills will play an indispensable role in the future.
Demonstration of partial gameplay
DISASSEMBLY
APPENDIX
Sketch for Arduino:
int buttonBlue = 2; int buttonWhite = 3; const int reed_1 = 5; const int reed_2 = 6; const int reed_3 = 7; const int reed_4 = 8; const int reed_5 = 9; const int reed_6 = 10; const int reed_7 = 11; const int reed_8 = 12; void setup() { Serial.begin(9600); pinMode(buttonBlue, INPUT); pinMode(buttonWhite, INPUT); } void loop() { int buttonState_1 = digitalRead(buttonBlue); int buttonState_2 = digitalRead(buttonWhite); int reedState_1 = digitalRead(reed_1); int reedState_2 = digitalRead(reed_2); int reedState_3 = digitalRead(reed_3); int reedState_4 = digitalRead(reed_4); int reedState_5 = digitalRead(reed_5); int reedState_6 = digitalRead(reed_6); int reedState_7 = digitalRead(reed_7); int reedState_8 = digitalRead(reed_8); Serial.print(buttonState_1); Serial.print(","); Serial.print(buttonState_2); Serial.print(","); Serial.print(reedState_1); Serial.print(","); Serial.print(reedState_2); Serial.print(","); Serial.print(reedState_3); Serial.print(","); Serial.print(reedState_4); Serial.print(","); Serial.print(reedState_5); Serial.print(","); Serial.print(reedState_6); Serial.print(","); Serial.print(reedState_7); Serial.print(","); Serial.print(reedState_8); Serial.println(); delay(2); }
Sketch for Processing:
import processing.serial.*; Serial serialPort; import processing.sound.*; SoundFile crime; SoundFile suspect; SoundFile court; SoundFile objection; SoundFile pursuit; SoundFile end; SoundFile crisis; SoundFile win; SoundFile phoenixObjection; SoundFile milesObjection; PImage title; PImage evidence; PImage[] opening = new PImage[48]; PImage[] start = new PImage[243]; int count; int preValue; int white; int preWhite; //choice determination boolean boolean wrongBadge; boolean correctBadge; boolean wrongA; boolean correctA; boolean wrongB; boolean correctB; boolean wrongC; boolean correctC; int NUM_OF_VALUES_FROM_ARDUINO = 10; int arduino_values[] = new int[NUM_OF_VALUES_FROM_ARDUINO]; void setup() { size(1200, 800); //screen displays title = loadImage("title.png"); evidence = loadImage("evidence.png"); for (int i = 0; i < opening.length; i++) { opening[i] = loadImage("opening"+i+".png"); } for (int i = 0; i < start.length; i++) { start[i] = loadImage("start"+i+".png"); } //sound files for background soundtracks crime = new SoundFile(this, "crime.wav"); suspect = new SoundFile(this, "suspect.wav"); court = new SoundFile(this, "court.wav"); objection = new SoundFile(this, "objection.wav"); pursuit = new SoundFile(this, "pursuit.wav"); end = new SoundFile(this, "end.wav"); crisis = new SoundFile(this, "crisis.wav"); win = new SoundFile(this, "win.wav"); phoenixObjection = new SoundFile(this, "phoenixObjection.mp3"); milesObjection = new SoundFile(this, "milesObjection.mp3"); //serial communication printArray(Serial.list()); serialPort = new Serial(this, "/dev/cu.usbmodem14201", 9600); } void draw() { background(0); getSerialData(); //push button to continue setting if (arduino_values[0] == 1 && preValue == 0) { count++; } if (arduino_values[1] == 1 && preWhite == 0) { white++; } //default page if (count == 0) { image(title, 0, 0); } //game start if (count > 0 && count < 9) { //opening scene if (crime.isPlaying() == false) { crime.loop(); image(opening[count-1], 0, 0); } else { image(opening[count-1], 0, 0); } } else if (count > 9 && count < 49) { //detention scene if (suspect.isPlaying() == false) { crime.stop(); suspect.loop(); image(opening[(count-1)], 0, 0); } else { image(opening[(count-1)], 0, 0); } } else if (count > 48 && count < 143) { //trial start if (court.isPlaying() == false) { suspect.stop(); court.loop(); image(start[(count-49)], 0, 0); } else { image(start[(count-49)], 0, 0); } } else if (count > 142 && count < 186) { //first stage if (objection.isPlaying() == false) { court.stop(); phoenixObjection.play(); objection.loop(); image(start[(count-49)], 0, 0); } else { image(start[(count-49)], 0, 0); } } else if (count > 185 && count < 203) { //second stage if (pursuit.isPlaying() == false) { objection.stop(); phoenixObjection.play(); pursuit.loop(); image(start[(count-49)], 0, 0); } else { image(start[(count-49)], 0, 0); } } else if (count > 202 && count < 228) { //third stage if (crisis.isPlaying() == false) { pursuit.stop(); milesObjection.play(); crisis.loop(); image(start[(count-49)], 0, 0); } else { image(start[(count-49)], 0, 0); } } else if (count > 227 && count < 258) { //fourth stage if (end.isPlaying() == false) { crisis.stop(); phoenixObjection.play(); end.loop(); image(start[(count-49)], 0, 0); } else { image(start[(count-49)], 0, 0); } } else if (count > 257) { //post trial if (win.isPlaying() == false) { end.stop(); win.loop(); image(start[(count-49)], 0, 0); } else { image(start[(count-49)], 0, 0); } } //badge presenting if (count == (opening.length + 7) && arduino_values[2] == 0) { wrongBadge = true; } else if (count == (opening.length + 7) && arduino_values[2] == 1) { correctBadge = true; } if (wrongBadge == true) { image(start[(count-49)], 0, 0); if (count == (opening.length + 10) && arduino_values [0] == 1 && preValue == 0) { count = count - 3; wrongBadge = false; } } if (correctBadge == true) { count = count + 4; image(start[(count-49)], 0, 0); correctBadge = false; } //A presenting if (count == (opening.length + 92) && arduino_values[6] == 0 && arduino_values[9] == 0) { wrongA = true; } else if (count == (opening.length + 92) && (arduino_values[6] == 0||arduino_values[9] == 0)) { correctA = true; } if (wrongA == true) { image(start[(count-49)], 0, 0); if (count == (opening.length + 94) && arduino_values [0] == 1 && preValue == 0) { count = count - 3; wrongA = false; } } if (correctA == true) { count = count + 3; image(start[(count-49)], 0, 0); correctA = false; } //B presenting if (count == (opening.length + 135) && arduino_values[4] == 0) { wrongB = true; } else if (count == (opening.length + 135) && arduino_values[4] == 1) { correctB = true; } if (wrongB == true) { image(start[(count-49)], 0, 0); if (count == (opening.length + 137) && arduino_values [0] == 1 && preValue == 0) { count = count - 3; wrongB = false; } } if (correctB == true) { count = count + 3; image(start[(count-49)], 0, 0); correctB = false; } //C presenting if (count == (opening.length + 176) && arduino_values[3] == 0) { wrongC = true; } else if (count == (opening.length + 176) && arduino_values[3] == 1) { correctC = true; } if (wrongC == true) { image(start[(count-49)], 0, 0); if (count == (opening.length + 179) && arduino_values [0] == 1 && preValue == 0) { count = count - 4; wrongC = false; } } if (correctC == true) { count = count + 4; image(start[(count-49)], 0, 0); correctC = false; } //evidence page if ((white % 2) == 1) { image(evidence, 0, 0); } preValue = arduino_values[0]; preWhite = arduino_values[1]; //reset game after finish if (count > 290) { resetGame(); } } void resetGame() { count = 0; white = 0; wrongBadge = false; correctBadge = false; wrongA = false; correctA = false; wrongB = false; correctB = false; wrongC = false; correctC = false; win.stop(); } // the helper function 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]); } } } } }