For this project, I continued to want to make a two-player game. The game is competitive in nature, where the two players will rush to grab valuable resources before it’s all taken by the other.
Inspiration
There isn’t any single source of inspirations for this game. It’s 2D interface kind of resembles the Nintendo pokemon games, where the world is a flat surface and players take a birds-eye view of the world. The mode of the game actually reminds me of the lego star wars game, in some ways. Players have to rush to collect resources while dodging dangerous objects.
Game-Specific Information
There are two heroes, a ninja and a warrior that are on a quest together in an underground mine. While resting, candies start to float out of the caves, and the two heroes decide to grab candies to eat. However, there are edible candies and poisonous candies, and only the candy indicator tells the players which candies are edible. In the game, this candy indicator is located at the bottom middle of the screen, as shown:
If a player “eats” an edible candy, one point will go to their score, which shows at their spawn point, at the bottom of the screen. If a player “eats” a poisonous candy, they die and respawn at their spawn point. Once all the edible candies are collected, the player with the most candy wins. If both have the same amount, it is a tie.
When the game initializes, there is a starting screen with the name of the game, “Candy Rush”, as well as the instructions and a “play” button. When hovering the mouse over the play button, the button will be highlighted:
Regarding the sounds, the game has three in total. One is the background music that loops endlessly. The other two are sounds for when the hero eats a poisonous candy and an edible candy.
There are two classes, one for the heroes and one for the candies. The hero tab simply includes its display and move functions. The candy tab includes its display, collision with the heroes, collision with a wall, and movement functions. The main code page, apart from draw and setup, includes four main functions. The reset function is in charge of initializing all the game values. Most of these functions could have gone into setup, but I needed to create a restart function for when the game ends. Thus, I moved most of the value initialization into the reset function, and then called the reset function in setup. This way, I could decide when to “re-setup” my game.
The next function is menu, which is in charge of the starting menu. I run this function first in draw. While running the menu function, when the player clicks “play”, the universal boolean “go” will turn to true, triggering the begin function.
The begin function is the main gameplay, where all the object functions are called. At the end of the begin function, the last function is called, which is checkwin.
Checkwin simply runs through the win conditions constantly and displays the winning screen when the win condition is met.
Images used
Background:
Hero Sprites:
Sounds used
Background Music:
Catching edible candy:
Catching poisonous candy:
Full Code
Main Code Page:
PFont font; 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"); //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(); } } //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 Page:
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 (keyPressed) { if (keyCode == UP) { y = y - 5; wdirection = 0; } if (keyCode == LEFT) { x = x - 5; wdirection = 3; } if (keyCode == DOWN) { y = y + 5; wdirection = 2; } if (keyCode == RIGHT) { x = x + 5; wdirection = 1; } if (frameCount%speed == 0) { wstep = (wstep + 1) %3; } } } } }
Candy Class Page:
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; } } }
Entire Compressed Code
Game Demonstration