Inspiration
For this week’s project, I wanted to use Arduino controls to refine what I did in my midterm. To refresh your memory, my midterm was a two-player game where the players ran around the map competing for candies. The game was ok, but the main bug was that I couldn’t have the two players moving at one time. This was due to the limitations of “Keypressed function”, as it wasn’t able to differentiate which player was pressing the keys.
By introducing Arduino into the game, I could simply create another “console” for player 2, so the player controls won’t be mixed up in the game.
Limitations
There were two limitations to using Arduino as a “console.” One is more obvious, which is the fact that the kit buttons aren’t that responsive, and aren’t really stationary. While I was playing the game with it, the buttons would slip off a lot of the times.
The second limitation is the problem of the breadboard. There are four buttons in the kit, so just enough for the four directions. However, the breadboard didn’t have the horizontal space to put an “up” and “down” button next to each other. As a compromise, I had to put the four buttons in a row, with the middle right being “up” and middle left “down”. Here is the final layout:
Challenges
The main challenge I ran into for this project was trying to integrate Arduino into my midterm project code. My midterm code was rather chaotic with a lot of functions, so it took a lot of time trying to piece together where the Arduino parts of the code would fit into the logic.
Full code
Arduino:
int button1 = 2; int button2 = 12; int button3 = 6; int button4 = 8; void setup() { // put your setup code here, to run once: pinMode(button1, INPUT); pinMode(button2, INPUT); pinMode(button3, INPUT); pinMode(button4, INPUT); Serial.begin(9600); } void loop() { int buttonState1 = digitalRead(button1); int buttonState2 = digitalRead(button2); int buttonState3 = digitalRead(button3); int buttonState4 = digitalRead(button4); char inByte=Serial.read(); Serial.print(buttonState1); Serial.print(','); Serial.print(buttonState2); Serial.print(','); Serial.print(buttonState3); Serial.print(','); Serial.println(buttonState4); }
Main Code:
PFont font; import processing.serial.*; Serial myPort; boolean left = false; boolean right = false; boolean up = false; boolean down = false; import processing.sound.*; //all the sounds in this game SoundFile eat; SoundFile die; SoundFile music; Ball b1; //initializing the two players Ball b2; PImage ninja, warrior, bg; PImage[][] ninjawalk; PImage[][] warriorwalk; int ndirection = 0; int wdirection = 0; int nstep = 0; int wstep = 0; int speed = 5; Candy[] candy; //initializing all the candy int count; color chosen; int livecount = 0; //how many edible candy there are boolean go = false; //start game value void setup() { size(1200, 800); bg = loadImage("bg.jpg"); printArray(Serial.list()); String portname=Serial.list()[3]; println(portname); myPort = new Serial(this,portname,9600); myPort.clear(); myPort.bufferUntil('\n'); //sounds eat = new SoundFile(this, "eat.mp3"); die = new SoundFile(this, "die.mp3"); music = new SoundFile(this, "music.mp3"); music.loop(); //slicing the two sprite sheets for player 1 and 2 ninja = loadImage("ninja.png"); ninjawalk = new PImage[4][3]; int w = ninja.width/3; int h = ninja.height/4; for(int iy = 0; iy < 4; iy++){ for(int ix = 0; ix<3; ix++){ ninjawalk[iy][ix] = ninja.get(ix*w, iy *h, w, h); } } warrior = loadImage("warrior.png"); warriorwalk = new PImage[4][3]; int ww = warrior.width/3; int wh = warrior.height/4; for(int wy = 0; wy < 4; wy++){ for(int wx = 0; wx<3; wx++){ warriorwalk[wy][wx] = warrior.get(wx*ww, wy *wh, ww, wh); } } //use this function to restart the game reset(); } void draw() { menu(); if (go == true){ begin(); } } void serialEvent(Serial myPort){ String s=myPort.readStringUntil('\n'); s=trim(s); if (s!=null){ int values[]=int(split(s,',')); if (values.length==4){ println(values); if (values[0] == 1){ right = true; } else if (values[0] == 0){ right = false; } if (values[1] == 1){ left = true; } else if (values[1] == 0){ left = false; } if (values[2] == 1){ up = true; } else if (values[2] == 0){ up = false; } if (values[3] == 1){ down = true; } else if (values[3] == 0){ down = false; } } } } //this function restarts the game from the gaming interface directly void reset(){ float unit = 200; b1 = new Ball(width/4, height - 20, 1); //player 1 character b2 = new Ball(width*3/4, height - 20, 2); //player 2 character int wideCount = width / int(unit); //calling the candies, initializing different candy colors int highCount = (height*3/4) / int(unit); count = wideCount * highCount; candy = new Candy[count]; int index = 0; color[] colors = new color[6]; colors[0] = #FF0011; colors[1] = #BA00FF; colors[2] = #2500FF; colors[3] = #00FFE8; colors[4] = #00FF01; colors[5] = #FFAF00; chosen = colors[int(random(0,5))]; for (int y = 0; y < highCount; y++) { for (int x = 0; x < wideCount; x++) { candy[index++] = new Candy(x * unit + unit/2, y * unit + unit/2, 20, 20, colors[int(random(0,5))]); } } for (Candy candy : candy) { //counting how many edible candies there are to know the win condition if (chosen == candy.c){ livecount ++; } } for (int i =0; i<count;i++){ //candy speed initializing candy[i].xSpeed = random(-2,2); candy[i].ySpeed = random(-2,2); } } //this is the starting menu void menu(){ background(bg); fill(255); font = createFont("BreatheFire-65pg.ttf", 60); textFont(font); textAlign(CENTER); text("CANDY RUSH \n even heros like candy", width/2, 185); font = createFont("Georgia", 40); textFont(font, 30); text("This is a two player game. Ninja and Warrior wants to take a break \n on their quest, so they are going to catch candy. Some candies are \n poisonous, and only the candy indicator can tell you which candies are edible. \n The first player to catch all the edible candy wins. \n NINJA: WASD, WARRIOR: direction keys", width /2, 370); stroke(0); fill(0); rectMode(CENTER); rect(width/2, 680, 200, 72); fill(255); textFont(font, 60); font = createFont("BreatheFire-65pg.ttf", 42); textFont(font); textAlign(CENTER); text("PLAY", width/2, 700); if (mouseX>=(width/2 - 100) & mouseX<=(width/2 + 100) & mouseY>=650 & mouseY<=720){ stroke(255); noFill(); rect(width/2, 680, 200, 72); if (mousePressed){ go = true; } } } //this is the game starting function. the actual game is ran here void begin(){ background(bg); textSize(24); text(b1.score, width/4, height - 50); text(b2.score, width*3/4, height - 50); //calling candy functions for (Candy candy : candy) { candy.display(); candy.collision(); candy.update(); candy.checkEdges(); } //this is the candy indicator stroke(0); fill(chosen); ellipse(width/2, height - b1.diameter, b1.diameter, b1.diameter); //calling character functions b1.display(); b1.move(); b2.display(); b2.move(); checkwin(); } void checkwin(){ if (livecount == 0){ //win condition for (Candy candy : candy) { candy.xSpeed = 0; candy.ySpeed = 0; } rectMode(CENTER); if (b1.score > b2.score){ fill(0); rect(width/2, height/2, width, height); fill(255, 255, 255); textSize(32); text("Ninja Wins! \n (Press r to restart)", width/2, height/2); } else if (b2.score > b1.score){ fill(0); rect(width/2, height/2, width, height); fill(255, 255, 255); textSize(32); text("Warrior Wins! \n (Press r to restart)", width/2, height/2); } else if (b2.score == b1.score){ fill(0); rect(width/2, height/2, width, height); fill(255, 255, 255); textSize(32); text("Tie! \n (Press r to restart)", width/2, height/2); } //restart button if (keyPressed) { if (key == 'r' || key == 'R') { reset(); } } } }
Player Class:
class Ball { float x; float y; float diameter; int score; int player; int w = ninja.width/3; int h = ninja.height/4; int ww = warrior.width/3; int wh = warrior.height/4; Ball(float tempX, float tempY, int tempPlayer) { x = tempX; y = tempY; diameter = 45; score = 0; player = tempPlayer; } void display() { imageMode(CENTER); //displaying player 1 if (player == 1) { image(ninjawalk[ndirection][nstep], x, y, w, h); } if (player == 2) { //displaying player 2 image(warriorwalk[wdirection][wstep], x, y, ww, wh); } } void move() { if (player == 1) { if (keyPressed) { if (key == 'w' || key == 'W') { y = y - 5; ndirection = 0; } if (key == 'a' || key == 'A') { x = x - 5; ndirection = 3; } if (key == 's' || key == 'S') { y = y + 5; ndirection = 2; } if (key == 'd' || key == 'D') { x = x + 5; ndirection = 1; } if (frameCount%speed == 0) { nstep = (nstep + 1) %3; } } } if (player == 2) { if (up == true) { y = y - 5; wdirection = 0; } if (left == true) { x = x - 5; wdirection = 3; } if (down == true) { y = y + 5; wdirection = 2; } if (right == true) { x = x + 5; wdirection = 1; } if (frameCount%speed == 0) { wstep = (wstep + 1) %3; } } } }
Candy Class:
class Candy { float x; float y; float w; float h; float xSpeed, ySpeed; boolean caught = false; color c; Candy(float tempX, float tempY, float tempW, float tempH, color tempC) { x = tempX; y = tempY; w = tempW; h = tempH; c = tempC; xSpeed = ySpeed = 0; } void display() { ellipseMode(CENTER); if (caught == false){ fill(c); ellipse(x, y, w, h); } } //collision function for when players catch the right and wrong candy void collision() { if ((b1.x - b1.w *3/4 < x && x < b1.x + b1.w*3/4) & (b1.y - b1.h*3/4 < y && y < b1.y + b1.h*3/4)){ if (c == chosen){ x = -50; y = -50; caught = true; b1.score ++; livecount --; eat.play(); } else if (c != chosen){ b1.x = width/4; b1.y = height - b1.diameter; die.play(); } } if ((b2.x - b2.w*3/4 < x && x < b2.x + b2.w*3/4) & (b2.y - b2.h*3/4 < y && y < b2.y + b2.h*3/4)){ if (c == chosen){ x = -50; y = -50; caught = true; b2.score ++; livecount --; eat.play(); } else if (c != chosen){ b2.x = width*3/4; b2.y = height - 20; die.play(); } } } void update(){ x += xSpeed; y += ySpeed; } void checkEdges() { if (y>(height*3)/4) { ySpeed = -ySpeed; } if (y<0) { ySpeed = -ySpeed; } if (x>width) { xSpeed = -xSpeed; } if (x<0) { xSpeed = -xSpeed; } } }