Super Princess
Jade Wen & Robbin Wu
Instructor: Andy Garcia
Super Princess is a game following the storyline of a princess running through woods, fighting monsters, and finally saving the prince. Players use controllers with slide potentiometer and tilt sensors as swords to play the game. The game implies a message to encourage female characters in heroine story and gameplay, with modified characters from Super Mario games, a game of a male plumer saving the princess, that can resonate with the general audience (“Super Mario Bros. (Video Game 1985)”).
Before setting our idea on this project, we planned to make games (a) with ultra sensors that examine physical positions to play a modified forest version of Minesweeper, and (b) with a storyline that leads to different outcomes according to every decision the player makes. But we expected that the former would deal with technical challenges and we want a project with deeper and more interesting interaction. Therefore, we combine the elements in our proposal–a storyline that sets in the woods, and involves body movement, in which players swing the “swords” to beat the monster.
Our project is composed of two sensors, which is a slide potentiometer and a tilt sensor. The reason why we choose these two sensors is because how they are used is straightforwardly corresponds to the gameplay experience. The potentiometer sliding from left to right controls the character’s horizontal movements, and the sensor counts on the time the sword is “waved” or tilted. On top of that, we spent most of the time designing visual graphics for the characters and coding. Robbin was responsible for the graphics design, and I managed most of the coding. We first build the game structure with simple shapes that represent the characters and elements.
And then we add “switch case” to distinguish different stages of the game. We encountered a problem: we couldn’t reset case 1 when returning from case 3. After discussing with the Professor, we added code to re-setup the character in case 3 to solve the problem.
Then we replaced all the shapes with videos and pictures that Robbin designed for the characters. But the problem was during the battle. We wanted the video to play from the top to the bottom every time the sensor value became 1, but the video stopped playing every time the value became 0, which meant that the player finished swinging the sword. The solution was using
battleRight2.jump(0);
code to the video to the beginning, and didn’t stop playing until the video reached the end. The sequence and the position of the codes are the most difficult part. We tried several times with different combinations to make it work.
Our goal is to make a game that engages a fun interactive user experience with movements and visuals, as well as to imply the message that is meaningful. Instead of having a traditional joystick game controller, we made it simple and straightforward, with tilt sensors that detect people’s arm waving. In this sense, it’s not a game on fingers anymore, but involves the player’s movements that correspond to the character’s in the game, making the interaction more interesting. The main character is the modification of the princess in Super Mario. Not wearing a long dress anymore, she wears armor, and has weapons to fight against all odds to save the prince. Turning around the story of a game that is common in many people’s childhood experiences, we aimed to shed light on the female characters in gameplay and hero stories. Although we have made lots of effort on visual design, we can improve more on it if we had time. Additionally, we can also design different levels for the game, adding richness to the gaming experience.
We did face a lot of challenges in our original proposal of project and coding. But we developed and converged our ideas to a final decision through discussion within the team and with the Professor. It’s a process that takes time and inspiration, but also interesting. We originally didn’t have any implication planned, but it was suggested by the Professor to have one. Even though the game itself is interesting, having a message in it makes it more meaningful, and it carries the social responsibility to make a difference in society. Overall, despite the improvements expected, I’m satisfied with the outcome and team experience of overcoming challenges though working apart from each other.
Prcocessing Code:
import processing.serial.*; import processing.video.*; PImage tree, monster; Movie running, battleRight1, battleRight2, battleLeft; int NUM_OF_VALUES_FROM_ARDUINO = 2; /** YOU MUST CHANGE THIS ACCORDING TO YOUR PROJECT **/ int sensorValues[]; /** this array stores values from Arduino **/ String myString = null; Serial myPort; int monsteramount=1; int amount = 3; float[] x1 = new float[amount]; float[] x2 = new float[amount]; float[] x3 = new float[amount]; float[] m1 = new float[monsteramount]; float[] m2 = new float[monsteramount]; float[] my = new float[monsteramount]; float[] y = new float[amount]; float[] size = new float[amount]; float[] speed = new float[amount]; boolean battleplay=true; int swing=26; int charactersize=200; int diameter; int situation; int pMs, ms, s, m, value, arcMs, arcS; void setup() { setupSerial(); //size (1000,1000); fullScreen(); for (int j=0; j<monsteramount; j++) { m1[j]= random(650, 690); m2[j]= random(1310, 1340); my[j]= random(-height*20, -height*19); } for (int i=0; i<amount; i++) { x1[i] = random(0, 650); x2[i] = random(690, 1310); x3[i]= random(1340, width); y[i] = random(-height*3, -height); size[i] = 100; speed[i] = 5; } tree = loadImage("tree.jpg"); monster = loadImage("monster.jpg"); running= new Movie(this, "running.mp4"); battleRight1= new Movie(this, "battleRight1.mp4"); battleRight2= new Movie(this, "battleRight2.mp4"); battleLeft= new Movie(this, "battleLeft.mp4"); running.loop(); battleRight1.loop(); battleLeft.loop(); battleRight2.play(); battleRight2.pause(); } void draw() { getSerialData(); printArray(sensorValues); background(#DFBD91); //for (int i=0; i<amount; i++) { switch(situation) { default: fill(0); rectMode(CENTER); rect(width/2, height/2, width/4, height/4); fill(255); textAlign(CENTER, CENTER); textSize(100); text("RUN", width/2, height/2); if (mousePressed) { if (mouseX>=width/2-width/8 && mouseX<=width/2+width/8 && mouseY>=height/2-height/8 && mouseY<=height/2+height/8) { situation='1'; } } break; case '1': case1(); break; case '2': case2(); break; case '3': case3(); break; case '4': case4(); break; } } void case1() { if (running.available()) { running.read(); } for (int j=0; j<monsteramount; j++) { //m1[j]= random(650, 690); //m2[j]= random(1310, 1340); //my[j]= random(-height*5,-height*3); for (int i=0; i<amount; i++) { imageMode(CENTER); noStroke(); fill(255, 120, 139); image(tree, x1[i], y[i], size[i], size[i]); image(tree, x2[i], y[i], size[i], size[i]); image(tree, x3[i], y[i], size[i], size[i]); y[i] += speed[i]; image(monster, m1[j], my[j], size[i]*1.5, size[i]*1.5); image(monster, m2[j], my[j], size[i]*1.5, size[i]*1.5); my[j] += speed[i]+5; if (y[i] > height+(size[i]/2)) { x1[i] = random(0, 650); x2[i] = random(690, 1310); x3[i]= random(1340, width); y[i] = random(0, -height); size[i] = 100; speed[i] = 5; } if (my[j]>height+size[i]) { m1[j]= random(650, 690); m2[j]= random(1310, 1340); my[j]= random(-height*10, -height*9); size[i] = 100; speed[i] = 5; } diameter=int(map(sensorValues[0], 0, 1023, 0, width)); fill(255, 0, 0); image(running, diameter, height-charactersize, charactersize, charactersize*1); if (dist(diameter, height-charactersize/2, x1[i], y[i])<=size[i]/2+charactersize/2) { situation='3'; } if (dist(diameter, height-charactersize/2, x2[i], y[i])<=size[i]/2+charactersize/2) { situation='3'; } if (dist(diameter, height-charactersize/2, x3[i], y[i])<=size[i]/2+charactersize/2) { situation='3'; } if (dist(diameter, height-charactersize/2, m1[j], my[j])<=size[i]*1.5/2+charactersize/2) { situation='2'; } if (dist(diameter, height-charactersize/2, m2[j], my[j])<=size[i]*1.5/2+charactersize/2) { situation='2'; } } } } void case2() { if (battleLeft.available()) { battleLeft.read(); } image(battleLeft, width/2-500, height/2, width/3, width/3); if (sensorValues[0]>=1000) { beginShape(POINTS); for (int a = 0; a < 360; a+=6) { float angle = radians(a); float x = width/2 + cos(angle) *70; float y = 200 + sin(angle) * 70; vertex(x, y); } ms = millis(); s = (ms-pMs)/1000; m = (ms-pMs)%1000; if ((millis()>=60000+pMs)) { textSize(32); textAlign(CENTER); text("time up ", width/2, 50); situation='3'; } else { fill(2); noStroke(); textSize(32); textAlign(RIGHT); text(59-s, width/2-20, 50); textAlign(LEFT); text(999-m, width/2, 50); textAlign(CENTER); text("SWING Your Sword", width/2, 450); noFill(); stroke(2); strokeWeight(5); float arcMs = map(999-m, 0, 1000, 0, TWO_PI) - HALF_PI; arc(width/2, 200, 150, 150, -HALF_PI, arcMs); float arcS = map(59-s, 0, 60, 0, TWO_PI) - HALF_PI; strokeWeight(2); line(width/2, 200, width/2 + cos(arcS) * 60, 200 + sin(arcS) * 60); } }else { pMs= millis(); fill(2); noStroke(); textSize(32); textAlign(RIGHT); text(60, width/2-20, 50); textAlign(LEFT); text("000", width/2, 50); textSize(22); textAlign(CENTER); text("Swipe the slider to the TOP to begin.", width/2, 370); noFill(); stroke(2); strokeWeight(5); arc(width/2, 200, 150, 150, -HALF_PI, -HALF_PI); strokeWeight(2); line(width/2, 200, width/2, 200 - 60); } float mt = battleRight2.time(); float md = battleRight2.duration(); imageMode(CENTER); if (battleRight1.available()) { battleRight1.read(); } if (battleRight2.available()) { battleRight2.read(); } if (battleLeft.available()) { battleLeft.read(); } image(battleLeft, width/2-500, height/2, width/3, width/3); image(battleRight1, width/2+500, height/2, width/3, width/3); image(battleRight2, width/2+500, height/2, width/3, width/3); if (sensorValues[1]==1) { if (mt >= md*0.95) { battleplay=true; } } textSize(50); text(swing, width-200, 200); if (battleplay==true) { swing-=1; battleplay=false; battleRight2.jump(0); battleRight2.play(); } if (swing==0) { situation='4'; } } void case3() { for (int j=0; j<monsteramount; j++) { for (int i=0; i<amount; i++) { x1[i] = random(0, 650); x2[i] = random(690, 1310); x3[i]= random(1340, width); y[i] = random(-height*10, -height*9); m1[j]= random(650, 690); m2[j]= random(1310, 1340); my[j]= random(-height*5, -height*3); size[i] = 100; speed[i] = 5; background(0); fill(255); rectMode(CENTER); rect(width/2, height/2+200, width/4, height/4); text("UH-OH, YOU DIDN'T MAKE IT", width/2, height/2-100); fill(0); textAlign(CENTER, CENTER); textSize(100); text("RESTART", width/2, height/2+200); if (mousePressed) { if (mouseX>=width/2-width/8 && mouseX<=width/2+width/8 && mouseY>=height/2-height/8+200 && mouseY<=height/2+height/8+200) { situation='1'; } } } } } void case4() { for (int j=0; j<monsteramount; j++) { for (int i=0; i<amount; i++) { x1[i] = random(0, 650); x2[i] = random(690, 1310); x3[i]= random(1340, width); y[i] = random(-height*10, -height*9); m1[j]= random(650, 690); m2[j]= random(1310, 1340); my[j]= random(-height*5, -height*3); size[i] = 100; speed[i] = 5; background(0); fill(255); rectMode(CENTER); rect(width/2, height/2+200, width/4, height/4); text("You Are My Heroine, Girl", width/2, height/2-100); fill(0); textAlign(CENTER, CENTER); textSize(100); text("RESTART", width/2, height/2+200); if (mousePressed) { if (mouseX>=width/2-width/8 && mouseX<=width/2+width/8 && mouseY>=height/2-height/8+200 && mouseY<=height/2+height/8+200) { situation='1'; } } } } } void setupSerial() { printArray(Serial.list()); myPort = new Serial(this, Serial.list()[ 0 ], 9600); // WARNING! // You will definitely get an error here. // Change the PORT_INDEX to 0 and try running it again. // And then, check the list of the ports, // find the port "/dev/cu.usbmodem----" or "/dev/tty.usbmodem----" // and replace PORT_INDEX above with the index number of the port. myPort.clear(); // Throw out the first reading, // in case we started reading in the middle of a string from the sender. myString = myPort.readStringUntil( 10 ); // 10 = '\n' Linefeed in ASCII myString = null; sensorValues = new int[NUM_OF_VALUES_FROM_ARDUINO]; } void getSerialData() { while (myPort.available() > 0) { myString = myPort.readStringUntil( 10 ); // 10 = '\n' Linefeed in ASCII if (myString != null) { String[] serialInArray = split(trim(myString), ","); if (serialInArray.length == NUM_OF_VALUES_FROM_ARDUINO) { for (int i=0; i<serialInArray.length; i++) { sensorValues[i] = int(serialInArray[i]); } } } } }
Arduino Code:
const int SENSOR_PIN = 6; int tiltVal; int prevTiltVal; int curls = 0; unsigned long lastDebounceTime = 0; // the last time the output pin was toggled unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers void setup() { Serial.begin(9600); pinMode(SENSOR_PIN, INPUT); // Set sensor pin as an INPUT pin } void loop() { // to send values to Processing assign the values you want to send //this is an example int sensor1 = analogRead(A0); int sensor2; //tiltsensor: int reading = digitalRead(SENSOR_PIN); // if the tilt sensor value changed, print the new value if (reading != prevTiltVal) { lastDebounceTime = millis(); //Serial.println(tiltVal); } if ( (reading != tiltVal) && (millis() - lastDebounceTime) > debounceDelay) { // whatever the reading is at, it's been there for longer than the debounce // delay, so take it as the actual current state: tiltVal = reading; if (reading == HIGH) { sensor2=1; }else{ sensor2=0; } } prevTiltVal = reading; // for Serial Plotter use //Serial.println(tiltVal); //delay(1); // send the values keeping this format Serial.print(sensor1); Serial.print(","); // put comma between sensor values Serial.print(sensor2); Serial.println(); // add linefeed after sending the last sensor value // too fast communication might cause some latency in Processing // this delay resolves the issue. delay(100); // end of example sending values }
Works Cited
The Timer during the Battle:
Leave a Reply