So, here goes a shot at the description for Adham and I’s last project!
Firstly, the project did end up looking like the original idea. There was a controller which activated physical things on a board to move, which in turn caused the main character to move. The visuals even turned out cooler than I thought, as originally I wanted to paint the board, but Adham thought that projection mapping objects onto the wooden pieces on the board would look cooler. Which it did.
The project consisted of a 46in x 36in wooden board with 2 stepper motors, one servo, and 2 dc motors mounted onto it. Along with various wooden platforms for the character to walk on. In addition to this, there was a physical controller with 5 buttons which controlled each of these motors, and activated the animations for the board. The end goal is to get from the starting point, to the door in the bottom left-hand screen, and this is only possible if the user presses the buttons in the right sequence. Pressing the buttons will always cause the physical component on the board to move, but it only triggers the animation when they are pressed with correlation to where the heroine is on the board.
Here are some videos of people using our project:
I am quite proud of the code, as the animation took around 450 lines to write, making it the most code I have ever written for a project. Here is the code:
//compatible with madmapper import codeanticode.syphon.*; import processing.serial.*; Serial port; SyphonServer server; //right walk images PImage[] walkRight = new PImage[10]; PImage img0; PImage img1; PImage img2; PImage img3; PImage img4; PImage img5; PImage img6; PImage img7; PImage img8; PImage img9; //left walk images PImage[] walkLeft = new PImage[10]; PImage img_0; PImage img_1; PImage img_2; PImage img_3; PImage img_4; PImage img_5; PImage img_6; PImage img_7; PImage img_8; PImage img_9; //fire animation PImage[] fireAni = new PImage[32]; PImage f0; PImage f1; PImage f2; PImage f3; PImage f4; PImage f5; PImage f6; PImage f7; PImage f8; PImage f9; PImage f10; PImage f11; PImage f12; PImage f13; PImage f14; PImage f15; PImage f16; PImage f17; PImage f18; PImage f19; PImage f20; PImage f21; PImage f22; PImage f23; PImage f24; PImage f25; PImage f26; PImage f27; PImage f28; PImage f29; PImage f30; PImage f31; //background designs PImage plat; PImage wall; PImage ground; PImage door; PImage win; //triggers boolean standing = true; boolean triggerWaterMill = false; boolean triggerTrapDoor = false; boolean triggerLift = false; boolean triggerSpike1 = false; boolean triggerSpike2 = false; //count numbers int animIndex = 0; int animIndexFire = 0; int animIndexFire0 = 0; //coordinates of figure int locX=80; int locY=60; int[] permissions = {0, 0, 0, 0, 0}; int timer = 120; void setup() { size (920, 720, P3D); frameRate(10); server = new SyphonServer(this, "Processing Syphon"); printArray(Serial.list()); String portName = Serial.list()[5]; port = new Serial(this, portName, 9600); //load right walk images walkRight[0] = loadImage("figure0.png"); walkRight[1] = loadImage("figure1.png"); walkRight[2] = loadImage("figure2.png"); walkRight[3] = loadImage("figure3.png"); walkRight[4] = loadImage("figure4.png"); walkRight[5] = loadImage("figure5.png"); walkRight[6] = loadImage("figure6.png"); walkRight[7] = loadImage("figure7.png"); walkRight[8] = loadImage("figure8.png"); walkRight[9] = loadImage("figure9.png"); //load left walk images walkLeft[0] = loadImage("figure_0.png"); walkLeft[1] = loadImage("figure_1.png"); walkLeft[2] = loadImage("figure_2.png"); walkLeft[3] = loadImage("figure_3.png"); walkLeft[4] = loadImage("figure_4.png"); walkLeft[5] = loadImage("figure_5.png"); walkLeft[6] = loadImage("figure_6.png"); walkLeft[7] = loadImage("figure_7.png"); walkLeft[8] = loadImage("figure_8.png"); walkLeft[9] = loadImage("figure_9.png"); //load fire images fireAni[0] = loadImage("fire0.png"); fireAni[1] = loadImage("fire1.png"); fireAni[2] = loadImage("fire2.png"); fireAni[3] = loadImage("fire3.png"); fireAni[4] = loadImage("fire4.png"); fireAni[5] = loadImage("fire5.png"); fireAni[6] = loadImage("fire6.png"); fireAni[7] = loadImage("fire7.png"); fireAni[8] = loadImage("fire8.png"); fireAni[9] = loadImage("fire9.png"); fireAni[10] = loadImage("fire10.png"); fireAni[11] = loadImage("fire11.png"); fireAni[12] = loadImage("fire12.png"); fireAni[13] = loadImage("fire13.png"); fireAni[14] = loadImage("fire14.png"); fireAni[15] = loadImage("fire15.png"); fireAni[16] = loadImage("fire16.png"); fireAni[17] = loadImage("fire17.png"); fireAni[18] = loadImage("fire18.png"); fireAni[19] = loadImage("fire19.png"); fireAni[20] = loadImage("fire20.png"); fireAni[21] = loadImage("fire21.png"); fireAni[22] = loadImage("fire22.png"); fireAni[23] = loadImage("fire23.png"); fireAni[24] = loadImage("fire24.png"); fireAni[25] = loadImage("fire25.png"); fireAni[26] = loadImage("fire26.png"); fireAni[27] = loadImage("fire27.png"); fireAni[28] = loadImage("fire28.png"); fireAni[29] = loadImage("fire29.png"); fireAni[30] = loadImage("fire30.png"); fireAni[31] = loadImage("fire31.png"); //load background stuff plat = loadImage("platform.png"); wall = loadImage("block.jpg"); ground = loadImage("wall2.jpg"); door =loadImage("door.png"); win =loadImage("win.png"); } void draw() { //resets the background to white each frame background(255); if (millis()%1000 == 0){ timer--; } text(str(timer),10,10); if (standing && !triggerWaterMill) { //loads the new image from the array PImage frameStand = walkRight[0]; //draws this new image from the center at coordinate x,y imageMode(CENTER); image(frameStand, 80, 60, 40, 80); } //platform 1 imageMode(CENTER); image(plat, 78, 115, 61, 28); //platform 2 image(plat, 345, 175, 70, 28); //platform 3 image(plat, 500, 209, 190, 28); //wall 1 image(wall, 647, 112, 11, 182); //platform 4 image(plat, 608, 414, 212, 28); //platform5 image(plat, 763, 317, 70, 28); //platform6 image(plat, 763, 510, 70, 28); //platform7 image(plat, 551, 551, 63, 28); //platform 8 image(plat, 438, 414, 178, 28); // //platform9 image(plat, 79, 608, 157, 28); //door image(door, 71, 540, 90, 110); pushMatrix(); translate(282, 575); //rotate(cos(angle)) rotate(-PI/14); image(plat, 0, 0, 216, 30); popMatrix(); //loads the new image from the array drawing FIRE AAYYYYY PImage frame0 = fireAni[animIndexFire]; PImage frame1 = fireAni[animIndexFire0]; //draws this new image from the center at coordinate x,y FIRE!!!! imageMode(CENTER); //ground 1 image(ground, 75, 675, 146, 90); //fire 1 image(frame1, 174, 655, 98, 200); //ground 2 image(ground, 287, 675, 166, 140); //fire 2 image(frame0, 394, 655, 100, 200); //fire 3 image(frame1, 447, 655, 100, 200); //fire4 image(frame0, 495, 655, 100, 200); //fire5 image(frame1, 610, 655, 100, 200); //fire 6 image(frame0, 660, 655, 100, 200); //fire 7 image(frame1, 714, 655, 100, 200); //fire 8 image(frame0, 820, 655, 100, 200); //fire 9 image(frame1, 868, 655, 100, 200); //ground 3 image(ground, 555, 675, 60, 200); //ground 4 image(ground, 765, 705, 60, 200); image(ground, 765, 630, 60, 200); //ground 5 image(ground, 900, 705, 60, 200); image(ground, 900, 650, 60, 200); //loops the animation animIndexFire = (animIndexFire + 1) % fireAni.length; animIndexFire0 = (animIndexFire0 + 2) % fireAni.length; noFill(); //draw the ladder strokeWeight(6); line(730, 100, 792, 100); line(730, 130, 792, 130); line(730, 160, 792, 160); line(730, 190, 792, 190); line(730, 220, 792, 220); //trampoline fill(255, 0, 0); strokeWeight(3); ellipse(605, 350, 50, 20); line(600, 360, 625, 370); line(625, 370, 590, 375); line(590, 375, 620, 385); strokeWeight(6); line(580, 385, 635, 385); if (permissions[0]>0 && !triggerWaterMill) { triggerWaterMill = true; standing = false; } if (permissions[1]>0) { if (triggerWaterMill&&!triggerTrapDoor) { triggerTrapDoor = true; standing = false; } } if (permissions[4]>0) { if (triggerTrapDoor&&!triggerLift) { triggerLift = true; standing = false; } } if (permissions[3]>0){ if (triggerLift&&!triggerSpike1 ) { triggerSpike1 = true; standing = false; } } if (permissions[2]>0){ if (triggerSpike1&&!triggerSpike2) { triggerSpike2 = true; standing = false; } } //if watermill //----------------------------FIRSTTRIGGER_WATERMILL--------------------------------- if (triggerWaterMill&&!triggerTrapDoor) { if (locX <= 118 && locY== 60) { PImage frame = walkRight[animIndex]; imageMode(CENTER); image(frame, locX, locY, 40, 80); animIndex = (animIndex % (walkRight.length-1))+1; locX +=5; } else if (locX <= 135) { //PImage frame = walkRight[animIndex]; PImage frame = walkRight[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locX +=5; locY -=3; } else if (locX <= 170) { //PImage frame = walkRight[animIndex]; PImage frame = walkRight[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locX +=5; locY +=5; } else if (locX <= 260) { //PImage frame = walkRight[animIndex]; PImage frame = walkRight[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locX +=5; locY ++; } else if (locX <= 280) { //PImage frame = walkRight[animIndex]; PImage frame = walkRight[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locX +=2; locY +=5; } else if (locX <= 320) { //PImage frame = walkRight[animIndex]; PImage frame = walkRight[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locX +=5; locY -=3; } else if (locX < 342) { PImage frame = walkRight[animIndex]; imageMode(CENTER); image(frame, locX, locY, 40, 80); animIndex = (animIndex % (walkRight.length-1))+1; locX +=5; } else { standing = true; PImage frame = walkRight[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); fuck = 1; //animIndex = (animIndex % (walkRight.length-1))+1; } } //if trapdoor //----------------------------SECONDTRIGGER_TRAPDOOR--------------------------------- if (triggerTrapDoor&&!triggerLift) { if (locX <= 360) { PImage frame = walkRight[animIndex]; imageMode(CENTER); image(frame, locX, locY, 40, 80); animIndex = (animIndex % (walkRight.length-1))+1; locX +=5; } else if (locX <= 385) { //PImage frame = walkRight[animIndex]; PImage frame = walkRight[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locX +=5; locY -=3; } else if (locX <= 414) { //PImage frame = walkRight[animIndex]; PImage frame = walkRight[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locX +=3; locY +=5; } //double image for some reason else if (locX <= 605 && locY==152) { PImage frame = walkRight[animIndex]; imageMode(CENTER); image(frame, locX, locY, 40, 80); animIndex = (animIndex % (walkRight.length-1))+1; locX +=5; } else if (locY < 300 && locX<=620) { PImage frame = walkRight[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locY +=5; } else if (locY <= 315 && locX<=690) { PImage frame = walkRight[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locX +=5; locY -=5; } else if (locY < 320 && locX<=725) { PImage frame = walkRight[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locX +=5; locY +=5; } else if (locX<=760 && locY<317) { PImage frame = walkRight[animIndex]; imageMode(CENTER); image(frame, locX, locY, 40, 80); animIndex = (animIndex % (walkRight.length-1))+1; locX +=2; } else if (locX<=780 && locY>60) { PImage frame = walkRight[animIndex]; imageMode(CENTER); image(frame, locX, locY, 40, 80); animIndex = (animIndex % (walkRight.length-1))+1; locY -=5; } else { standing =true; PImage frame = walkRight[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); fuck = 2; //animIndex = (animIndex % (walkRight.length-1))+1; } } //if lift //----------------------------THIRDTRIGGER_LIFT--------------------------------- if (triggerLift&&!triggerSpike1) { if (locX <= 860 && locY <=57) { PImage frame = walkRight[animIndex]; imageMode(CENTER); image(frame, locX, locY, 40, 80); animIndex = (animIndex % (walkRight.length-1))+1; locX +=5; } else if (locX<=900 && locY<=416) { PImage frame = walkRight[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); locY +=10; } else if (locY>=416 && locX>=843) { PImage frame = walkLeft[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locX -=5; println(locX); println(locY); } else if (locY>=417 && locX>=802) { PImage frame = walkLeft[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locX-=4; locY+=4; } else if (locY>=461 && locX>=754) { PImage frame = walkLeft[animIndex]; imageMode(CENTER); image(frame, locX, locY, 40, 80); animIndex = (animIndex % (walkRight.length-1))+1; locX-=4; } else { standing =true; PImage frame = walkLeft[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); } } //___________________________fourth Trigger_________________ if (triggerSpike1 && !triggerSpike2) { if (locY>=386 && locX>=720) { PImage frame = walkLeft[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locX-=4; locY +=4; } else if (locY>=471 && locX>=582) { PImage frame = walkLeft[animIndex]; imageMode(CENTER); image(frame, locX, locY, 40, 80); animIndex = (animIndex % (walkRight.length-1))+1; locX-=4; locY -=4; } else if (locY>=457 && locX>=630) { PImage frame = walkLeft[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); animIndex = (animIndex % (walkRight.length-1))+1; locX-=4; //println("here0"); //println(locX); //println(locY); } else if (locY>=450 && locX>=610 ) { PImage frame = walkLeft[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); animIndex = (animIndex % (walkRight.length-1))+1; locX-=4; locY+=2; //println("now0"); //println(locX); //println(locY); } else if (locY<=490 && locX>=540) { PImage frame = walkLeft[0]; image(frame, locX, locY, 40, 80); println("blah"); println(locX); println(locY); locX-=4; locY+=2; } else { standing =true; PImage frame = walkLeft[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //println("bl"); //println(locX); //println(locY); } } //___________________________fourth Trigger_________________ if (triggerSpike2 && triggerSpike1) { if (locY>=430 && locX>=480) { PImage frame = walkLeft[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locX-=5; locY -=5; //println("here1"); //println(locX); //println(locY); } if (locY>=420 && locX>=475) { PImage frame = walkLeft[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locY -=2; locX --; //println("here2"); //println(locX); //println(locY); } else if (locY>=390 && locX>=378) { PImage frame = walkLeft[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); //animIndex = (animIndex % (walkRight.length-1))+1; locX-=5; locY +=5; //println("b"); //println(locX); //println(locY); } else if (locY>=390 && locX>=290) { standing =true; PImage frame = walkLeft[animIndex]; imageMode(CENTER); animIndex = (animIndex % (walkRight.length-1))+1; image(frame, locX, locY, 40, 80); locX-=5; locY--; //println(locX); //println(locY); } else if (locY>=400 && locX>=220) { standing =true; PImage frame = walkLeft[animIndex]; animIndex = (animIndex % (walkRight.length-1))+1; imageMode(CENTER); image(frame, locX, locY, 40, 80); locX-=5; locY+=2; println("now"); println(locX); println(locY); } else if (locY>=400 && locX>=100) { standing =true; PImage frame = walkLeft[animIndex]; animIndex = (animIndex % (walkRight.length-1))+1; imageMode(CENTER); image(frame, locX, locY, 40, 80); locX-=5; println("here"); println(locX); println(locY); } else { standing =true; PImage frame = walkLeft[0]; imageMode(CENTER); image(frame, locX, locY, 40, 80); image(win, 250, 410, 300, 300); } } server.sendScreen(); } void keyPressed() { //watermill if (standing) { if (key == 'w' || key == 'W') { if (!triggerWaterMill) { triggerWaterMill = true; standing = false; } } //trapdoor if (key == 't' || key == 'T') { if (triggerWaterMill&&!triggerTrapDoor) { triggerTrapDoor = true; standing = false; } } //lift if (key == 'l' || key == 'L') { if (triggerTrapDoor&&!triggerLift) { triggerLift = true; standing = false; } } //spike1 if (key == 's' || key == 'S') { if (triggerLift&&!triggerSpike1 ) { triggerSpike1 = true; standing = false; } } //spike2 if (key == 'z' || key == 'Z') { if (triggerSpike1&&!triggerSpike2) { triggerSpike2 = true; standing = false; } } } } void serialEvent(Serial port) { String incoming = port.readStringUntil('\n'); if (incoming!=null) { incoming = trim(incoming); if (incoming.length()>1) { String[] lis = split(incoming, ','); permissions[0] = int(lis[0]); permissions[1] = int(lis[1]); permissions[2] = int(lis[2]); permissions[3] = int(lis[3]); permissions[4] = int(lis[4]); println(permissions); } port.write('0'); } }
Now for a description of some of our difficulties on this proj, as there were some complications.
- Adham originally planned trying to make our controller wirelessly work with the board instead of using long wires. This was very time consuming, and in the end the idea was scrapped because the wireless communication was simply not working the best.
- This was the first time I did this type of animation on processing. Most times, I simply use photoshop for animations by making gifs, but it turns out that processing and gifs do not play nicely. Therefore, it was time to use processing for the animation.
- There were a TON of random bugs. Adham had trouble causing certain motors to move on the board, even after we attached wires to the proj. (In the end, one of the stepper motors just would not move.) And our first stepper motor would move, but super slowly (Not to mention that our laser cut wheel for it would occasionally fly of when the stepper spun…oops.) Then there was also the fact that the slide potentiometer that was originally in the controller was on a logarithmic scale, making it hard to map. Another issue was the way I coded the animation, sometimes my character would slightly glitch as coordinate commands would partially overlap. And although there were more, I can’t remember them all.
- After lots of help and patience from Aaron, I was able to projection map the animation onto the board. This was actually easier after I found and used the image of the girl above, instead of a stick figure drawn in processing which was originally used for testing. This was due to the fact that the image was simply drawn from the CENTER coordinate from it, while the stick figure needed to move various lines in relation to each other. In the end though, I have a better idea of how to projection map with madmapper, and I look forward to working with it more in the future.
Overall, I would say the project was a success. Although I do think we should have toned down the project a bit, I am happy with the results.