Final Project: Mini-Tesla car

Description:

For my project, I am building a mini-Tesla car that will feature both autopilot mode and manual mode. When the autopilot mode is enabled, the car will be able to move around and avoid all obstacles. Whereas for the manual mode, the user will be able to control the car through processing using the arrows/buttons.

Components:

I am using the Adafruit motor shield to control the wheels and add movement to the car. To use it correctly, I had to download a new library called “Adafruit Motor Shield V2 Library”, and a couple of other sub-libraries that come with it.

#include <Adafruit_MotorShield.h>
 
Adafruit_MotorShield AFMS(0x61);

Adafruit_DCMotor *rb = AFMS.getMotor(3);
Adafruit_DCMotor *lf = AFMS.getMotor(2);
Adafruit_DCMotor *lb = AFMS.getMotor(1);
Adafruit_DCMotor *rf = AFMS.getMotor(4);

I am also using a Servo and an Ultrasonic Sensor to detect obstacles around the car.

Setup:

To begin with, in my setup function, I am setting the pin modes and the motors’ speed, then ensuring that all motors are stopped.

void setup(){
  AFMS.begin();
  Serial.begin(9600);
  Serial.println("0,0");
  myservo.attach(3);
  pinMode(4,OUTPUT);
  pinMode(5,INPUT);
  rb->setSpeed(car_speed);
  lb->setSpeed(car_speed);
  rf->setSpeed(car_speed);
  lf->setSpeed(car_speed);
  delay(1500);
  rb->run(RELEASE);
  lb->run(RELEASE);
  rf->run(RELEASE);
  lf->run(RELEASE);
}
Autopilot mode:
Measuring the distance:

At first, I am setting my servo to look ahead, then using an ultrasonic I am measuring the distance to the next obstacle (basically to check if there are objects ahead).

myservo.write(90);
// calculate the distance ahead
digitalWrite(4, LOW);
delayMicroseconds(2);
digitalWrite(4, HIGH);
delayMicroseconds(5);
digitalWrite(4, LOW);
duration = pulseIn(5, HIGH);
dist = duration/29/2;

Then, using a while() loop, I keep measuring the distance while moving forward, until the obstacle is within the minimum stopping distance.

while (dist >= min_dist){
  rb->run(FORWARD);
  lb->run(FORWARD);
  rf->run(FORWARD);
  lf->run(FORWARD);
  digitalWrite(4, LOW);
  delayMicroseconds(2);
  digitalWrite(4, HIGH);
  delayMicroseconds(5);
  digitalWrite(4, LOW);
  duration = pulseIn(5, HIGH);
  dist = duration/29/2;
}

If the car is close enough to the obstacle, the wheels are stopped, the servo checks both the right and left directions, and using the ultrasonic, we get both distances.

int rdist = 0;
int ldist = 0;

// calculate right distance
myservo.write(0);
delay(1500);
pinMode(4, OUTPUT);
digitalWrite(4, LOW);
delayMicroseconds(2);
digitalWrite(4, HIGH);
delayMicroseconds(5);
digitalWrite(4, LOW);
pinMode(5, INPUT);
duration = pulseIn(5, HIGH);
rdist = duration/29/2;

// calculate left distance
myservo.write(180);
delay(1500);
pinMode(4, OUTPUT);
digitalWrite(4, LOW);
delayMicroseconds(2);
digitalWrite(4, HIGH);
delayMicroseconds(5);
digitalWrite(4, LOW);
pinMode(5, INPUT);
duration = pulseIn(5, HIGH);
ldist = duration/29/2;

Here, we come across 4 scenarios:

1st scenario: 

If both directions are blocked (distance is below the minimum stopping distance), the car does a U-turn. To do it, the car keeps turning to the right until it reaches 180 degrees (using Delay).

// if both directions are blocked, do a u-turn
if (rdist<= min_dist && ldist<= min_dist) {
  rb->setSpeed(255);
  lb->setSpeed(255);
  rf->setSpeed(255);
  lf->setSpeed(255);
  rb->run(BACKWARD);
  lb->run(FORWARD);
  rf->run(BACKWARD);
  lf->run(FORWARD);
  delay(1400);
  rb->setSpeed(60);
  lb->setSpeed(60);
  rf->setSpeed(60);
  lf->setSpeed(60);
  rb->run(RELEASE);
  lb->run(RELEASE);
  rf->run(RELEASE);
  lf->run(RELEASE);
}

2nd scenario:

If both directions are free (distance is much higher than the minimum stopping distance), the car chooses the right direction. 🙂

// if both directions are free, choose right
else if (rdist>=20 && ldist>=20){
  rb->setSpeed(255);
  lb->setSpeed(255);
  rf->setSpeed(255);
  lf->setSpeed(255);
  rb->run(BACKWARD);
  lb->run(FORWARD);
  rf->run(BACKWARD);
  lf->run(FORWARD);
  delay(700);
  rb->setSpeed(60);
  lb->setSpeed(60);
  rf->setSpeed(60);
  lf->setSpeed(60);
  rb->run(RELEASE);
  lb->run(RELEASE);
  rf->run(RELEASE);
  lf->run(RELEASE);
}

3rd scenario:

If the right direction is free, but the left is blocked, choose the right direction.

// if right way is free, choose right
else if (rdist>=ldist){
  rb->setSpeed(255);
  lb->setSpeed(255);
  rf->setSpeed(255);
  lf->setSpeed(255);
  rb->run(BACKWARD);
  lb->run(FORWARD);
  rf->run(BACKWARD);
  lf->run(FORWARD);
  delay(700);
  rb->setSpeed(60);
  lb->setSpeed(60);
  rf->setSpeed(60);
  lf->setSpeed(60);
  rb->run(RELEASE);
  lb->run(RELEASE);
  rf->run(RELEASE);
  lf->run(RELEASE);
}

4th scenario:

If the left direction is free, but the right is blocked, choose the left direction.

// if left way is free, choose left
else if (rdist<ldist){
  rb->setSpeed(255);
  lb->setSpeed(255);
  rf->setSpeed(255);
  lf->setSpeed(255);
  rb->run(FORWARD);
  lb->run(BACKWARD);
  rf->run(FORWARD);
  lf->run(BACKWARD);
  delay(700);
  rb->setSpeed(60);
  lb->setSpeed(60);
  rf->setSpeed(60);
  lf->setSpeed(60);
  rb->run(RELEASE);
  lb->run(RELEASE);
  rf->run(RELEASE);
  lf->run(RELEASE);
}

View post on imgur.com

Manual mode:

When enabled, the user can control the car through Processing using either the arrows or the screen buttons. When an action is specified, Processing sends the data to Arduino.

To receive data from Processing, I am doing the following:

while (Serial.available()) {
  if (Serial.read() == '\n') {
    state = Serial.parseInt();
    direc = Serial.parseInt();
  }
}

To control the car:

else if (autopilot==false) {
  // forward
  if (direc == 0) {
    rb->setSpeed(60);
    lb->setSpeed(60);
    rf->setSpeed(60);
    lf->setSpeed(60);
    rb->run(FORWARD);
    lb->run(FORWARD);
    rf->run(FORWARD);
    lf->run(FORWARD);
    delay(200);
    rb->run(RELEASE);
    lb->run(RELEASE);
    rf->run(RELEASE);
    lf->run(RELEASE);
    
  }
  // right
  if (direc == 1) {
    rb->setSpeed(255);
    lb->setSpeed(255);
    rf->setSpeed(255);
    lf->setSpeed(255);
    rb->run(BACKWARD);
    lb->run(FORWARD);
    rf->run(BACKWARD);
    lf->run(FORWARD);
    delay(100);
    rb->setSpeed(60);
    lb->setSpeed(60);
    rf->setSpeed(60);
    lf->setSpeed(60);
    rb->run(RELEASE);
    lb->run(RELEASE);
    rf->run(RELEASE);
    lf->run(RELEASE);
  }
  // backward
  if (direc == 2) {
    rb->run(BACKWARD);
    lb->run(BACKWARD);
    rf->run(BACKWARD);
    lf->run(BACKWARD);
    delay(200);
    rb->run(RELEASE);
    lb->run(RELEASE);
    rf->run(RELEASE);
    lf->run(RELEASE);
  }
  // left
  if (direc == 3) {
    rb->setSpeed(255);
    lb->setSpeed(255);
    rf->setSpeed(255);
    lf->setSpeed(255);
    rb->run(FORWARD);
    lb->run(BACKWARD);
    rf->run(FORWARD);
    lf->run(BACKWARD);
    delay(100);
    rb->setSpeed(60);
    lb->setSpeed(60);
    rf->setSpeed(60);
    lf->setSpeed(60);
    rb->run(RELEASE);
    lb->run(RELEASE);
    rf->run(RELEASE);
    lf->run(RELEASE);
  }
  // stop
  if (direc == 4) {
    rb->run(RELEASE);
    lb->run(RELEASE);
    rf->run(RELEASE);
    lf->run(RELEASE);
  }
}
Processing:
import processing.serial.*;
Serial myPort;  
PImage img;

void setup() { 
  size(960,720);
  printArray(Serial.list());
  String portname=Serial.list()[1];
  println(portname);
  myPort = new Serial(this,portname,9600);
  myPort.clear();
  myPort.bufferUntil('\n');
  img = loadImage("bg.png");
}

void draw() {
  background(#e9eaea);
  image(img, width*1/2-135, height/2-125,270,250);
  fill(#555555);
  textSize(30);
  text("Use the arrows to control the car.", width*1/2-200, height/2+ 145);
  text("Click on S to switch to stop the car.", width*1/2-210, height/2+ 195);
  text("Click on A to switch to autopilot mode.", width*1/2-225, height/2+ 245);
  if(keyPressed) { 
    //if a key is pressed, do the following
    if (key == 'm'){     
      myPort.write(0+","+9+"\n");
    }
    else if (keyCode == UP){     
      myPort.write(0+","+0+"\n"); 
    }
    else if (keyCode == DOWN){     
      myPort.write(0+","+2+"\n");  
    }
    else if (keyCode == RIGHT){     
      myPort.write(0+","+1+"\n");  
    }
    else if (keyCode == LEFT){     
      myPort.write(0+","+3+"\n");  
    }
    else if (key == 's'){     
      myPort.write(0+","+4+"\n");
    }
    else if (key == 'a'){     
      myPort.write(1+","+0+"\n");
    }
    else {
      myPort.write(4+","+9+"\n");
    }
  }
  else {
    myPort.write(4+","+9+"\n");
  }
}
IM Showcase:

Here are a couple of videos from the IM showcase:

View post on imgur.com

View post on imgur.com

 

Final project progress: Mini-Tesla car

Description:

For my project, I am building a mini-Tesla car that will feature both autopilot mode and manual mode. When the autopilot mode is enabled, the car will be able to move around and avoid all obstacles. Whereas for the manual mode, the user will be able to control the car through processing using the arrows/buttons.

Progress:
Autopilot mode:

Instead of the L298N motor driver shield, I am using the Adafruit motor shield (due to its unavailability) to control the wheels and add movement to the car. To use it correctly, I had to download a new library called “Adafruit Motor Shield V2 Library”, and a couple of other sub-libraries that come with it. 

#include <Adafruit_MotorShield.h>

Adafruit_MotorShield AFMS(0x61);

Adafruit_DCMotor *rb = AFMS.getMotor(3);
Adafruit_DCMotor *lb = AFMS.getMotor(2);
Adafruit_DCMotor *rf = AFMS.getMotor(1);
Adafruit_DCMotor *lf = AFMS.getMotor(4);
Calculate the distance:

For that, I am using pulseIn() which starts the timing when the pin goes to HIGH then stops it when it goes to LOW. It works on pulses higher than 10 microseconds so I had to use a delay there.

// calculate the distance ahead
digitalWrite(4, HIGH);
delayMicroseconds(10);
digitalWrite(4, LOW);
dist = pulseIn(5, HIGH, 10000) * 340 / 10000 / 2;
Movement:

If all directions are blocked, do a U-turn:

To do that, I am alternating BACKWARD and FORWARD between the 4 wheels.

Right back wheel: Backward

Left back wheel: Forward

Right front wheel: Backward

Left front wheel: Forward

I have also added speed for the turn, then decreased it back at the end.

The delay is quite high 700 microseconds, to leave them time to make the whole U-turn.

if (rdist<= min_dist && ldist<= min_dist) {
      rb->setSpeed(80);
      lb->setSpeed(80);
      rf->setSpeed(80);
      lf->setSpeed(80);
      rb->run(BACKWARD);
      lb->run(FORWARD);
      rf->run(BACKWARD);
      lf->run(FORWARD);
      delay(700);
      rb->setSpeed(40);
      lb->setSpeed(40);
      rf->setSpeed(40);
      lf->setSpeed(40);
      rb->run(RELEASE);
      lb->run(RELEASE);
      rf->run(RELEASE);
      lf->run(RELEASE);
    }

If both directions are free, choose right:

The delay is half the one used for a U-turn.

// if both directions are free, choose right
    else if (rdist>=220 && ldist>=220){
      rb->setSpeed(80);
      lb->setSpeed(80);
      rf->setSpeed(80);
      lf->setSpeed(80);
      rb->run(BACKWARD);
      lb->run(FORWARD);
      rf->run(BACKWARD);
      lf->run(FORWARD);
      delay(400);
      rb->setSpeed(40);
      lb->setSpeed(40);
      rf->setSpeed(40);
      lf->setSpeed(40);
      rb->run(RELEASE);
      lb->run(RELEASE);
      rf->run(RELEASE);
      lf->run(RELEASE);
    }

If the right way is free, choose right:

// if right way is free, choose right
    else if (rdist>=ldist){
      rb->setSpeed(80);
      lb->setSpeed(80);
      rf->setSpeed(80);
      lf->setSpeed(80);
      rb->run(BACKWARD);
      lb->run(FORWARD);
      rf->run(BACKWARD);
      lf->run(FORWARD);
      delay(400);
      rb->setSpeed(40);
      lb->setSpeed(40);
      rf->setSpeed(40);
      lf->setSpeed(40);
      rb->run(RELEASE);
      lb->run(RELEASE);
      rf->run(RELEASE);
      lf->run(RELEASE);
    }

If the left way is free, choose left:

// if left way is free, choose left
    else if (rdist>=ldist){
      rb->setSpeed(80);
      lb->setSpeed(80);
      rf->setSpeed(80);
      lf->setSpeed(80);
      rb->run(FORWARD);
      lb->run(BACKWARD);
      rf->run(FORWARD);
      lf->run(BACKWARD);
      delay(400);
      rb->setSpeed(40);
      lb->setSpeed(40);
      rf->setSpeed(40);
      lf->setSpeed(40);
      rb->run(RELEASE);
      lb->run(RELEASE);
      rf->run(RELEASE);
      lf->run(RELEASE);
    }
Manual mode:

When enabled, the user can control the car through Processing using either the arrows or the screen buttons. When an action is specified, Processing sends the data to Arduino.

else {
    // forward
    if (direc == 0) {
      rb->run(FORWARD);
      lb->run(FORWARD);
      rf->run(FORWARD);
      lf->run(FORWARD);
    }
    // right
    if (direc == 1) {
      rb->setSpeed(80);
      lb->setSpeed(80);
      rf->setSpeed(80);
      lf->setSpeed(80);
      rb->run(BACKWARD);
      lb->run(FORWARD);
      rf->run(BACKWARD);
      lf->run(FORWARD);
      delay(400);
      rb->setSpeed(40);
      lb->setSpeed(40);
      rf->setSpeed(40);
      lf->setSpeed(40);
      rb->run(RELEASE);
      lb->run(RELEASE);
      rf->run(RELEASE);
      lf->run(RELEASE);
    }
    // backward
    if (direc == 2) {
      rb->run(BACKWARD);
      lb->run(BACKWARD);
      rf->run(BACKWARD);
      lf->run(BACKWARD);
    }
    // left
    if (direc == 3) {
      rb->setSpeed(80);
      lb->setSpeed(80);
      rf->setSpeed(80);
      lf->setSpeed(80);
      rb->run(FORWARD);
      lb->run(BACKWARD);
      rf->run(FORWARD);
      lf->run(BACKWARD);
      delay(400);
      rb->setSpeed(40);
      lb->setSpeed(40);
      rf->setSpeed(40);
      lf->setSpeed(40);
      rb->run(RELEASE);
      lb->run(RELEASE);
      rf->run(RELEASE);
      lf->run(RELEASE);
    }
    // stop
    if (direc == 4) {
      rb->run(RELEASE);
      lb->run(RELEASE);
      rf->run(RELEASE);
      lf->run(RELEASE);
    }
}
Processing:
import processing.serial.*;
Serial myPort;  
PImage img;

void setup() { 
  size(960,720);
  printArray(Serial.list());
  String portname=Serial.list()[1];
  println(portname);
  myPort = new Serial(this,portname,9600);
  myPort.clear();
  myPort.bufferUntil('\n');
  img = loadImage("bg.png");
}

void draw() {
  background(#e9eaea);
  image(img, width*1/2-135, height/2-125,270,250);
  fill(#555555);
  textSize(30);
  text("Use the arrows to control the car.", width*1/2-200, height/2+ 145);
  text("Click on S to switch to stop the car.", width*1/2-210, height/2+ 195);
  text("Click on A to switch to autopilot mode.", width*1/2-225, height/2+ 245);
  if(keyPressed) {                                                                                            //if a key is pressed, do the following
    if (keyCode == UP){     
      myPort.write(0+","+0+"\n"); 
    }
    else if (keyCode == DOWN){     
      myPort.write(0+","+2+"\n");  
    }
    else if (keyCode == RIGHT){     
      myPort.write(0+","+1+"\n");  
    }
    else if (keyCode == LEFT){     
      myPort.write(0+","+3+"\n");  
    }
    else if (key == 's'){     
      myPort.write(0+","+4+"\n");
    }
    else if (key == 'a'){     
      myPort.write(1+","+0+"\n");
    }
    else {
      myPort.write(4+","+9+"\n");
    }
  }
  else {
    myPort.write(4+","+9+"\n");
  }
}

 

View post on imgur.com

View post on imgur.com

Hardest parts:

One of the challenges I have faced was using the motor shield, as some of the parts were soldered (between the two right pads), which affected the address of the shield. So I had to either fix it through code or remove the soldering. One more issue was related to the VIN jumper because for some time there was no power in the shield, it might be because the 5v pad was also soldered.

User testing:

View post on imgur.com

View post on imgur.com

Comments from user:
  • Add more speed (Answer: I think it would be a bad idea because it will lead to the car hitting obstacles)
  • Add sound ( Answer: I will look into adding a buzzer if it is not going to affect the circuit because the motors require too much voltage)
  • Add lights that automatically turn on when it’s dark ( Answer: I will implement that using LDRs)

Extra-credit assignment: Flappy Bird

Description:

For this assignment, I tried to recreate an Arduino version of flappy bird using pushbuttons as primary controllers.  The main idea is that the avatar (bird), which continuously moves to the right, has to move through the equally sized gaps (between the pairs of obstacles aka pipes) that are placed at random heights. The bird is affected by gravity and is always pulled down to the bottom, however, the push button allows it to make a sort of jump in the air and therefore move a bit to the top. After every successful passing through the gap, the score is incremented by one. When the avatar hits the obstacle, the game ends, the score is displayed and the user can restart the game by clicking on the second push button.

Process:
Obstacles:

To generate obstacles I have used a class that includes three parameters (float xcoord for the x-coordinate, float first and second for the y-coordinates of the obstacles) and a couple of functions. Generate() generates random values for the y-coordinate of the first obstacle, then adds 140 (for the gap) and gets the y-coordinate of the second obstacle. Create() draws the rectangles on the display window, and whenever the obstacle goes out of the screen, it gets generated again on the other side with different y-coordinates.

class Obstacle {

  float xcoord; // x coordinate
  float first = random(150,450); // top
  float second = first+140; // bottom
  
  void generate(){
    first = random(150,450);
    second = first+140;
  }
 
  void create(){
    fill(#4caf50);
    noStroke();
    rect(xcoord, 0, 80, first);
    rect(xcoord, second, 80, height);
    fill(#9bf09b);
    rect(xcoord+5, 0, 80-10, first-5);
    rect(xcoord+5, second+5, 80-10, height);
   
    if(xcoord < -80) { // if out of screen
      xcoord = width+230;
      generate();
      score++;
    }
  }
}
Movement:

Instead of making the bird move inside the display window, I used an infinite side-scrolling in which the character is static whereas the background moves from the right to the left. 

To add movement to the obstacles, I am decreasing their x-coordinate by a constant amount (speed) inside the draw() loop.

Obstacle obstacle1= new Obstacle(); // generate first obstacle
Obstacle obstacle2= new Obstacle(); // generate second obstacle
Obstacle obstacle3= new Obstacle(); // generate third obstacle
Obstacle obstacle4= new Obstacle(); // generate fourth obstacle

  obstacle1.xcoord=width+(width/3)/2-40; // x coordinate of the first obstacle
  obstacle2.xcoord=width+((width/3)/2-40)+(width/3);  // x coordinate of the second obstacle
  obstacle3.xcoord=width+((width/3)/2-40)+(width/3)*2;  // x coordinate of the third obstacle
  obstacle4.xcoord=width+((width/3)/2-40)+(width/3)*3;  

// create all obstacles
    obstacle1.create();
    obstacle2.create();
    obstacle3.create();
    obstacle4.create();
    // move obstacles
    obstacle1.xcoord -= speed;
    obstacle2.xcoord -= speed;
    obstacle3.xcoord -= speed;
    obstacle4.xcoord -= speed;
Collisions:

To flag when the avatar touches one of the obstacles, I first used an ellipse to limit the area of the avatar, then checked if it overlapped with the obstacle. Meaning that the x and y coordinates of the avatar would be inside the obstacle area.

// check if avatar touches the obstacles
  if((avatar_ycoord-25<=obstacle1.first && obstacle1.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle1.second&& obstacle1.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  if((avatar_ycoord-25<=obstacle2.first && obstacle2.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle2.second && obstacle2.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  if((avatar_ycoord-25<=obstacle3.first && obstacle3.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle3.second && obstacle3.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  if((avatar_ycoord-25<=obstacle4.first && obstacle4.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle4.second && obstacle4.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  // check if avatar goes out of display window
  if(avatar_ycoord>height || avatar_ycoord<0){
    stop=true;
  }
Gravity:

The gravity is set to 3 and pulls the avatar down constantly. But when the user clicks on the button, the avatar makes a quick jump and its y-coordinate is affected.

Arduino:

On the Arduino ide, I am reading the state of the buttons, and sending them to processing using Serial.println().

int inPin = 3;  // green pushbutton
int inPin2 = 4; // yellow pushbutton
int val = 0;
int val2 = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("0,0");
}
void loop() {
  while (Serial.available()) {
    if (Serial.read() == '\n') {
       val = digitalRead(inPin); // green push button state
       val2 = digitalRead(inPin2); // yellow push button state
       // send the state of the push button to processing
       if (val == HIGH && val2== HIGH) {         
          Serial.println("1,1");
       }
       else if (val == HIGH && val2== LOW) {         
          Serial.println("1,0");
       }
       else if (val == LOW && val2== LOW) {         
          Serial.println("0,0");
       }
       else if (val == LOW && val2== HIGH) {    
          Serial.println("0,1");
       }
    }
  }
}
End game: 

When a collision occurs, or the avatar goes out of the screen, the game ends.

Restart game: 

To restart the game, the user has to click on the second push button.

View post on imgur.com

Code:
Processing:
import processing.serial.*;
Serial myPort;
PImage img; // avatar img

int speed = 3; // speed of game
int avatar_xcoord = 70; // avatar's x coordinate
int avatar_ycoord = 0;  // avatar's y coordinate
int gravity = 3; // gravity effect
boolean stop = false; // boolean var to end game
int score; // score
boolean clicked= false; // boolean var to restard game

Obstacle obstacle1= new Obstacle(); // generate first obstacle
Obstacle obstacle2= new Obstacle(); // generate second obstacle
Obstacle obstacle3= new Obstacle(); // generate third obstacle
Obstacle obstacle4= new Obstacle(); // generate fourth obstacle

void setup(){
  size(900,600);
  obstacle1.xcoord=width+(width/3)/2-40; // x coordinate of the first obstacle
  obstacle2.xcoord=width+((width/3)/2-40)+(width/3);  // x coordinate of the second obstacle
  obstacle3.xcoord=width+((width/3)/2-40)+(width/3)*2;  // x coordinate of the third obstacle
  obstacle4.xcoord=width+((width/3)/2-40)+(width/3)*3;  // x coordinate of the fourth obstacle
  // Get response from Arduino
  printArray(Serial.list());
  String portname=Serial.list()[1];
  myPort = new Serial(this,portname,9600);
  myPort.clear();
  // load image for avatar
  img = loadImage("avatar.png");
}

void serialEvent(Serial myPort){
  String s=myPort.readStringUntil('\n');
  s=trim(s);
  if (s!=null){
    int values[]=int(split(s,','));
    // jump effect for the avatar
    if (stop==false && values[0]==1) {avatar_ycoord -= gravity*0.6;}
    // restart game
    else if (stop==true && values[1]==1) {clicked=true; print("hi");}
  }
  myPort.write("\n");
}

void draw(){
  // check if avatar touches the obstacles
  if((avatar_ycoord-25<=obstacle1.first && obstacle1.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle1.second&& obstacle1.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  if((avatar_ycoord-25<=obstacle2.first && obstacle2.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle2.second && obstacle2.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  if((avatar_ycoord-25<=obstacle3.first && obstacle3.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle3.second && obstacle3.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  if((avatar_ycoord-25<=obstacle4.first && obstacle4.xcoord<=avatar_xcoord+25) || (avatar_ycoord+25>=obstacle4.second && obstacle4.xcoord<=avatar_xcoord+25)){
    stop=true;
  }
  // check if avatar goes out of display window
  if(avatar_ycoord>height || avatar_ycoord<0){
    stop=true;
  }
  
  background(#cdeefd);
  // if game ends
  if (stop==true){
    background(#cdeefd);
    textSize(25);
    stroke(#9bf09b);
    noFill();
    strokeWeight(8);
    rect(width/2-200, height/2-150, 400, 300);
    strokeWeight(4);
    // display score
    text("Game Over!", width/2-60, height/2-70);
    text("Score: " + score, width/2-40, height/2-70 + 40);
    text("Click on yellow", width/2-75, height/2-70 + 80);
    text("button to replay!", width/2-85, height/2-70 + 120);
    // restart game and reset all parameters
    if (clicked){
      stop=false;  // reset boolean var
      clicked=false; // reset boolean var
      score=0; // reset score
      obstacle1.xcoord=width+(width/3)/2-40; // x coordinate of he first obstacle
      obstacle2.xcoord=width+((width/3)/2-40)+(width/3); // x coordinate of he second obstacle
      obstacle3.xcoord=width+((width/3)/2-40)+(width/3)*2; // x coordinate of he third obstacle
      obstacle4.xcoord=width+((width/3)/2-40)+(width/3)*3; // x coordinate of he fourth obstacle
      avatar_xcoord= 70; // x coordinate of avatar
      avatar_ycoord= 0; // y coordinate of avatar
    }
  }
  // if game starts
  if (stop==false){
    // create all obstacles
    obstacle1.create();
    obstacle2.create();
    obstacle3.create();
    obstacle4.create();
    // move obstacles
    obstacle1.xcoord -= speed;
    obstacle2.xcoord -= speed;
    obstacle3.xcoord -= speed;
    obstacle4.xcoord -= speed;
    // display the avatar
    image(img, avatar_xcoord-30, avatar_ycoord-35, 80, 70);
    avatar_ycoord += gravity;
    fill(0);
    textSize(12);
    // display the score
    text("Score: " + score, 820, 20);
  }
}

class Obstacle {

  float xcoord; // x coordinate
  float first = random(150,450); // top
  float second = first+140; // bottom
  
  void generate(){
    first = random(150,450);
    second = first+140;
  }
 
  void create(){
    fill(#4caf50);
    noStroke();
    rect(xcoord, 0, 80, first);
    rect(xcoord, second, 80, height);
    fill(#9bf09b);
    rect(xcoord+5, 0, 80-10, first-5);
    rect(xcoord+5, second+5, 80-10, height);
   
    if(xcoord < -80) { // if out of screen
      xcoord = width+230;
      generate();
      score++;
    }
  }
}
Arduino:
int inPin = 3;  // green pushbutton
int inPin2 = 4; // yellow pushbutton
int val = 0;
int val2 = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("0,0");
}
void loop() {
  while (Serial.available()) {
    if (Serial.read() == '\n') {
       val = digitalRead(inPin); // green push button state
       val2 = digitalRead(inPin2); // yellow push button state
       // send the state of the push button to processing
       if (val == HIGH && val2== HIGH) {         
          Serial.println("1,1");
       }
       else if (val == HIGH && val2== LOW) {         
          Serial.println("1,0");
       }
       else if (val == LOW && val2== LOW) {         
          Serial.println("0,0");
       }
       else if (val == LOW && val2== HIGH) {    
          Serial.println("0,1");
       }
    }
  }
}

 

Final project progress: mini-Tesla car

Idea:

For my final project, I am planning to build a mini-Tesla car that will feature both autopilot mode and manual mode. When the autopilot mode is enabled, the car will be able to move around and avoid all obstacles. Whereas for the manual mode, the user will be able to control the car through processing using the arrows/buttons.

The car will be using an ultrasonic sensor mounted on a servo to detect obstacles around it, and DC motors to spin the wheels. Basically, the car will move forward until an obstacle comes within the range of the sensor. The servo is then used to rotate the sensor, and decide on which direction the car should move. If both sides are blocked, the car will turn around and drive back out the way it came. 

The processing display window will show a menu with a couple of options (autopilot mode, manual mode, instructions) that the user can choose from. If manual mode is chosen, arrows/buttons will be displayed and will enable the user to control the car’s movement.

Material needed:
x1 L298N motor driver shield
x1 Servo
x4 DC motors
x4 wheels
x1 Ultrasonic sensor
x4 LEDs (2 yellow, 2 red)
x1 Battery pack
Hardest Part:

I think the hardest part would be figuring out the perfect algorithm for the autopilot mode and choosing the minimum distance. The car will have to decide which way is free depending on the results received from the ultrasonic sensor. Even though the ultrasonic is the most suitable component to use, it is still inaccurate, and might have some difficulties in reading the distance from curved objects, and maybe very thin ones too. The noise might also affect the algorithm so I will try to discard it.

Progress:

As for now, I am working on the coding part on both Arduino and Processing. I have set the basic layout (necessary functions, variables, flags, etc…), and I believe I will have the hardest parts done by Nov 30th. I am also waiting to get the rest of the material needed from the IM lab, to proceed with the circuits, and build the car.

Final Project proposal: mini-Tesla

Idea:

For my final project, I am planning to build a mini Tesla car that will feature both autopilot mode and manual mode. When the autopilot mode is enabled, the car will be able to move around and avoid all obstacles. Whereas for the manual mode, the user will be able to control the car through processing using the arrows/buttons.

Processing:

The processing display window will feature two options (autopilot mode and manual mode) that the user can switch between, in addition to arrows/buttons that will enable the user to control the car’s movement.

Arduino:

For the technical part, I will be using four wheels, an ultrasonic sensor, a servo, an L298N driver, an LDR, and a couple of LEDs.

Week 11: Exercises

Exercise 1:
Arduino
void setup() {
  Serial.begin(9600);
  Serial.println("0");
}

void loop() {
  while (Serial.available()) {
    if (Serial.read() == '\n') {
      int sensor = analogRead(A0);
      Serial.println(sensor);
    }
  }
}
Processing
import processing.serial.*;
Serial myPort;
int xPos=0;
int yPos=0;

void setup(){
  size(960,720);
  printArray(Serial.list());
  String portname=Serial.list()[1];
  println(portname);
  myPort = new Serial(this,portname,9600);
  myPort.clear();
  myPort.bufferUntil('\n');
}

void draw(){
  background(255);
  ellipse(xPos,yPos,30,30);
}

void serialEvent(Serial myPort){
  String s=myPort.readStringUntil('\n');
  s=trim(s);
  if (s!=null){
    int value= int(s);
    xPos=(int)map(value,0,1023,0, width);
    yPos=height/2;
  }
  myPort.write("\n");
}
Exercise 2:
Arduino
void setup() {
  Serial.begin(9600);
  Serial.println("0");
  pinMode(3, OUTPUT);
}

void loop() {
  while (Serial.available()) {
    int right=Serial.parseInt();
    analogWrite(3,right);
  }
}
Processing
import processing.serial.*;
Serial myPort;
int brightness;

void setup(){
  size(960,720);
  printArray(Serial.list());
  String portname=Serial.list()[1];
  println(portname);
  myPort = new Serial(this,portname,9600);
  myPort.clear();
  myPort.bufferUntil('\n');
}

void draw(){
  background(255);
  brightness=int(map(mouseX,0,width,0,255));
  myPort.write(brightness+"\n");
}
Exercise 3:
Arduino
void setup() {
  Serial.begin(9600);
  Serial.println("0");
  pinMode(3, OUTPUT);
}
void loop() {
  while (Serial.available()) {
    int right = Serial.parseInt();
    if (Serial.read() == '\n') {
      digitalWrite(3, right);
      int sensor = analogRead(A0);
      Serial.println(sensor);
    }
  }
}
Processing
import processing.serial.*;
Serial myPort; 
int light;
int pos;
int value=0;
float previous;

PVector velocity;
PVector gravity;
PVector position;
PVector acceleration;
PVector wind;
float drag = 0.99;
float mass = 50;
float hDampening;

void setup() {
  size(640, 360);
  noFill();
  printArray(Serial.list());
  String portname=Serial.list()[1]; 
  myPort = new Serial(this, portname, 9600);
  myPort.clear();
  myPort.bufferUntil('\n');
  position = new PVector(width/2, 0);
  velocity = new PVector(0,0);
  acceleration = new PVector(0,0);
  gravity = new PVector(0, 0.5*mass);
  wind = new PVector(0,0);
  hDampening=map(mass,15,80,.98,.96);
}

void draw() {
  background(255);
  previous=position.y;
  if (!keyPressed){
    wind.x=map(value,0,1023,-1, 1); // Add the wind
    velocity.x*=hDampening;
  }
  applyForce(wind);
  applyForce(gravity);
  velocity.add(acceleration);
  velocity.mult(drag);
  position.add(velocity);
  acceleration.mult(0);
  ellipse(position.x,position.y,mass,mass);
  if (position.y > height-mass/2) {
      velocity.y *= -0.9;  // A little dampening when hitting the bottom
      position.y = height-mass/2;
   }
  // check when it touches the bottom
  if (position.y >= height-mass/2) {
      light= int(true); // turn led on
  }
  else {
    light= int(false); // turn led off
  }
  // compare previous position to the current one (check if it stopped bouncing)
  if (previous==position.y){light= int(false);}
}

void serialEvent(Serial myPort){
  String s = myPort.readStringUntil('\n');
  s=trim(s);
  if (s!=null){
    value = parseInt(s);
    //wind.x = map(value,0,1023,-1, 1);
  }
  myPort.write(light+"\n");
}
  
void applyForce(PVector force){
  // Newton's 2nd law: F = M * A
  // or A = F / M
  PVector f = PVector.div(force, mass);
  acceleration.add(f);
}

void keyPressed(){
  if (keyCode==LEFT){
    wind.x=-1;
  }
  if (keyCode==RIGHT){
    wind.x=1;
  }
  if (key==' '){
    mass=random(15,80);
    position.y=-mass;
    velocity.mult(0);
  }
}

 

View post on imgur.com

Emergency light

Main idea:

For this assignment, I tried to build an emergency light that automatically turns on whenever there is a power outage using both an analog sensor (LDR) and a digital sensor (switch).

Components:

x1 LDR

x1 Button push switch

x2 10K resistors

x1 350 resistor

x7 wires

View post on imgur.com

Process:

When the photoresistor’s surface (LDR) receives enough luminosity (light), its resistance decreases and is read by the program. When the values go lower than 350, it means that there was a power outage, therefore, the program turns the LED on. If the values remain higher than 350, the LED stays off and no power outage is detected.

Global variables:

I am using four global variables (3 are of data type int for the pins, and one boolean variable to check the state of the LED).

int led = 3; // LED pin 
int ldr = A0; // LDR pin 
int button = 5; // Button pin 
bool on = false; // Boolean variable for the state of the LED
Setup():

In the setup function, I mainly set the components to the pins (LED to 3, switch to 5, and LDR to A0) and set Serial.begin() to 9600.

void setup() { 
   Serial.begin(9600); 
   pinMode(led, OUTPUT); // Set the LED pinmode 
   pinMode(ldr, INPUT); // Set the LDR pinmode 
   pinMode(button, INPUT); // Set the button pinmode 
}
Loop():

To achieve the desired result, I am using analogRead() to get the luminosity’s value, then check if it is higher or lower than 350 using if/else conditions. 

// read the brightness from the ldr 
int brightness = analogRead(ldr);
// if its dark (power outage) 
if (brightness < 350) { 
   digitalWrite(led, HIGH); 
} 
// if everything is normal (light) 
else { 
   digitalWrite(led, LOW); 
}

The switch here works as an ON/OFF button for the whole process. I am using a boolean variable as well as digitalRead() to check the state of the LED.

// check if switch is clicked 
if (on == false && digitalRead(button)== HIGH){     
    on = true; 
} 
if (on == true && digitalRead(button) == HIGH){ 
   on = false; 
}

Challenges & thoughts:

To give it a realistic look, I wanted to add a delay() function after the click, and before the lights go on or off, but I wasn’t sure if it would affect the interactivity (mainly when the user clicks on the button). Therefore, I decided to avoid it.

Demo:

View post on imgur.com

Code:
int led = 3; // LED pin
int ldr = A0; // LDR pin
int button = 5; // Button pin
bool on = false; // Boolean variable for the state of the LED


void setup() {
  Serial.begin(9600);
  pinMode(led, OUTPUT); // Set the LED pinmode
  pinMode(ldr, INPUT); // Set the LDR pinmode
  pinMode(button, INPUT); // Set the button pinmode
}

void loop() {
// read the brightness from the ldr
  int brightness = analogRead(ldr);
// check if switch is clicked
  if (on == false && digitalRead(button)== HIGH){
    on = true;
  }
  if (on == true && digitalRead(button) == HIGH){
    on = false;
  }
  // if switch is clicked, then proceed 
  if (on==true){
// if its dark (power outage)
    if (brightness < 350) {
      digitalWrite(led, HIGH);
    }
// if everything is normal (light)
    else {
      digitalWrite(led, LOW);
    }
  }
  else if (on==false) {digitalWrite(led, LOW);}
}

 

Week 8: Hands-free switch

Inspiration:

For this assignment, I was mainly inspired by the UV shoe sanitizer that is used to help fight bacteria in hospitals.

Process:

For the circuit, I have used 5 LEDs ( 4 red, 1 green), 8 wires, and one 320Ω resistor. The basic idea is that the individual waits for the green light to put his shoe on the platform, then the disinfection starts and takes 5 seconds to complete, when the green light goes on again, the individual can then remove his shoe.

View post on imgur.com

Platform:

View post on imgur.com

Setup:

I have used a for() loop and pinMode to avoid unnecessary repetition. I also have an array to store all the LEDs.

void setup() {
  pinMode(7, OUTPUT);
  for (int i=0; i<4; i++){
    pinMode(Led[i], OUTPUT);
  }
}
LEDs:

To tell the user that the disinfection is still going on, I have added an animation to the red LEDs, in which the light bounces between the two edges of the strip. To do that, I used the Delay function as well as two for() loops (for each direction), and digitalWrite() to control the LEDs.

// Check when the shoe is sanitized
if (DONE){
  for (int i=0; i<4; i++){
    digitalWrite(Led[i], LOW);
    digitalWrite(7, HIGH);
  }
}

// If not sanitized
if (DONE==false){
  digitalWrite(7, LOW);
  // One way, left to right
  for (int i=0; i<4; i++){
    // 2 Leds at the same time
    digitalWrite(Led[i], HIGH);
    digitalWrite(Led[i+1], HIGH);
    // Delay of 120
    delay(120);
    digitalWrite(Led[i], LOW);
    digitalWrite(Led[i+1], LOW);}
  // The other way , right to left
  for (int i=3; i>=0; i--){
    digitalWrite(Led[i], HIGH);
    digitalWrite(Led[i-1], HIGH);
    delay(120);
    digitalWrite(Led[i], LOW);
    digitalWrite(Led[i-1], LOW);
  }
}
Timer:

For the timer, I have used millis() to change a boolean variable every 5 seconds.

// Timer for the sanitization
unsigned long curr = millis();
if (curr-prev>=5000){
  prev=curr;
  DONE=!DONE;
}

One of the challenges I faced was setting up a good connection between wires as the tape was not ideal.

Demo:

View post on imgur.com

Code:
int Led[]= {9, 10, 11, 12}; // list of Leds
boolean DONE= false; // check when the sanitization is done
unsigned long prev=0; // for the timer

void setup() {
  pinMode(7, OUTPUT);
  for (int i=0; i<4; i++){
    pinMode(Led[i], OUTPUT);
  }
}

void loop() {
  // Timer for the sanitization
  unsigned long curr = millis();
  if (curr-prev>=5000){
    prev=curr;
    DONE=!DONE;
  }
  
  // Check when the shoe is sanitized
  if (DONE){
    for (int i=0; i<4; i++){
      digitalWrite(Led[i], LOW);
      digitalWrite(7, HIGH);
    }
  }

  // If not sanitized
  if (DONE==false){
    digitalWrite(7, LOW);
    // One way, left to right
    for (int i=0; i<4; i++){
      // 2 Leds at the same time
      digitalWrite(Led[i], HIGH);
      digitalWrite(Led[i+1], HIGH);
      // Delay of 120
      delay(120);
      digitalWrite(Led[i], LOW);
      digitalWrite(Led[i+1], LOW);}
    // The other way , right to left
    for (int i=3; i>=0; i--){
      digitalWrite(Led[i], HIGH);
      digitalWrite(Led[i-1], HIGH);
      delay(120);
      digitalWrite(Led[i], LOW);
      digitalWrite(Led[i-1], LOW);
    }
  }
}

 

Midterm Project

Overview:

As mentioned before, for my midterm project, I was inspired by the mechanics of the Chrome Dino Runner game and tried to create a newer version with more features that I called “Knight Runner”. To avoid obstacles, the avatar can either jump over the fire, or jump/crouch when it’s a bird.

Process & Features:
Parallax:

To give the game a realistic aspect, I added a Parallax effect in which the far-away clouds and mountains seem to move more slowly than the closer ones, by changing each layer’s (6 layers) position by a different amount (between 1 and 5).

// Parallax effect
void update(){
  x6--; x6_2--;
  x5-=2; x5_2-=2;
  x4-=3; x4_2-=3;
  x3-=3; x3_2-=3;
  x2-=4; x2_2-=4;
  x1-=5; x1_2-=5;
}
Infinite Side-scrolling:

Instead of making the character move inside the display window, I used an infinite side-scrolling in which the character is static whereas the background moves from the right to the left. To achieve that, I used two images placed next to each other that reappear on the right side once they get out of the display window.

  // Infinite scrolling
  if (x6<=-width){x6=width;} if (x6_2<=-width){x6_2=width;}
  if (x5<=-width){x5=width;} if (x5_2<=-width){x5_2=width;}
  if (x4<=-width){x4=width;} if (x4_2<=-width){x4_2=width;}
  if (x3<=-width){x3=width;} if (x3_2<=-width){x3_2=width;}
  if (x2<=-width){x2=width;} if (x2_2<=-width){x2_2=width;}
  if (x1<=-width){x1=width;} if (x1_2<=-width){x1_2=width;}
Spritesheet:

To animate the character, I am using 3 sprite sheets stored in a 2D array (running, jumping, sliding, dying), each row has 10 images.

To animate the obstacles, I am using 2 other sprite sheets, one for fire (64 images), and the other for birds (9 images).

I am using frameCount to loop over the sprites

// Upload all the sprites
void loadsprites(){
  // Running
  for (int i=0; i<sprites.length;i++){
    sprites[i][0]=loadImage("assets/run/run"+i+".png");
    sprites[i][0].resize(53,74);
  }
  // Jumping
  for (int i=0; i<sprites.length;i++){
    sprites[i][1]=loadImage("assets/jump/jump"+i+".png");
    sprites[i][1].resize(53,74);
  }
  // Sliding
  for (int i=0; i<sprites.length;i++){
    sprites[i][2]=loadImage("assets/slide/slide"+i+".png");
    sprites[i][2].resize(53,57);
  }
  // Dying
  for (int i=0; i<sprites.length;i++){
    sprites[i][3]=loadImage("assets/dying/Dead__00"+i+".png");
    sprites[i][3].resize(73,77);
  }
  // Fire
  for (int i=0; i<firesprites.length;i++){
    firesprites[i]=loadImage("assets/fire/tile0"+i+".png");
    firesprites[i].resize(60,60);
  }
  // Bird
  for (int i=0; i<birdsprites.length;i++){
    birdsprites[i]=loadImage("assets/bird/tile00"+i+".png");
    birdsprites[i].resize(80,80);
  }
}
Gravity:

I am using a gravity effect for both jumping and sliding, to give the animation a realistic aspect. When jumping, the speed is continuously decreased by the amount of gravity, however, when crouching, it gets increased.

void move(){
  ycoord -= speed;
  // gravity if jumps
  if (ycoord<425){
    speed -= gravity;
  }
  // gravity if crouches
  else if (ycoord>425){
    ycoord += speed;
    speed += gravity;
  }
  // remain same when running
  else{
    state=0;
    speed=0;
    ycoord=425;
  }
}
Jump and Crouch:

The user cannot jump and crouch at the same time. Both the jump and crouch are done using ycoord, speed and the gravity.

// jump
void jump(){
  if (ycoord==425 && crouching==false){
    state=1;
    gravity=1;
    speed= 16;
  }
}

// crouch
void crouch(){
  if (crouching==true && ycoord==425){
    state=2;
    ycoord=442;
    gravity=1;
    speed= -20;
  }
}
Generating obstacles:

To generate obstacles I am both using frameCount and two random functions, the first one is used to choose when to generate the obstacle, whereas the second one is used to choose what to generate (fire or bird). The obstacles are automatically stored in an array to keep track of their position and to display them continuously. If the obstacles disappear from the screen (go over the edge) they get immediately remove from the array.

The bird obstacles have a higher speed than fire because technically fire is static so it should have the same speed as the scrolling, whereas the bird is flying.

// add a new obstacle
void addObstacle(){
  if (frameCount % 60 == 0 && random(1)<0.5){
    // choose randomly if its fire or a bird
    if (random(2)<1){
      // add it to the list
      firelist.add(new Fire());
    }
    else {
      birdlist.add(new Bird());
    }
  }
}
Collisions:

To flag when the avatar touches one of the obstacles, I first used a rectangle to limit the areas of both the obstacle and avatar, then checked if those areas overlapped. Meaning that the x and y coordinates of the avatar would be inside the obstacle area. The avatar should jump when there is fire, but can either jump or crouch when there is a bird, to avoid collisions with obstacles.

// Bird
boolean checkfail(float ycoord){
  // if crouching, avatar is safe
 if (avatar.state==2){
      return false;
    }
    // check if avatar touches the obstacle
    return xcoord+25>=100 && xcoord+25<=150 && ycoord>=400 && ycoord<=400+30 || xcoord+40+25>=100 && xcoord+40+25<=150 && ycoord+70>=400 && ycoord<=400+30;
}
// fire
boolean checkfail(float ycoord){
  // check if avatar touches the obstacle
  return xcoord>=100 && xcoord<=150 && ycoord+70>=540-60-30 || xcoord+40>=100 && xcoord+40<=150 && ycoord+70>=540-60-30;
}
Menu: 

Similarly, to check which button the user clicked, I used mouseClicked(), mouseX, and mouseY and checked whether the x and y coordinates are inside the area of that specific button. The menu (lobby) is displayed first, then the user has a choice to either start the game, or read the instructions.

To switch between lobby, game, and instructions pages, I am overlapping backgrounds over each other.

draw():

Inside my draw() function, I mainly check the status of the game using boolean variables (avatar died, menu, reset, game ongoing…), then proceed with displaying the right images, and the right text.

Boolean variables:

Start: flags when the user clicks on the start button, if start is false, then the menu is displayed

Help: flags when the user clicks on the help button, the instructions are then displayed

Dead: flags when the avatar touches an obstacle, the game then ends, and the user is given a choice to replay

Reset: flags when the user chooses to replay, all the games settings are reset, and the arrays get cleared

void draw(){
   
  // if instructions icon clicked
  if (help){
    // mute the music
    back.amp(0);
    image(main,0,0);
    imageMode(CENTER);
    image(instructmenu,width/2,height/2);
    image(backmenu,width/2, height/2-200);
    imageMode(CORNER);
    textSize(20);
    text("↑ : Jump",width/2,height/2-60);
    text("↓ : Crouch",width/2,height/2-20);
    text("Try to avoid all obstacles",width/2,height/2+20);
    text("(Fire, birds)",width/2,height/2+60);
    noFill();
    rect(width/2-30,height/2-230,60,60);  
  }
  
  else if (start){
    // unmute the music if alive
    if (dead==false) {back.amp(1);}
    // mute the music if dead
    else {back.amp(0);}
    display(); // display the background images
    update(); // parallax effect and infinite scrolling
    rect(100,425,50,70);
    avatar.show(); // display the avatar
    addObstacle(); // add an obstacle
    imageMode(CENTER);
    // Display the score
    image(scoremenu, width/2, 50); 
    imageMode(CORNER);
    textSize(20);
    textAlign(CENTER);
    text("score:  " + round(score),width/2,55);
    
    // Display the obstacles
    for (int i=0; i<firelist.size(); i++){
      firelist.get(i).show();
      firelist.get(i).move();
      
      // check if avatar touches an obstacle
      if (firelist.get(i).checkfail(avatar.ycoord)){
        dead=true;
      }
      
      // remove the obstacles that are not displayed
      if (firelist.get(i).xcoord <-70){
        firelist.remove(i);
      }
    }
    
    // Display the obstacles
    for (int i=0; i<birdlist.size(); i++){
      birdlist.get(i).show();
      birdlist.get(i).move();
      
      // check if avatar touches an obstacle
      if (birdlist.get(i).checkfail(avatar.ycoord)){
        dead=true;
      }
      
      // remove the obstacles that are not displayed
      if (birdlist.get(i).xcoord <-70){
        birdlist.remove(i);
      }
    }
    
    // If replay button is clicked, reset the game
    if (reset==true){
     back.amp(1); // unmute the music
     dead=false;
     start=true;
     reset=false;
     score=0; // reset the score
     // reset the obstacles list
     firelist = new ArrayList<Fire>();
     birdlist = new ArrayList<Bird>();
    }
   
    if (dead==true){
      // stop the parallax
      x6++; x6_2++;
      x5+=2; x5_2+=2;
      x4+=3; x4_2+=3;
      x3+=3; x3_2+=3;
      x2+=4; x2_2+=4;
      x1+=5; x1_2+=5;
      
      // stop the obstacles animation
      for (int i=0; i<firelist.size(); i++){
        firelist.get(i).xcoord +=5;
      }
      for (int i=0; i<birdlist.size(); i++){
        birdlist.get(i).xcoord +=10;
      }
      // enable the dying animation
      avatar.state=3;
      // display the replay button
      imageMode(CENTER);
      image(startmenu,width/2, height/2-20);
      text("REPLAY",width/2,height/2+7-20);
      imageMode(CORNER);
   }
 }
 
 // display the lobby menu
 else if (start==false){
   // mute the music
   back.amp(0);
   menu();
   image(main,0,0);
   imageMode(CENTER);
   textAlign(CENTER);
   textSize(30);
   // display the ui
   image(startmenu,width/2, height/2-20);
   text("PLAY",width/2,height/2+7-20);
   image(helpmenu,width/2, height/2+100-20);
   text("HELP",width/2,height/2+100+7-20);
   image(title,width/2, 100);
   noFill();
   rect(width/2-75,height/2-55,150,70);
   rect(width/2-90,height/2+40,180,80);
   imageMode(CORNER);
 }
 
}
Shapes:

I have used 4 blinking rectangles to add some aesthetics to the title.

noStroke();
if (frameCount%15==0){
  noFill();}
 else{ fill(0);}
rect(width/2+220, 85,10,30,PI);
rect(width/2+240, 90,10,20,PI);
rect(width/2-225, 85,10,30,PI);
rect(width/2-245, 90,10,20,PI);
fill(255);
User Input:

To detect when the user clicks on a key, I used both keyPressed() and keyReleased() functions for keyboard, and mouseClicked.

void mouseClicked(){
// if player clicks on start
if ((mouseX>width/2-75) && (mouseX<width/2+75) && (mouseY>height/2-55) && (mouseY<height/2+15)){
start=true;
menu.play();
}
// if player clicks on help
if ((start==false) && (mouseX>width/2-90) && (mouseX<width/2+90) && (mouseY>height/2+40) && (mouseY<height/2+120)){
help=true;
menu.play();
}
// if player clicks on back
else if ((help==true) && (mouseX>width/2-30) && (mouseX<width/2+30) && (mouseY>height/2-230) && (mouseY<height-170)){
help=false;
menu.play();
}
// if player clicks on replay
else if ((dead==true) && (mouseX>width/2-75) && (mouseX<width/2+75) && (mouseY>height/2-55) && (mouseY<height/2+15)){
reset=true;
menu.play();
}
}
void keyPressed(){
  // Jump
  if (keyCode==UP && dead==false){
    avatar.jump();
  }
  // Crouch
  if (keyCode==DOWN && dead==false){
    avatar.crouching=true;
    avatar.crouch();
  }
}

void keyReleased(){
  // stop crouching
  if (keyCode==DOWN && dead==false){
    avatar.crouching=false;
  }
}
SFX:

Concerning the sound effects, I have only used two main ones (the background music, and the click sound effect). Instead of stopping the music, I mute it, then unmute it when the game starts.

SoundFile menu; // Click sound effect
SoundFile back; // background music 

// Load the sound effects
menu = new SoundFile(this, "assets/menu.wav");
back = new SoundFile(this, "assets/back.mp3");
// Play the background music
back.play();
// Loop the background music
back.loop();

// unmute the music if alive
if (dead==false) {back.amp(1);}
// mute the music if dead
else {back.amp(0);}
Score:

The score gets incremented when the avatar is moving by O.O5 per frame and is displayed on the top-center of the display window. It gets reset when the game restarts.

// increment the score
if (!dead) {score+=0.05;}
End of the game:

When the avatar touches an obstacle, the dying animation is enabled and freezes at the last sprite, and all the other animations/motions are stopped. The score is displayed on top, and the replay button appears.

 

View post on imgur.com

FULL CODE:

import processing.sound.*;
PImage bg1, bg2, bg3, bg4, bg5, bg6, bg7, platform, main, startmenu, helpmenu, title, instructmenu, scoremenu; // Background images
PImage backmenu;
int x1=0, x1_2=960, x2=0, x2_2=960, x3=0, x3_2=960; // X-coordinates of the images
int x4=0, x4_2=960, x5=0, x5_2=960, x6=0, x6_2=960; // X-coordinates of the images
PImage[][] sprites = new PImage[10][4]; // Store the sprites for the avatar
PImage [] firesprites = new PImage[64]; // Store the sprites for the fire
PImage [] birdsprites = new PImage[9]; // Store the sprites for the birds
ArrayList<Fire> firelist = new ArrayList<Fire>(); // Store all the fire objects
ArrayList<Bird> birdlist = new ArrayList<Bird>(); // store all the bird objects
boolean start=false;
boolean dead= false; 
boolean help= false;
boolean reset= false;
float score=0; // Keep track of the score
SoundFile menu; // Click sound effect
SoundFile back; // background music 

// Create a new avatar
Avatar avatar = new Avatar(0,425);

// Upload all the sprites
void loadsprites(){
  // Running
  for (int i=0; i<sprites.length;i++){
    sprites[i][0]=loadImage("assets/run/run"+i+".png");
    sprites[i][0].resize(53,74);
  }
  // Jumping
  for (int i=0; i<sprites.length;i++){
    sprites[i][1]=loadImage("assets/jump/jump"+i+".png");
    sprites[i][1].resize(53,74);
  }
  // Sliding
  for (int i=0; i<sprites.length;i++){
    sprites[i][2]=loadImage("assets/slide/slide"+i+".png");
    sprites[i][2].resize(53,57);
  }
  // Dying
  for (int i=0; i<sprites.length;i++){
    sprites[i][3]=loadImage("assets/dying/Dead__00"+i+".png");
    sprites[i][3].resize(73,77);
  }
  // Fire
  for (int i=0; i<firesprites.length;i++){
    firesprites[i]=loadImage("assets/fire/tile0"+i+".png");
    firesprites[i].resize(60,60);
  }
  // Bird
  for (int i=0; i<birdsprites.length;i++){
    birdsprites[i]=loadImage("assets/bird/tile00"+i+".png");
    birdsprites[i].resize(80,80);
  }
}

// Load the background images
void load(){
  bg7 = loadImage("assets/bg.png");
  // Resize the images
  bg7.resize(960,540);
  bg6 = loadImage("assets/bg6.png");
  bg6.resize(960,540);
  bg5 = loadImage("assets/bg5.png");
  bg5.resize(960,540);
  bg4 = loadImage("assets/bg2.png");
  bg4.resize(960,540);
  bg3 = loadImage("assets/bg4.png");
  bg3.resize(960,540);
  bg2 = loadImage("assets/bg1.png");
  bg2.resize(960,540);
  bg1 = loadImage("assets/bg3.png");
  bg1.resize(960,540);
  platform = loadImage("assets/platform.png");
  platform.resize(960,610);
}

// menu loading
void menu(){
  // load ui images
  startmenu = loadImage("assets/b_3.png");
  helpmenu = loadImage("assets/b_4.png");
  // resize the images
  startmenu.resize(150,70);
  helpmenu.resize(180,80);
  title = loadImage("assets/title.png");
  instructmenu = loadImage("assets/b_5.png");
  scoremenu = loadImage("assets/bar_1.png");
  scoremenu.resize(250,40);
  instructmenu.resize(350,300);
  backmenu = loadImage("assets/b_6.png");
  backmenu.resize(60,60);
}

// Display the background
void display(){
  image(bg7,0,0);
  image(bg6,x6,0);
  image(bg6,x6_2,0);
  image(bg5,x5,0);
  image(bg5,x5_2,0);
  image(bg4,x4,0);
  image(bg4,x4_2,0);
  image(bg3,x3,0);
  image(bg3,x3_2,0);
  image(bg2,x2,0);
  image(bg2,x2_2,0);
  image(bg1,x1,0);
  image(bg1,x1_2,0);
  // Add a tint to match the background
  tint(#7cdfd2);
  image(platform,x1,0);
  image(platform,x1_2,0);
  noTint();
}

// Parallax effect
void update(){
  x6--; x6_2--;
  x5-=2; x5_2-=2;
  x4-=3; x4_2-=3;
  x3-=3; x3_2-=3;
  x2-=4; x2_2-=4;
  x1-=5; x1_2-=5;
  
  // Infinite scrolling
  if (x6<=-width){x6=width;} if (x6_2<=-width){x6_2=width;}
  if (x5<=-width){x5=width;} if (x5_2<=-width){x5_2=width;}
  if (x4<=-width){x4=width;} if (x4_2<=-width){x4_2=width;}
  if (x3<=-width){x3=width;} if (x3_2<=-width){x3_2=width;}
  if (x2<=-width){x2=width;} if (x2_2<=-width){x2_2=width;}
  if (x1<=-width){x1=width;} if (x1_2<=-width){x1_2=width;} 
}

// add a new obstacle
void addObstacle(){
  if (frameCount % 60 == 0 && random(1)<0.5){
    // choose randomly if its fire or a bird
    if (random(2)<1){
      // add it to the list
      firelist.add(new Fire());
    }
    else {
      birdlist.add(new Bird());
    }
  }
}

void setup(){
  size(960,540);
  load(); // load the background images
  loadsprites(); // load the sprites
  main = loadImage("assets/main.png");
  main.resize(960,540);
  // Load the sound effects
  menu = new SoundFile(this, "assets/menu.wav");
  back = new SoundFile(this, "assets/back.mp3");
  // Play the background music
  back.play();
  // Loop the background music
  back.loop();
}

void draw(){
   
  // if instructions icon clicked
  if (help){
    // mute the music
    back.amp(0);
    image(main,0,0);
    imageMode(CENTER);
    image(instructmenu,width/2,height/2);
    image(backmenu,width/2, height/2-200);
    imageMode(CORNER);
    textSize(20);
    text("↑ : Jump",width/2,height/2-60);
    text("↓ : Crouch",width/2,height/2-20);
    text("Try to avoid all obstacles",width/2,height/2+20);
    text("(Fire, birds)",width/2,height/2+60);
    noFill();
    //rect(width/2-30,height/2-230,60,60);  
  }
  
  else if (start){
    // unmute the music if alive
    if (dead==false) {back.amp(1);}
    // mute the music if dead
    else {back.amp(0);}
    display(); // display the background images
    update(); // parallax effect and infinite scrolling
    //rect(100,425,50,70);
    avatar.show(); // display the avatar
    addObstacle(); // add an obstacle
    imageMode(CENTER);
    // Display the score
    image(scoremenu, width/2, 50); 
    imageMode(CORNER);
    textSize(20);
    textAlign(CENTER);
    text("score:  " + round(score),width/2,55);
    
    // Display the obstacles
    for (int i=0; i<firelist.size(); i++){
      firelist.get(i).show();
      firelist.get(i).move();
      
      // check if avatar touches an obstacle
      if (firelist.get(i).checkfail(avatar.ycoord)){
        dead=true;
      }
      
      // remove the obstacles that are not displayed
      if (firelist.get(i).xcoord <-70){
        firelist.remove(i);
      }
    }
    
    // Display the obstacles
    for (int i=0; i<birdlist.size(); i++){
      birdlist.get(i).show();
      birdlist.get(i).move();
      
      // check if avatar touches an obstacle
      if (birdlist.get(i).checkfail(avatar.ycoord)){
        dead=true;
      }
      
      // remove the obstacles that are not displayed
      if (birdlist.get(i).xcoord <-70){
        birdlist.remove(i);
      }
    }
    
    // If replay button is clicked, reset the game
    if (reset==true){
     back.amp(1); // unmute the music
     dead=false;
     start=true;
     reset=false;
     score=0; // reset the score
     // reset the obstacles list
     firelist = new ArrayList<Fire>();
     birdlist = new ArrayList<Bird>();
    }
   
    if (dead==true){
      // stop the parallax
      x6++; x6_2++;
      x5+=2; x5_2+=2;
      x4+=3; x4_2+=3;
      x3+=3; x3_2+=3;
      x2+=4; x2_2+=4;
      x1+=5; x1_2+=5;
      
      // stop the obstacles animation
      for (int i=0; i<firelist.size(); i++){
        firelist.get(i).xcoord +=5;
      }
      for (int i=0; i<birdlist.size(); i++){
        birdlist.get(i).xcoord +=10;
      }
      // enable the dying animation
      avatar.state=3;
      // display the replay button
      imageMode(CENTER);
      image(startmenu,width/2, height/2-20);
      text("REPLAY",width/2,height/2+7-20);
      imageMode(CORNER);
   }
 }
 
 // display the lobby menu
 else if (start==false){
   // mute the music
   back.amp(0);
   menu();
   image(main,0,0);
   imageMode(CENTER);
   textAlign(CENTER);
   textSize(30);
   // display the ui
   image(startmenu,width/2, height/2-20);
   text("PLAY",width/2,height/2+7-20);
   image(helpmenu,width/2, height/2+100-20);
   text("HELP",width/2,height/2+100+7-20);
   image(title,width/2, 100);
   noFill();
   //rect(width/2-75,height/2-55,150,70);
   //rect(width/2-90,height/2+40,180,80);
   imageMode(CORNER);
   noStroke();
   if (frameCount%15==0){
     noFill();}
    else{ fill(0);}
   rect(width/2+220, 85,10,30,PI);
   rect(width/2+240, 90,10,20,PI);
   rect(width/2-225, 85,10,30,PI);
   rect(width/2-245, 90,10,20,PI);
   fill(255);
 }
 
}

void mouseClicked(){
  // if player clicks on start
  if ((mouseX>width/2-75) && (mouseX<width/2+75) && (mouseY>height/2-55) && (mouseY<height/2+15)){
    start=true;
    menu.play();
  }
  // if player clicks on help
  if ((start==false) && (mouseX>width/2-90) && (mouseX<width/2+90) && (mouseY>height/2+40) && (mouseY<height/2+120)){
     help=true;
     menu.play();
  }
  // if player clicks on back
  else if ((help==true) && (mouseX>width/2-30) && (mouseX<width/2+30) && (mouseY>height/2-230) && (mouseY<height-170)){
     help=false;
     menu.play();
  }
  // if player clicks on replay
  else if ((dead==true) && (mouseX>width/2-75) && (mouseX<width/2+75) && (mouseY>height/2-55) && (mouseY<height/2+15)){
    reset=true;
    menu.play();
  }
}

void keyPressed(){
  // Jump
  if (keyCode==UP && dead==false){
    avatar.jump();
  }
  // Crouch
  if (keyCode==DOWN && dead==false){
    avatar.crouching=true;
    avatar.crouch();
  }
}

void keyReleased(){
  // stop crouching
  if (keyCode==DOWN && dead==false){
    avatar.crouching=false;
  }
}

class Bird{
  //bird's x-coordinate
  float xcoord;
  
  Bird(){
    // generate the bird outside the screen
    xcoord = 40 + 960;
  }
  
  // display the bird
  void show(){
    // stop the animation
    if (dead){image(birdsprites[6],xcoord,380);}
    else{
    // play the animation
    image(birdsprites[frameCount/2%birdsprites.length],xcoord,380);}
  }
  
  // move the bird
  void move(){
    xcoord -= 10;
    noFill();
    //rect(xcoord+25, 400, 48,30);
  }
  
  boolean checkfail(float ycoord){
    // if crouching, avatar is safe
    if (avatar.state==2){
      return false;
    }
    // check if avatar touches the obstacle
    return xcoord+25>=100 && xcoord+25<=150 && ycoord>=400 && ycoord<=400+30 || xcoord+40+25>=100 && xcoord+40+25<=150 && ycoord+70>=400 && ycoord<=400+30;
  }
}

class Fire{
  //fire's x-coordinate
  float xcoord;
  
  Fire(){
    // generate the fire outside the screen
    xcoord = 40 + 960;
  }
  
  // display the fire
  void show(){
    // stop the animation
    if (dead){image(firesprites[10],xcoord,435);}
    else{
    // play the animation
    image(firesprites[frameCount/2%firesprites.length],xcoord,435);}
  }

  // move the fire
  void move(){
    xcoord -= 5;
  }
  
  boolean checkfail(float ycoord){
    // check if avatar touches the obstacle
    return xcoord>=100 && xcoord<=150 && ycoord+70>=540-60-30 || xcoord+40>=100 && xcoord+40<=150 && ycoord+70>=540-60-30;
  }
}

class Avatar{
  float xcoord=100; // xcoordinate of avatar
  float ycoord; // ycoordinate of avatar
  float gravity=1; // gravity of avatar
  float speed= 0; // speed of avatar
  int state=0; // state of avatar (jumping, crouchin, dying)
  boolean crouching =false; // flag if crouching
   // used for dying animation
  int k=0; 
  int cnt;
  
  // constructor
  Avatar(int state, float ycoord){
    this.state= state;
    this.ycoord= ycoord;
  }
  
  void show(){
    // play the dying animation
    if (state==3 && cnt<9){
      // freeze at last sprite
      ycoord=427;
      image(sprites[k*frameCount/2%sprites.length][state],xcoord,ycoord);
      k=1;
      cnt++;
    }
    // display animation
    else if (state==1 || state==2 || state==0){
      image(sprites[frameCount/2%sprites.length][state],xcoord,ycoord);
    }
    else {
      // freeze at last sprite
      ycoord=427;
      image(sprites[9][state],xcoord,ycoord);
    }
    move();
  }
  
  // move the player
  void move(){
    // increment the score
    if (!dead) {score+=0.05;}
    ycoord -= speed;
    // gravity if jumps
    if (ycoord<425){
      speed -= gravity;
    }
    // gravity if crouches
    else if (ycoord>425){
      ycoord += speed;
      speed += gravity;
    }
    // remain same when running
    else{
      state=0;
      speed=0;
      ycoord=425;
    }
  }
  
  // jump
  void jump(){
    if (ycoord==425 && crouching==false){
      state=1;
      gravity=1;
      speed= 16;
    }
  }
  
  // crouch
  void crouch(){
    if (crouching==true && ycoord==425){
      state=2;
      ycoord=442;
      gravity=1;
      speed= -20;
    }
  }
}

 

 

Midterm Project – Progress

 

 

Inspiration:

For my midterm project, I was inspired by the mechanics of the Chrome Dino Runner game and tried to create a newer version with more features.

Process:

First, the layers of the background are loaded, then resized to fit the screen.

// Load the background images
void load(){
  bg7 = loadImage("assets/bg.png");
  // Resize the images
  bg7.resize(960,540);
  bg6 = loadImage("assets/bg6.png");
  bg6.resize(960,540);
  bg5 = loadImage("assets/bg5.png");
  bg5.resize(960,540);
  bg4 = loadImage("assets/bg2.png");
  bg4.resize(960,540);
  bg3 = loadImage("assets/bg4.png");
  bg3.resize(960,540);
  bg2 = loadImage("assets/bg1.png");
  bg2.resize(960,540);
  bg1 = loadImage("assets/bg3.png");
  bg1.resize(960,540);
  platform = loadImage("assets/platform.png");
  platform.resize(960,610);
}
Infinite side-scrolling:

Instead of making the character move inside the display window, I used an infinite side-scrolling in which the character is static whereas the background moves from the right to the left. To achieve that, I used two images placed next to each other that reappear on the right side once they get out of the display window.

Parallax effect

To give the game a realistic aspect, I added a Parallax effect in which the far-away clouds and mountains seem to move more slowly than the closer ones, by changing each layer’s (6 layers) position by a different amount (between 1 and 5).

// Parallax effect
void update(){
  x6--; x6_2--;
  x5-=2; x5_2-=2;
  x4-=3; x4_2-=3;
  x3-=3; x3_2-=3;
  x2-=4; x2_2-=4;
  x1-=5; x1_2-=5;
  
  // Infinite scrolling
  if (x6<=-width){x6=width;} if (x6_2<=-width){x6_2=width;}
  if (x5<=-width){x5=width;} if (x5_2<=-width){x5_2=width;}
  if (x4<=-width){x4=width;} if (x4_2<=-width){x4_2=width;}
  if (x3<=-width){x3=width;} if (x3_2<=-width){x3_2=width;}
  if (x2<=-width){x2=width;} if (x2_2<=-width){x2_2=width;}
  if (x1<=-width){x1=width;} if (x1_2<=-width){x1_2=width;} 
}

View post on imgur.com

Spritesheet:

To animate the character, I am using 3 sprite sheets stored in a 2D array (running, jumping, sliding), each row has 10 images.

// Upload all the sprites
void loadsprites(){
  // Running
  for (int i=0; i<sprites.length;i++){
    sprites[i][0]=loadImage("assets/run/run"+i+".png");
    sprites[i][0].resize(53,74);
  }
  // Jumping
  for (int i=0; i<sprites.length;i++){
    sprites[i][1]=loadImage("assets/jump/jump"+i+".png");
    sprites[i][1].resize(53,74);
  }
  // Sliding
  for (int i=0; i<sprites.length;i++){
    sprites[i][2]=loadImage("assets/slide/slide"+i+".png");
    sprites[i][2].resize(53,57);
  }
}

To loop over the sprites, I am using frameCount:

image(sprites[frameCount/2%sprites.length][x],xcoord,ycoord);

Then, I am using both keyPressed() and keyReleased() functions to detect when the user presses UP and DOWN keys and proceed with the movement.

void keyPressed(){
  if (keyCode==UP){
    x=1;
    ycoord=360;
  }
  if (keyCode==DOWN){
    x=2;
    ycoord=442;
  }
}

void keyReleased(){
  if (keyCode==UP){
    x=0;
    ycoord=425;
  }
  if (keyCode==DOWN){
    x=0;
    ycoord=425;
  }
}
Gravity:

To make it a bit more realistic, I am trying to add the gravity effect to when the character jumps and falls back down.

View post on imgur.com

Full Code:

PImage bg1, bg2, bg3, bg4, bg5, bg6, bg7, platform; // Background images
int x1=0, x1_2=960, x2=0, x2_2=960, x3=0, x3_2=960; // X-coordinates of the images
int x4=0, x4_2=960, x5=0, x5_2=960, x6=0, x6_2=960; // X-coordinates of the images
PImage[][] sprites = new PImage[10][3]; // Store the sprites

// Upload all the sprites
void loadsprites(){
  // Running
  for (int i=0; i<sprites.length;i++){
    sprites[i][0]=loadImage("assets/run/run"+i+".png");
    sprites[i][0].resize(53,74);
  }
  // Jumping
  for (int i=0; i<sprites.length;i++){
    sprites[i][1]=loadImage("assets/jump/jump"+i+".png");
    sprites[i][1].resize(53,74);
  }
  // Sliding
  for (int i=0; i<sprites.length;i++){
    sprites[i][2]=loadImage("assets/slide/slide"+i+".png");
    sprites[i][2].resize(53,57);
  }
}

// Load the background images
void load(){
  bg7 = loadImage("assets/bg.png");
  // Resize the images
  bg7.resize(960,540);
  bg6 = loadImage("assets/bg6.png");
  bg6.resize(960,540);
  bg5 = loadImage("assets/bg5.png");
  bg5.resize(960,540);
  bg4 = loadImage("assets/bg2.png");
  bg4.resize(960,540);
  bg3 = loadImage("assets/bg4.png");
  bg3.resize(960,540);
  bg2 = loadImage("assets/bg1.png");
  bg2.resize(960,540);
  bg1 = loadImage("assets/bg3.png");
  bg1.resize(960,540);
  platform = loadImage("assets/platform.png");
  platform.resize(960,610);
}

void display(){
  image(bg7,0,0);
  image(bg6,x6,0);
  image(bg6,x6_2,0);
  image(bg5,x5,0);
  image(bg5,x5_2,0);
  image(bg4,x4,0);
  image(bg4,x4_2,0);
  image(bg3,x3,0);
  image(bg3,x3_2,0);
  image(bg2,x2,0);
  image(bg2,x2_2,0);
  image(bg1,x1,0);
  image(bg1,x1_2,0);
  tint(#7cdfd2);
  image(platform,x1,0);
  image(platform,x1_2,0);
  noTint();
}

// Parallax effect
void update(){
  x6--; x6_2--;
  x5-=2; x5_2-=2;
  x4-=3; x4_2-=3;
  x3-=3; x3_2-=3;
  x2-=4; x2_2-=4;
  x1-=5; x1_2-=5;
  
  // Infinite scrolling
  if (x6<=-width){x6=width;} if (x6_2<=-width){x6_2=width;}
  if (x5<=-width){x5=width;} if (x5_2<=-width){x5_2=width;}
  if (x4<=-width){x4=width;} if (x4_2<=-width){x4_2=width;}
  if (x3<=-width){x3=width;} if (x3_2<=-width){x3_2=width;}
  if (x2<=-width){x2=width;} if (x2_2<=-width){x2_2=width;}
  if (x1<=-width){x1=width;} if (x1_2<=-width){x1_2=width;} 
}

void setup(){
  size(960,540);
  load();
  loadsprites();
}
int x=0, xcoord=100, ycoord=425;
float g=2;

void gravity(){
  if (ycoord<425){
    ycoord+=g;
  }
}

void draw(){
  display();
  update(); 
  gravity();
  image(sprites[frameCount/2%sprites.length][x],xcoord,ycoord);
}

void keyPressed(){
  if (keyCode==UP){
    x=1;
    ycoord=360;
  }
  if (keyCode==DOWN){
    x=2;
    ycoord=442;
  }
}

void keyReleased(){
  if (keyCode==UP){
    x=0;
    ycoord=425;
  }
  if (keyCode==DOWN){
    x=0;
    ycoord=425;
  }
}