Final Project

Main Concept

For the final project, I created a California themed car game. The physical hardware consists of a box with an arduino installed inside along with an ultrasonic sensor. There is a long road panel in front of it where the player places their NYU card keys and slides it along the road lanes to mimic the movement of a car on the road. The ultrasonic sensor detects the motion of the card along the lanes and causes the actual car in p5.js to move as well. On the screen, I decided to design the UI on a californian theme so I went with pictures and elements which depicted that concept. When the game begins, there are cars approaching the player from each lane and the player needs to move their key on the physical road to change the lane of the car on screen. With time the car speed increases and becomes more difficult. As it is a high score based game, every incoming car that is avoided is counted as one point so the ending page shows the high score and individual player score as well. As an indication of when the game has started, the green LED lights up on the box labeled “gas station” and similarly when the car crashes, red LED lights up or yellow goes off when the player is reading instructions, almost to imitate real life traffic lights. 

P5.js

In the p5.js code, I have created a game using different classes. There is the player class which deals with the movement of the player by receiving values from arduino and then mapping it according to the canvas and player position. Then there is a class for the incoming cars, which deals with the downward motion of the cars and is also passed as an argument to the player class to check if the x or y coordinates of the player coincide with those of the incoming cars, in which case the game ends as the cars crash. I created an array for the incoming cars so that they are added at a certain frameRate count and are removed from the array as soon as they go out of the canvas.

//variable declarations


let h = 600;
let w = 400;
let count = 0;
let pos = [55, 165, 270];
let charsize = 80 ; //defines size of player
let charheight = 100
let player; //variable for the player class object
let newcarssize = 100; 
let newcarswidth = 80;
let newcars = [];
let score=0;
let highscore=0;
let bonusarr = []; //array to store bonus objects
let caught = false; 
let bonuscaught = []; //
let scorebool = false;
let newchar = false; //boolean variable to help in creating new character when game starts again
let foodnum = 0; //to iterate over food array
let speedincr = 1; //for the vertical motion of newcarss
let carimg = 0;
let carspeeds = 2;

let r1=0;
let r2 =-600;
let r3 = 0;

let food = []; //array for food images

let gamestate = 'start'; //gamestate for changes in display
framewidth = 20; 

let a = 150;
let b = 300;
let c= 450;
let d = 550;

//image variables
var gif_loadImg, gif_createImg;
let pixelfont;
let startimg;
let startbutton;
let instructbutton;
let instructpage;
let endimg;
let road1, road2, road3;
let car1, car2, car3, car4, car5;
let cars = [];
let endsound, crash, backsound;
let tree;

//arduino variables
let rVal = 0;


//preload function for all the images and sound
function preload() {
    startimg = loadImage('2.png');
    startbutton = loadImage('startbutton.png');
    instructpage = loadImage('3.png');
    instructbutton = loadImage('instruct.png');
    endimg = loadImage('1.png');
    road1 = loadImage('road.png');
    road2 = loadImage('road.png');
    road3 = loadImage('road.png');
    car1 = loadImage('car1.png');
    car2 = loadImage('car2.png');
    car3 = loadImage('car3.png');
    car4 = loadImage('car4.png');
    car5 = loadImage('car5.png');
    tree = loadImage('tree.png');
  
    endsound = loadSound('endsound.mp3');
    crash = loadSound('crash.mp3');
    backsound = loadSound('background.mp3');
    

}




//====================================================
//class for the player object
class Character{
  constructor(){
    this.x = 5+ width/2-(charsize/2);
    this.y = height - ((charsize));
    this.speed = 4; //speed for the movement of player horizontally

  }
  
  display(){

    image(car2, this.x, this.y-10, charsize, charheight ); 


  }
  moveleft(){ //if left key is pressed, causes player to move left
    this.x -= this.speed;

    if (this.x < 0) {
      this.x = 0; //so player does not go beyond the canvas
    }
  }
  moveright(){ //if right key is pressed, causes player to move left
    this.x+=this.speed;

    if (this.x+charsize > width) {
      this.x = width-charsize; //so player does not go beyond the canvas
    }
  }
  update(newcars){
    
    if(rVal < 7){
      this.x = 55;
    }
    else if(rVal>=7 && rVal<14 ){
      this.x = 165;
    }
    else if(rVal >= 14){
      this.x = 270;
    }
    

    


        if(this.x>=newcars.x && this.x <= newcars.x+(newcars.pwidth) && this.y>=newcars.y &&  this.y<=newcars.y+ newcars.pheight){
          count++;

      console.log("Loss:" , count);
          crash.play();
          gamestate = 'over';
        

    }
    else if(this.x+charsize >=newcars.x && this.x+charsize <= newcars.x+(newcars.pwidth) && this.y>=newcars.y &&  this.y<=newcars.y+ newcars.pheight){
      count++;

      console.log("Loss:" , count);
      crash.play();
      gamestate = 'over';
            
            }
    
    else if(this.x>=newcars.x && this.x <= newcars.x+(newcars.pwidth) && this.y+charheight>=newcars.y &&  this.y+charheight<=newcars.y+newcars.pheight){
      
          count++;
      crash.play();

          console.log("Loss:" , count);
      gamestate = 'over';
            }

    

  }





}
//====================================================
//class to create newcarss
class incars{
  constructor(carimg, carspeeds){
    this.x = random(pos);
    this.y = 0;
    this.pwidth = newcarswidth;
    this.pheight = newcarssize;
    this.speed = carspeeds;
    this.num = carimg
    
  }
  
  displaynewcars(){
 
     image(cars[this.num], this.x, this.y , this.pwidth, this.pheight);
  }


  //causes newcars to move upwards
  update(){
    this.y += this.speed;

  }
  
  
}


function setup() {
  
  createCanvas(w,h);
  
  background(220);
  player = new Character();
  cars = [car1, car2, car3, car4, car5];





}

function draw() {
  if(frameCount == 1){
    backsound.play();
  }
  background(220);
   if (!serialActive) 
  {
    text("Press Space Bar to select Serial Port", 20, 30);
  } else 
  {

  }



  
  if(gamestate == 'start'){ //if game is at start page then display homepage


    startpage();
    



 
    
  }
  //if gamestate is of 'game' then plays the game through thegame() function
  else if(gamestate == 'game'){
        image(road1,  0,r1, width, height, 0, 0, road1.width, road1.height); //homepage image displayed
    image(road1, 0,r2,  width, height, 0, 0, road1.width, road1.height);
    
    r1+=2;
    r2+=2;

    if(r1 >= height){
      r1 = r2- height ;
    }
    if(r2>= height){
      r2 = r1-height ;
    }

 
    
    
    
  thegame(); //plays game through this function
    a++;
    b++;
    c++;
    d++;
    
       image(tree,  -75,a, 150, 150, 0, 0, tree.width, tree.height);
        image(tree,  -75,b, 150, 150, 0, 0, tree.width, tree.height);
            image(tree,  width-60,c, 150, 150, 0, 0, tree.width, tree.height);
        image(tree,  width-60,d, 150, 150, 0, 0, tree.width, tree.height);
    
    if(a > height+150){
      a = -150;
    }
    else if(b> height+150){
      b = -150;
    }
    else if(c> height+150){
      c = -150;
    }
        else if(d> height+150){
      d = -150;
    }
    

    


    


  }
  
  else if(gamestate == 'over'){ //if gamestate is over then end page displayed 


    image(endimg, 0, 0, width, height, 0, 0, endimg.width, endimg.height);

  endpage();
   
    
  }
  else if(gamestate == 'instructions'){ //displays instructions 
    instructionspage();

  }
}
//========================
function thegame(){ 

 
   //checks if game is started again and creates a new player to play the game again
    if(newchar == true){
     player = new Character();
    newchar = false;
  }
  
  
  


  //for keys to move the player
  if(keyIsDown(LEFT_ARROW)){
    player.moveleft();
  }
  else if (keyIsDown(RIGHT_ARROW)){
    player.moveright();
  }
  


  
  //for new newcars and elements to be created and added to their arrays
  if(frameCount%150 == 0){

    newcars.push(new incars(carimg, carspeeds));

    score++;
    console.log(score);
    carimg++;
    if(carimg>4){
      carimg = 0;
    }
    
 
  }


  
  //FOR LOOP
  for(let i = 0 ; i<newcars.length ; i++){
    //shows newcarss and updates their positions by decreasing y coordinated to move them up
    newcars[i].displaynewcars();
    
    newcars[i].update();
     
    

    

    //when newcars and food goes beyond the roof, they are removed from their arrays 
    if (newcars[i].y > height+400 ){
      newcars.splice(i, 1);
      // bonusarr.splice(i, 1);
      // bonuscaught.splice(i,1);
 

    }
    
    //Player's position checked against newcars and ladder's position
    player.update(newcars[i]);

    newcars[i].update();//moves it up
    
    
  }
  
  player.display(); 
  
  //END of FOR
  
  
  


  
}

//==============================

function startpage(){ //function to display the images of starting page
    image(startimg, 0, 0, width, height, 0, 0, startimg.width, startimg.height);
  


    imageMode(CENTER);
      image(startbutton, width/2, height/2, map(startbutton.width, 0, startbutton.width, 0, width/4), map(startbutton.height, 0, startbutton.height, 0, height/6));

    imageMode(CORNER);

    //checks if mouse pressed on the button and directs to instructions
    if(mouseX < width/2 + (width/(width/100)/2) && mouseX > width/2 - (width/(width/100)/2) && mouseY > height/2 - (height/(height/100)/2) && mouseY < height/2 + (height/(height/100)/2) && mouseIsPressed){
      gamestate = 'instructions';
      // success.play();
    }
  
}



function endpage(){ //function to display score and highscore and 'game over' page


  for(let i = 0 ; i<newcars.length ; i++){
      newcars.pop();
      bonusarr.pop();
      bonuscaught.pop();
  }
  newchar = true;
  
  if(score>highscore){ //updates the highscore with each previous score 
    highscore = score;
  }

  textAlign(CENTER);
  textFont('Times New Roman');
  fill("#FAF8F4");
  textSize(0.05*width);
  text('Score: '+score , width/2, (height/2)-58);
  text('High Score: '+highscore , width/2, ((height/2))-40);

  
  if(keyIsDown(ENTER)){ //when ENTER is pressed, gamestate changed so that game starts again
    gamestate = 'start';
    score= 0 ;
  }

}

function instructionspage(){ //displays instruction images 
  
  image(instructpage, 0, 0, width, height, 0, 0, instructpage.width, instructpage.height);
  

  image(instructbutton, (2.3*width)/4, (3.3*height)/3.7, map(instructbutton.width, 0, instructbutton.width, 0, 150), map(instructbutton.height, 0, instructbutton.height, 0, 50));
  
  //checks if mouse clicked on the button and continues to game state of playing the game
  if(mouseX>(2.3*width)/4 && mouseX < width && mouseY > (3.3*height)/3.7 && mouseY < height && mouseIsPressed){
    gamestate = 'game';
    //success.play();
  }

  

}


//====================
//ARDUINO



function keyPressed() {
  if (key == " ") 
  {
    // important to have in order to start the serial connection!!
    setUpSerial();
  }
}
function mouseIsPressed()
{
  readSerial();
}

// This function will be called by the web-serial library
// with each new line of data. The serial library reads
// the data until the newline and then gives it to us through
// this callback function
function readSerial(data) {
  ////////////////////////////////////
  //READ FROM ARDUINO HERE
  ////////////////////////////////////

  if (data != null) 
  {
    rVal = data;
  }
  
  //=============
  let sendToArduino=0;
  if(gamestate == 'game'){
    sendToArduino = 1;
  }
    else if(gamestate == 'instructions' || gamestate == 'start'){
    sendToArduino = 2;
  }
      else if(gamestate == 'over'){
    sendToArduino = 3;
  }
  sendToArduino = sendToArduino+"\n";
    
    writeSerial(sendToArduino);
}


 

Arduino

In arduino, the main component is the ultrasonic sensor and then the LEDs. I have set the trig and echo pins as well as the LED pins initially. The connection between arduino and p5.js is in sending data from the ultrasonic sensor when the card is moved in front of it. The data is read by p5.js and is then used to control the x-coordinate of the player car. The p5.js sends the data regarding the game states which controls the LED pins. The data is sent as an integer, each indicating the game state, and is then used to control the respective LEDs. I soldered the LEDs so I could fix it on top of the box. 

 

I was facing an issue with consistent readings from the ultrasonic sensor. As it sends out signals in a more spread out manner and reads very minor changes, the car was glitching because slightest tilt of the card was also causing the ultrasonic sensor to change values. I had to add a bit more delay to make sure the card movement has stabilized before the sensor reads its distance.

const int trigPin = 9;
const int echoPin = 10;
const int ledgreen = 2;
const int ledyellow= 4;
const int ledred = 7;
// defines variables
long duration;
int distance;
void setup() {
  pinMode(trigPin, OUTPUT); // Sets the trigPin as an Output
  pinMode(echoPin, INPUT); // Sets the echoPin as an Input
  Serial.begin(9600); // Starts the serial communication
  pinMode(ledgreen, OUTPUT);
  pinMode(ledyellow, OUTPUT);
  pinMode(ledred, OUTPUT);
}
void loop() {
  // Clears the trigPin
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  // Sets the trigPin on HIGH state for 10 micro seconds
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  // Reads the echoPin, returns the sound wave travel time in microseconds
  duration = pulseIn(echoPin, HIGH);
  // Calculating the distance
  distance = duration * 0.034 / 2;
  // Prints the distance on the Serial Monitor
  //Serial.print("Distance: ");
  Serial.println(distance);
  
  //Serial.write(distance);


    int brightness = Serial.parseInt();
    if(Serial.read() == '\n')
{
      //Serial.println(brightness);
      if(brightness == 1){
        digitalWrite(ledgreen, HIGH);
        digitalWrite(ledyellow, LOW);
        digitalWrite(ledred, LOW);
      }
      else if(brightness == 2){
        digitalWrite(ledyellow, HIGH);
        digitalWrite(ledgreen, LOW);
        digitalWrite(ledred, LOW);
      }
      else if(brightness == 3){
        digitalWrite(ledred, HIGH);
        digitalWrite(ledgreen, LOW);
        digitalWrite(ledyellow, LOW);
      }
    
    
    }
    delay(300);
    //analogWrite(ledgreen, brightness);
  }

 

Future improvements

I feel like there is a lot that could still be improved. I think the first thing would be to add vertical motion to the car, I was unable to implement it because the ultrasonic sensor detects horizontal distance changes only so it was a bit difficult to manage both the axes. Another factor would be to change the sensor to infrared which would solve the problem of inconsistent readings because infrared sensors send signals in a direct line while ultrasonic sensors send them spread out so the accuracy of readings would be improved. I would also like to add button to the Arduino such as buttons to turn the music on or off and also for the port selection, which I was unable to implement as well.

 

Leave a Reply