Midterm: The Fight Goes On

As stated last week, my project was inspired by the classic Road Fighter game, but trying to adapt it to another theme instead. And unfortunately (but not that surprisingly), this post is coming a night later than I thought it would, the 14th “night” instead of 13th.

In my game, named “Magic Fighter” now, the main character (it was pointless to get a wizard with a cloak because the movements would be hidden and I wanted a more retro feeling game) is trying to reach the safety of the Hogwarts castle, running away from a great evil threat. On the way, you can pick up objects like potions, charms and chocolates (it helps) which will increase your health, and you have to avoid dark magic residues and dementors on the way which will decrease your health and the time you have. You can see your health percentage in a bar on the side, as well as the timer.

Update: I can now add the graphics here. I made everything a darker colour because I thought the evening look fit it more.

I am done with all the mechanics of the game and also with the sprites and images for most things, but as I was testing out my object images, my laptop started a weird thing where I am not able to edit or see the image sizes of any of my images, because the cursor changes to the loading symbol whenever I try. Fortunately, this happened after I had already taken care of the character and enemies, and inserted one of the object images as a test, but I have to make more object variety options and update the code accordingly, and also insert something at the finish line. The png images have been ready since long, and I tried everything to get my editor to work properly, but it is just not happening.

I am going to attach a zip folder anyway, but I hope to post a proper code breakdown and video once I am able to insert the images I want to. So until then, this documentation is going to be a bit bare. Everything else is done, and it is still a playable game which you can try out.

I was also not able to implement super and sub classes because for some reason, the display function in all of them was giving a problem. So my final code now has separate classes. Overall, display has been acting a bit weird. Also, it might be a bit weird to see the dementors disappear once you hit them but let’s just assume they left because they got what they wanted. If they are not immediately removed from the array then the health would decrease to 0 almost immediately. But I did implement the dist() function as mentioned in the tip.

Overall, this project has given me a lot of trouble and I have perhaps made it more difficult for myself than I needed to, not because of the actual difficulty of the game itself but the way I set about it the past few days and how I have been managing things in general, and my laptop certainly hasn’t helped. I haven’t been able to cope with the stress of submitting a less than perfect game, I really wanted it to complete my vision. I can’t help but feel disappointed that this is still not over, and I am praying that in the morning my laptop will magically let me do what I need to do. This is a request (especially to Professor) that if you see this current version, by all means, go ahead and play it, but do check out the updated one too, I will update the documentation with it as soon as it is ready and it will look much better then.

(Edit: I am keeping this just so that you can see how much I was done with before, but this is not the final version now)

im_midterm_project 

I am really sorry 🙁

Updates:

My laptop still is refusing to work with PNGs, so in the end I had to resort to other methods but finally, I am done. Huge thanks to the internet for providing free tools.

Here are the start and end screens of my game. I have tried to keep the font and sprites to a retro look.

Additionally, here is the code for easier reading:

import processing.sound.*;

Character character;
Object[] objects;
Enemy[] enemies;
MovingEnemy[] moving_enemies;
int timer;
float health_percentage;
boolean game_over;
boolean started_game = false;
PFont f;
int finishline_y;
SoundFile theme;
SoundFile pick;
SoundFile hit;
PImage hogwarts;

void setup() {
  size(1000, 720);
  f = createFont("ARCADE.TTF", 72);
  textFont(f);
  theme = new SoundFile(this, "rise-and-shine.mp3");
  pick = new SoundFile(this, "object pick.wav");
  hit = new SoundFile(this, "enemy hit.wav");
  character = new Character(500, 500, 57, "Main Character.png", 100, 140, 4);
  objects = new Object[30];
  enemies = new Enemy[30];
  moving_enemies = new MovingEnemy[30];
  hogwarts = loadImage("hogwarts.png");

  //object initialisation
  for (int i = 0; i<10; i++) {
    int choice = int(random(3)); //so that one of the three options is chosen
    objects[i] = new Object(random(275, 725), random(-5200, 695), 25, choice==0? "potion.png": choice==1? "charm.png": "chocolate bar.png", 50, choice==2? 87: 50); //so that as the negative is updated to positive, the illusion of movement is created

    //to ensure the objects don't overlap with each other
    for (int j = 0; j<i; j++) {
      if (i!=j && objects[i].distance(objects[j]) < 50) {
        while (objects[i].distance(objects[j]) < 50) {
          objects[i] = new Object(random(275, 725), random(-5200, 695), 25, choice==0? "potion.png": choice==1? "charm.png": "chocolate bar.png", 50, choice==2? 87: 50);
        }
      }
    }
  }

  //enemy initialisation
  for (int i = 0; i<30; i++) {
    enemies[i] = new Enemy(random(275, 725), random(-5200, 695), 30, "magic blast.png", 60, 48);

    //to ensure they don't overlap with each other
    for (int j = 0; j<i; j++) {
      if (i!=j && enemies[i].distance(enemies[j]) < 60) {
        while (enemies[i].distance(enemies[j]) < 60) {
          enemies[i] = new Enemy(random(275, 725), random(-5200, 695), 30, "magic blast.png", 60, 48);
        }
      }
    }
  }

  //moving enemies initialisation
  for (int i = 0; i<20; i++) {
    moving_enemies[i] = new MovingEnemy(random(280, 720), random(-12000, 690), 60, "dementor.png", 120, 116, 280, 720);

    //to ensure they don't overlap with each other
    for (int j = 0; j<i; j++) {
      if (i!=j && moving_enemies[i].distance(moving_enemies[j]) < 120) {
        while (moving_enemies[i].distance(moving_enemies[j]) < 120) {
          moving_enemies[i] = new MovingEnemy(random(280, 720), random(-12000, 690), 60, "dementor.png", 120, 116, 280, 720);
        }
      }
    }
  }
  timer = 65;
  health_percentage = 100;
  finishline_y = -2600;
  game_over = false;
  if (started_game == false)
    startScreen();
}

void draw() {

  if (started_game && !game_over) {
    if (frameCount % 60 == 0)
      timer-=1;    
    finishline_y+=1; //so that the finish is slowly reached
    gameDisplay();
  }
}

void startScreen() {
  background(0);
  textSize(60);
  text("MAGIC FIGHTER", 280, 300);
  textSize(30);
  text("Reach the safety of the castle in time!", 200, 365);
  text("Avoid the green magic and Dementors. Pick up the other objects!", 50, 400);
  textSize(25);
  text("Press the left and right arrow keys for movement", 250, 450);
  textSize(40);
  text("Click to begin!", 370, 490);
}

void gameDisplay() {
  
  //game winning conditions
  if (character.y == finishline_y) {
    background(0);
    fill(255);
    textSize(60);
    text("Congratulations!", 275, 300);
    text("You are safe now!", 240, 365);
    textSize(30);
    text("Click to play again", 365, 435);
    theme.stop();
    game_over = true;
    return;
  }
  
  //game losing conditions
  if (character.health <= 0 || timer <= 0) {
    background(0);
    fill(255, 255, 255);
    textSize(100);
    text("Oops, Game Over!", 145, 310);
    textSize(30);
    text("Click to play again", 365, 415);
    theme.stop();
    game_over = true;
    return;
  }

  //basic background
  background(#5D575F);
  noStroke();
  fill(#2E793B);
  rect(0, 0, 250, 720);
  fill(0);
  rect(750, 0, 1000, 720);

  //displaying the objects and enemies and character
  for (int i = 0; i<30; i++) {
    //picking  up objects
    if (objects[i]!=null) {
      if (dist(character.x, character.y, objects[i].x, objects[i].y) <= character.r + objects[i].r) {
        objects[i] = null;
        if (character.health+2 < 150) {
          character.health+=2;
        } else {
          while (character.health < 150) {
            character.health+=1;
          }
        }
        //object sounds
        pick.play();
      }
    }

    //being hit by enemies
    if (enemies[i]!=null) {
      if (dist(character.x, character.y, enemies[i].x, enemies[i].y) <= character.r/2 + enemies[i].r) {
        character.health -= 10;
        timer -= 1;
        enemies[i] = null;
        //hit sounds
        hit.play();
      }
    }

    if (moving_enemies[i]!=null) {
      if (dist(character.x, character.y, moving_enemies[i].x, moving_enemies[i].y) <= character.r/2 + moving_enemies[i].r) {
        character.health -= 10;
        timer -= 1;
        moving_enemies[i] = null;
        //hit sounds
        hit.play();
      }
    }

    //"finish line"
    imageMode(CORNER);
    image(hogwarts, 150, finishline_y-529);
    imageMode(CENTER);

    if (objects[i]!=null)
      objects[i].display();

    if (enemies[i]!=null)
      enemies[i].display();

    if (moving_enemies[i]!=null)
      moving_enemies[i].display();
  }

  //calculating health
  fill(255, 255, 255);
  textSize(36);
  text("Health: ", 780, 100);
  health_percentage = int((character.health * 100) / 150.0 * 100) / 100.0;
  noStroke();

  //To change colour depending on health
  if (health_percentage >= 50)
    fill(0, 250, 0);
  else if (health_percentage >= 20)
    fill(255, 255, 0);
  else
    fill(255, 0, 0);
  rect(780, 125, character.health, 25);
  noFill();
  strokeWeight(5);
  stroke(255, 255, 255);
  rect(780, 125, 150, 25);
  fill(255, 255, 255);
  textSize(22);
  text(str(health_percentage) + "%", 935, 150);

  //To display timer
  fill(255);
  textSize(36);
  text("Time: ", 780, 225);
  textSize(40);
  text(str(timer), 780, 260);
  
  character.display();
}

void mouseClicked() {
  if (started_game == false) {
    started_game = true; //to start game
    theme.play();
  } else if (game_over) {
    frameCount = -1; //to restart game
    theme.play();
  }
}

//for movement when the left or right keys are pressed
void keyPressed() {
  if (keyCode == LEFT)
    character.pressed_left = true;
  else if (keyCode == RIGHT)
    character.pressed_right = true;
}
void keyReleased() {
  if (keyCode == LEFT)
    character.pressed_left = false;
  else if (keyCode == RIGHT)
    character.pressed_right = false;
}

The different classes:

class Character {
  float x, y, r, vx;
  int img_w, img_h;
  boolean pressed_left; //for the keys pressed
  boolean pressed_right;
  int health;
  PImage spritesheet;
  PImage[] sprites;
  int num_frames, frame;

  Character(float x, float y, float r, String s, int w, int h, int num_frames) {
    this.x = x;
    this.y = y;
    this.r = r;
    img_w = w;
    img_h = h;
    spritesheet = loadImage(s);
    sprites = new PImage[num_frames];
    for (int index = 0; index < num_frames; index++) {
      sprites[index] = spritesheet.get(index*img_w, 0, img_w, img_h);
    }
    frame = 0;
    this.num_frames = num_frames;
    vx = 0;
    health = 150;
    pressed_left = false;
    pressed_right = false;
  }

  void display() {
    update();
    image(sprites[frame], x - img_w/2, y - img_h/2);
  }

  void update() {
    if (pressed_left == true && x-r>300)
      vx = -5; //to change direction
    else if (pressed_right == true && x+r<800)
      vx = 5;
    else
      vx = 0;

    x+=vx;

    //for moving slower between the different frames
    if (frameCount % 5 == 0)
      frame = (frame + 1) % num_frames;
  }
}
class Enemy {

  float x, y, r;
  int img_w, img_h;
  PImage img;

  Enemy(float x, float y, float r, String s, int w, int h) {
    this.x = x;
    this.y = y;
    this.r = r;
    img = loadImage(s);
    img_w = w;
    img_h = h;
    imageMode(CENTER);
  }

  void update() {
    y+=2;
  }

  void display() {
    update();
    image(img, x, y, img_w, img_h);
  }

  float distance(Enemy other) {
    return dist(this.x, this.y, other.x, other.y);
  }
}
class MovingEnemy {

  float x, y, r, vx, ll, rl;
  int dir;
  int img_w, img_h;
  PImage img;

  MovingEnemy(float x, float y, float r, String s, int w, int h, float ll, float rl) {
    this.x = x;
    this.y = y;
    this.r = r;
    img = loadImage(s);
    img_w = w;
    img_h = h;
    imageMode(CENTER);
    this.ll = ll;
    this.rl = rl;
    vx = random(1, 5); //to have random speeds
    dir = int(random(0, 2));
    if (dir==0) {
      vx*=-1;
    }
  }

  void update() {
    x += vx;
    if (x<ll) { //moving between left and right limits
      vx*=-1;
      dir = 1;
    } else if (x>rl) {
      vx*=-1;
      dir = 0;
    }

    y+=2;
  }

  void display() {
    update();
    image(img, x, y, img_w, img_h);
  }

  float distance(MovingEnemy other) {
    return dist(this.x, this.y, other.x, other.y);
  }
}
class Object {
  float x, y, r;
  int img_w, img_h;
  PImage img;

  Object(float x, float y, float r, String s, int w, int h) {
    this.x = x;
    this.y = y;
    this.r = r;
    img = loadImage(s);
    img_w = w;
    img_h = h;
    imageMode(CENTER);
  }

  void update() {
    y += 2; //for downward movement
  }

  void display() {
    update();
    image(img, x, y, img_w, img_h);
  }

  float distance(Object other) {
    return dist(this.x, this.y, other.x, other.y);
  }
}

I hope the comments and my previous explanations make everything clear.

Finally, here is a video of me playing the game, once when I won, once when I lost. The game gets quite slow sometimes so the audio at the end glitched, otherwise it should stop at the end. I got the main theme from chosic.com.

Here is a zip folder where you can see all the sprites and images and sounds, and play the final game itself!

im_midterm_project 2

And phew, this is finally done! I hope I didn’t miss out anything. And despite what it might seem like, I did enjoy working on this game, until the very end! 🙂

Update 2.0: Well, my work wasn’t done. The above zip file won’t work either. Because I had apparently not saved the latest version of my code. Here is one which will hopefully work.

im_midterm_project 4

Leave a Reply