Nathan – Midterm Project

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

Midterm

Game Demonstration

Leave a Reply