Week 3 – Go, get that 4.0!

For the Week 3 assignment, I decided to make a game, and in order to make it a little more fun, I decided to make it NYUAD-themed (at least a little). However, this turned out a little harder than I expected it to be. I had the idea of a fun but very simple game, but the implementation was not-so-simple.

My game is based on a very basic concept. The player plays the game as Faiza the Falcon, and uses arrow keys to help Faiza dodge all distractions on the way and to eventually reach the 4.0 GPA. I was so excited when I started making the game that I created the sprite for Faiza myself using https://www.piskelapp.com/. Attached below is a screenshot of the game. A screen recording is also attached at the end (I’m sorry for the lag, my laptop does not cooperate when I start screen recording).

Implementation

Even when I started creating the game, it was clear that I had to create it using four classes; the Faiza class, the GPA class, the Distractions class and the main Game class.

Within the Faiza class, I had to work on controlling Faiza through the arrow keys, checking edges to ensure that Faiza didn’t go beyond the screen, displaying Faiza, and checking Faiza’s collisions with the distractions and with the GPA (collision with the GPA marked the end of the game). I made a separate distance() method within the Faiza class for collision detection. This method would return the distance between Faiza and the other object, and if this distance was less than the sum of their radii, then a collision was detected. Figuring this out was one of the more difficult parts of making the game.

The GPA class was straightforward and was nothing more than displaying the GPA.

The distractions class involved some additional elements. I instantiated each distraction at specific coordinates (because if it was random then there could be a scenario where it would be almost impossible to win the game). I assigned random x and y velocities (but within a certain range) to each distraction which would mean that the structure of the game was different every time. It was also important to ensure that the distractions would rebound whenever any of them hit any of the walls.

Lastly, the game class was mainly about displaying text and calling display methods of other objects.

Challenges

Firstly, I had a hard time working with methods of classes. The update and display methods of each class left me confused. I knew I had to use these two methods but couldn’t figure out how to exactly work with them. I ended up calling my update() method inside the display() method, and now I understand how it did the job for me.

Working with image objects was confusing for some reason. I never intended to use so many different images but then the game would look boring. I had to do a lot of research to get comfortable with using images.

 

Attached below are the video and the code.

import java.lang.Math;
import processing.sound.*;
SoundFile file;

String audioName = "intro.mp3";
String path;

PImage[] images;
Game game;

// creating an array for distractions which are used later
Distractions[] distractions;

class Faiza{
  float posX, posY;
  float radius;
  float velocityX;
  float velocityY;
  float imgwidth, imgheight;
  String directionX;
  String keyY, keyX;
  boolean alive;
  int counter, frame;
  
  Faiza(float x, float y, float r, float img_w, float img_h){
    posX = x;
    posY = y;
    radius = r;
    velocityX = 0;
    velocityY = 0;
    imgwidth = img_w;
    imgheight = img_h;
    directionX = "right";
    keyY = "none";
    keyX = "none";
    alive = true;
    counter = 0;
    frame = 0;
  }
  
  void display(){
    update();
    if (directionX == "right"){
      image(images[0], float(int(posX - imgwidth/2)), float(int(posY - imgheight/2)), imgwidth, imgheight, int(frame * imgwidth), 0, int((frame + 1) * imgwidth), int(imgheight));
    }
    else if (directionX == "left"){
      image(images[0], float(int(posX - imgwidth/2)), float(int(posY - imgheight/2)), imgwidth, imgheight, int((frame + 1) * imgwidth), 0, int(frame * imgwidth), int(imgheight));
    }
  }
  
  void update(){
    //The condition below is for when Faiza moves left
    if (keyX == "left"){
       velocityX = -2;
       if (posX - radius + velocityX < 6){
         velocityX = 0;
       }
       posX += velocityX;
    }
                    
    //The condition below is for when Faiza moves right
    else if (keyX == "right"){
      velocityX = 2;
      if (posX + radius + velocityX > 1018){
        velocityX = 0;
      }
      posX += velocityX;
    }

    //If none of the left and right keys are being pressed, Faiza stops moving horizontally
    else{
      velocityX = 0;   
    }        
    
    if (keyY == "up"){
      velocityY = -2;
      if (posY - radius + velocityY <= 5){
        velocityY = 0;
      }
      posY += velocityY;
    }
                    
//The condition below is for when Faiza moves downwards
    else if (keyY == "down"){
      velocityY = 2;
      if (posY + radius + velocityY >= 762){
        velocityY = 0;
      }      
      posY += velocityY;
    }
                    
//If none of the up and down keys are being pressed, Faiza stops moving vertically
    else{
    velocityY = 0;
    }
  
    if (distance(game.gpa) <= (radius + game.gpa.radius)){
      game.level += 1;
    }
    
    if (!(posX >= 0 && posX <= 100 && posY >= 530 && posY <= 640)){ 
      for (int i = 0; i < 6; i++){
        if (distance(distractions[i]) <= radius + distractions[i].radius){
          counter += 1;
          println(counter);
          alive = false;
        }
      }
    }
  }
  
  // this distance method will be used to check for collisions with distractions
  double distance(Distractions target){
    float a = (posX - target.posX);
    float b = (posY - target.posY);
    double c = Math.pow(a, 2);
    double d = Math.pow(b, 2);
    return Math.pow(c + d, 0.5);
  }
  
   // this distance method will be used to check for collisions with the gpa (marking the end of the game)
   double distance(GPA target){
    float a = (posX - target.posX);
    float b = (posY - target.posY);
    double c = Math.pow(a, 2);
    double d = Math.pow(b, 2);
    return Math.pow(c + d, 0.5);
  }
}

class Distractions{
  float posX, posY;
  float radius;
  float imgwidth, imgheight;
  int frame;
  PImage img;
  float velocityX, velocityY;
  
  Distractions(float x, float y, float r, String _img, float img_w, float img_h){
    posX = x;
    posY = y;
    radius = r;
    img = loadImage(_img);
    imgwidth = img_w;
    imgheight = img_h;
    frame = 0;
    velocityX = random(2,5);
    velocityY = -1 * random(2,5);
  }
  
  void update(){
    if (posX + radius >= 1024){
      velocityX *= -1;
    }
    if (posX - radius <= 0){
      velocityX *= - 1;
    }
    if (posY - radius <= 10){
      velocityY *= -1;
    }
    if (posY + radius >= 780){
      velocityY *= -1;
    }
    
    posX += velocityX;
    posY += velocityY;
  }
  
  void display(){
    update();
    image(img, float(int(posX - imgwidth/2)), float(int(posY - imgheight/2)), imgwidth, imgheight, int(frame * imgwidth), 0, int((frame + 1) * imgwidth), int(imgheight));
  }
}

class GPA{
  float posX, posY;
  float radius;
  float imgwidth, imgheight;
  int frame;
  
  GPA(float x, float y, float r, float img_w, float img_h){
    posX = x;
    posY = y;
    radius = r;
    imgwidth = img_w;
    imgheight = img_h;
    frame = 0;
  }
  
  void display(){
    image(images[1], float(int(posX - imgwidth/2)), float(int(posY - imgheight/2)), imgwidth, imgheight, int(frame * imgwidth), 0, int((frame + 1) * imgwidth), int(imgheight));
  }
}

class Game{
  float game_width, game_height;
  Faiza faiza;
  GPA gpa;
  int level;
  
  Game(float game_wth, float game_hght){
    level = 1;
    game_width = game_wth;
    game_height = game_hght;
    faiza = new Faiza(34, 585, 27, 66, 66);
    gpa = new GPA(990, 35, 25, 70, 56);
  }
  
  void update(){
    if (faiza.alive == false){
      faiza.posX = 34;
      faiza.posY = 585;
      faiza.alive = true;
    }
  }
  
  void display(){
    update();
    
    image(images[2], 0, 0);
    
    if (level == 1){
      textMode(CENTER);
      textSize(40);
      fill(255, 213, 43);
      text("GET THAT 4.0!", 310, 65);
    }
    
    if (level != 1){
      textSize(150);
      fill(255, 213, 43);
      text("GAME", 270, 220); 
      text("OVER", 290,350);
      textSize(50);
      text(faiza.counter + " distractions later,", 240, 550);
      text("you achieved that 4.0 GPA!", 200, 600);
    }
    
    if (level == 1){
      faiza.display();
      gpa.display();
    }
    
    for (int i = 0; i < 6; i++){
      distractions[i].display();
    }
    
  }
}
  

void setup(){
 size(1024,768);
 game = new Game(1024, 768);
 path = sketchPath(audioName);
 file = new SoundFile(this, path);
 file.loop();
 images = new PImage[3];
 images[0] = loadImage("faiza.png");
 images[1] = loadImage("gpa.png");
 images[2] = loadImage("background.png");
 
 distractions = new Distractions[6];
 distractions[0] =  new Distractions(100, 300, 58, "jake.png", 120, 120);
 distractions[1] =  new Distractions(444, 333, 48, "insta.png", 100, 100);
 distractions[2] =  new Distractions(900, 120, 48, "facebook.png", 100, 100);
 distractions[3] =  new Distractions(887, 635, 48, "netflix.png", 100, 100);
 distractions[4] =  new Distractions(134, 587, 48, "youtube.png", 100, 100);
 distractions[5] =  new Distractions(55, 100, 48, "ps.png", 120, 120);
}

void draw(){
  background(255, 255, 255);
  game.display();
}

// allowing key presses to dictate Faiza's movement
void keyPressed(){
  if (key == CODED){
    if (keyCode == RIGHT){
      game.faiza.keyX = "right";
    }
    if (keyCode == LEFT){
      game.faiza.keyX = "left";
    }
    if (keyCode == UP){
      game.faiza.keyY = "up";
    }
    if (keyCode == DOWN){
      game.faiza.keyY = "down";
    }
  }
}
    
    
void keyReleased(){
  if (key == CODED){
    if (keyCode == RIGHT){
      game.faiza.keyX = "none";
    }
    if (keyCode == LEFT){
      game.faiza.keyX = "none";
    }
    if (keyCode == UP){
      game.faiza.keyY = "none";
    }
    if (keyCode == DOWN){
      game.faiza.keyY = "none";
    }
  }
}

 

Creating Art using Loops – Week 2

This week, we were supposed to create some form of art (although mine looks like art no one would see) through the use of loops learned in class. The more difficult part for me was to come up with an idea which I could then expand on. I ended up taking inspiration from “Random Squares” by Bill Kolomyjec, and played with it a little to draw circles instead of squares.

Initially, I created a grid by dividing the canvas into rows and columns. This was pretty simple.

Next, I used a nested for-loop which first went over all the rows in the grid, and for each row, it went over all the columns in that row.

For each cell in this grid, my main goal was to create circles with decreasing radius. Initially I wrote a lot of code to do this (which I then took a screenshot of to realize how dumb my approach was), but then I realized that it was very repetitive. I simply used another for loop for each cell, which could draw for me as many circles as I wanted. This resulted in the use of a triple nested for-loop (a for-loop in a for-loop in a for-loop), something I was very proud of.

The dumb approach (before I used loops to draw circles)

Finally, I decided to play with colours. This took a long time trying to get the right combination (and I was never able to find it). I ended up using random colors for each of the circles which would change every time the draw function was called. I also used random greyscale shades for the background of each cell. I created two versions of the final work, one where the circles were in greyscale and the other where I used RGB colors.


Trigger Warning: Bright Flashing Lights

Lastly, I have changed the frameRate for this program. After experimenting, I set it to 5 where it gives the best effect. Last week, I also promised to try my best to make my code as dynamic as I could, and so I did.

float rows;
float cols;
float sqwidth = 200;

void setup() {
  size(800, 800);
  frameRate(5);
}

void draw() {
  rows = height/sqwidth;
  cols = width/sqwidth;

  //going over each row
  for (int i = 0; i < rows; i++)
  {
    //going over each column
    for (int j = 0; j < cols; j++)
    {
      fill(random(0, 255));
      noStroke();
      rect(i*sqwidth, j*sqwidth, sqwidth, sqwidth);
      
      //making multiple circles in each cell of the grid
      for (int k = 30; k < 200; k+=10)
      {
         stroke(0);
         //fill(random(0, 255), random(0, 255), random(0, 255));
         fill(random(0,255));
         circle(i*sqwidth + sqwidth/2, j*sqwidth + sqwidth/2, sqwidth - k);
      }
    }
  }
  
  //random greyscale backgrounds to give a different effect
  if (frameCount % 10 == 0)
  {
    background(random(0, 255));
  }
}

 

Week 1 – Self Portrait

The first assignment was about experimenting with shapes and Processing functions in order to create a self-portrait. I decided to add some flavour to it through a representation of my tongue touching my nose, something I am extremely proud of. And yes, this is exactly how the rest of my face looks like when I try showcasing my talents.

I have learnt a lot about the use of shapes during this process of making the self-portrait. Some of the shapes I used were triangle(), rect(), arc(), ellipse(), circle() and line(). I used functions for different parts of the face which made it easier for me to work with them individually.

The most challenging part for me was experimenting with the coordinates so that everything could appear in the right place. Some of the features of coding using Processing really helped me here, such as using a circle to represent the ears. Although I have used full circles to represent the ears, they do not appear as full circles because the colour used was the same as that of the rest of the face, leading to an overlap.

Also, I used two different hairstyles (one of them is commented). Once again, allowing the hair to appear over the face (because code is executed line by line) helped me avoid dealing with awkward shapes.

int centerX;
int centerY;
int faceCenter;
float faceWidth = 140;
float faceHeight = 175;

void setup() {
  size(640, 480);
  background(255, 255, 0);
  centerX = width / 2;
  centerY = height / 2;
}


void draw() {
  face();
  nose();
  hair();
  eyes();
  lips();
  body();
}

void face() {
  fill(247, 194, 168);
  noStroke();
  //ellipse(centerX, centerY - 45, faceWidth, faceHeight);
  rect(260, 140, 120, 90);
  arc(320, 230, 120, 70, 0, PI);
  ellipse(385, 180, 20, 25);
  ellipse(255, 180, 20, 25);
  noFill();
  stroke(255, 160, 180);
  ellipse(253, 180, 10, 15);
  ellipse(386, 180, 10, 15);
}

void nose() {
  stroke(0);
  strokeWeight(1);
  line(centerX, centerY - 55, centerX - 7, centerY - 35);
  line(centerX, centerY - 55, centerX + 7, centerY - 35);
  line(centerX - 7, centerY - 35, centerX, centerY - 33);
  line(centerX + 7, centerY - 35, centerX, centerY - 33);
}

void hair() {
  fill(0);
  arc(320, 140, 118, 50, PI, PI*2);
  triangle(260, 140, 360, 150, 379, 140);
  //the two lines below can be used to present another version of the hair
  //arc(290, 140, 60, 25, 0, PI);
  //arc(350, 140, 60, 25, 0, PI);
}

void eyes() {
  stroke(0);
  noFill();
  strokeWeight(2);
  arc(290, 170, 25, 10, PI, PI*2);
  arc(350, 170, 25, 10, PI, PI*2);
  strokeWeight(1);
  ellipse(290, 178, 25, 10);
  ellipse(350, 178, 25, 10);
  fill(0);
  circle(290, 178, 5);
  circle(350, 178, 5);
}

void lips() {
  noFill();
  arc(320, 230, 32, 15, 0, PI);
  //line(300, 230, 340, 230);
  //arc(320, 230, 32, 10, PI, 2*PI);
  fill(220, 133, 146);
  arc(320, 230, 32, 48, PI, 2*PI);
  fill(247, 194, 168);
  arc(320, 230, 32, 10, PI, 2*PI);
}

void body() {
  noStroke();
  fill(247, 194, 168);
  rect(300, 262, 40, 40);
  fill(0, 255, 255);
}