3 Point Shootout – Final Project

Game Idea

I really wanted to make something related to basketball, since I am a big fan of the sport. So I decided to make a mini basketball shooting game, similar to the ones seen in arcades. My game consists of a small boxy frame in which there is a hoop I made. The player basically has to shoot the ball into the hoop from a fair distance. Each time the ball goes through the hoop, it hits the flex sensor and changes its values. Each time a change in the flex sensor values is detected, the player is awarded 3 points. There are no point deductions for misses. There is also a timer of 60 seconds after which a buzzer sounds and the game ends. Then the player can see their score, the high score, and has the option to play again.

Game Box Frame

I made a boxy frame (see game demo below) to hold up the hoop and also have a ramp so that the ball would roll back towards the player which makes it more convenient. Ideally, the box should have been bigger to allow more space for the ball to fall on the ramp so that most misses would still fall within the box and the ball would roll back to the user. However, I did not think of this when designing the box. I wanted to use my monitor to display the p5js sketch behind the box frame just so that the score would be directly behind the target and the player would not have to look to the side each time they want to see the score or the time left.

I made the hoop using a plastic tube as the pole holding it up, a piece of cardboard as the backboard, and a transparent plastic cup as the rim. The ball is just a ping pong ball that I had in my room. To assemble the carboard pieces together, I used a glue gun and I drilled a hole into the ramp to insert the plastic tube into. The hole I drilled was smaller than the diameter of the tube so I forced the tube into the hole. This was to make sure the tube fits perfectly and is tightly held up. The two holes on the side only exist to make it easier for me to carry it.

P5JS Implementation

I started my project by making the game class, with different game states so I can transition between the menu screen and game screen easily, and to make it easier to restart the game when necessary. Doing this also lets me do different things whenever the mouse is pressed at different stages of the game. I then added all the necessary variables for all the images, sounds, the sprite sheet, arrays and other necessary variables.

Then I made the game full screen when the user clicks the starting screen, by resizing the canvas and using fullscreen(). While this worked fine on my laptop screen, it just would not fill up my entire monitor screen (see game demo below), even when I tried hard coding the canvas size values with the monitor resolution.

I used a sprite sheet of a bouncing basketball I found online on the menu screen to make the menu screen more dynamic and not just a boring old background.

Also, I had an array of sounds from audio clips of a famous NBA commentator, Mike Breen who has iconic lines. Each time a shot is made, a rando sound from the array is played to provide feedback to the user. When the game ends, there is a buzzer sound played to let the user know the game is over.

Here is the p5sj code:

let alpha = 255;
let game;
let backgroundMusic;
let spritesheet;
let bballSprite = [];
let step = 0;
let x = 0;
let w;
let h;
let startTime = 0;
let cd = 1;
let gameTime = 5;
let fade = 0;
let prevVal = 0;
let newVal = 0;
let time = 0;
let sounds = [];
let crowdSound;
let music;

function preload() {
  // backgroundMusic = loadSound("assets/background.mp3");
  spritesheet = loadImage("assets/sprite.png");
  crowd = loadImage("assets/background2.jpg");
  menu = loadImage("assets/menu.jpg");
  font = loadFont("assets/font.ttf");
  buzzer = loadSound("assets/buzzer.wav");
  for (let i = 1; i < 7; i++){
    sounds[i-1] = loadSound("assets/" + i + ".wav");
  }
  crowdSound = loadSound("assets/crowdSound.wav");
  music = loadSound("assets/music.wav");
}

function setup() {
  createCanvas(600, 600);
  textSize(18);
  setUpSerial();
  game = new Game();
  textAlign(CENTER);
  rectMode(CENTER);
  spritesheet.resize(spritesheet.width / 2, spritesheet.height / 2);
  w = spritesheet.width / 6;
  h = spritesheet.height;
  for (let i = 0; i < 6; i++) {
    bballSprite[i] = spritesheet.get(i * w, 0, w, h);
  }
  textFont(font);
}

function draw() {
  if (!serialActive) {
    fill(255);
    text("Press Space Bar to select Serial Port", 20, 30);
  } else {
    //backgroundMusic.play();
    background(255);
    if (game.state == 0) {
      game.click();
    } else if (game.state == 1) {
      game.menu();
    } else if (game.state == 2) {
      game.countdown();
    } else if (game.state == 3) {
      game.game();
    } else if (game.state == 4) {
      game.endScreen();
    }
  }
}

function mousePressed() {
  if (game.state == 0) {
    music.setVolume(0.5);
    music.loop();
    let fs = fullscreen();
    fullscreen(!fs);
    resizeCanvas(displayWidth, displayHeight);
    game.state++;
  } else if (
    game.state == 1 &&
    mouseX < width / 4 + width / 8 &&
    mouseX > width / 4 - width / 8 &&
    mouseY < (5 * height) / 9 + height / 14 &&
    mouseY > (5 * height) / 9 - height / 14
  ) {
    music.stop();
    game.state++;
    startTime = frameCount;
  } else if (
    game.state == 4 &&
    mouseX < width / 2 + width / 4 &&
    mouseX > width / 2 - width / 4 &&
    mouseY < (7 * height) / 9 + height / 5 &&
    mouseY > (7 * height) / 9 - height / 5
  ) {
    game.state = 2;
    game.score = 0;
    cd = 3;
    gameTime = 60;
  }
}

function keyPressed() {
  if (key == " ") {
    setUpSerial(SELECT_PORT);
  }
}

function readSerial(data) {
  if (data != null) {
    let fromArduino = split(trim(data), ",");
    if (newVal!=prevVal){
        prevVal = newVal;
    }
    if (fromArduino.length == 1) {
      newVal = fromArduino[0];
      //print (newVal);
    }
    writeSerial(0);
  }
}

class Game {
  constructor() {
    this.state = 0;
    this.score = 0;
    this.highScore = 0;
    this.time = 60;
    this.highScore = 0;
  }

  click() {
    push();
    background(0);
    fill(255);
    textSize(width / 10);
    text("Click to start", width / 2, height / 2);
    pop();
  }

  menu() {
    if (frameCount % 5 == 0) {
      step++;
      if (step == 6) {
        x += w;
        step = 0;
      }
      if (x >= width) {
        x = 0;
      }
    }
    push();
    imageMode(CORNER);
    image(menu, 0, 0, width, height);
    //backgroundMusic.play();
    pop();
    push();
    image(bballSprite[step], x, (2 * height) / 3);
    if (fade < 255) {
      fade += 2;
    }
    fill(0, 150);
    rect(width / 4, (5 * height) / 9, width / 4, height / 7, 20);
    rect(width / 2, height / 7, 5*width / 6, height / 6, 20);
    fill(255, fade);
    textSize(width / 10);
    textFont(font);
    text("3 POINT SHOOTOUT", width/2, height/5);
    text("Start", width / 4, (3 * height) / 5);
    pop();
  }

  countdown() {
    push();
    background(0);
    fill(255);
    textSize(width / 12);
    text(cd, width / 4, height / 2);
    if ((frameCount - startTime) % 60 == 0) {
      cd--;
    }
    if (cd == 0) {
      crowdSound.setVolume(0.5);
      crowdSound.loop();
      this.state++;
      startTime = frameCount;
    }
    pop();
  }

  game() {
    push();
    imageMode(CORNER);
    image(crowd, 0, 0, width, height);
    pop();
    push();
    textSize(width / 15);
    if ((frameCount - startTime) % 60 == 0) {
      gameTime--;
    }
    text("Time left: " + gameTime, width / 4, height / 8);
    text("Score: " + this.score, width / 4, (10 * height) / 11);
    if (newVal < prevVal - 7 && time < frameCount - 30){
      let rand = int(random(6));
      sounds[rand].play();
      this.score+=3;
      time = frameCount;
    }
    if (gameTime == 0) {
      buzzer.play();
      this.state++;
      if (this.score > this.highScore) {
        this.highScore = this.score;
      }
    }
    pop();
  }
  endScreen() {
    push();
    imageMode(CORNER);
    image(crowd, 0, 0, width, height);
    pop();
    push();
    fill(255);
    textSize(width / 12);
    text("Game Over", width / 2, height / 2);
    text("Score: " + this.score, (1.5 * width) / 7, height / 4);
    text("High Score: " + this.highScore, (5 * width) / 7, height / 4);
    fill(255);
    text("Play Again", width / 2, (7 * height) / 9);
    pop();
  }
}

Here is the p5js sketch (the embedded version does not seem to work well and I can’t figure out why):

Arduino Implementation

My project only used one flex sensor so my circuit and code were fairly simple. The circuit only consisted of the flex sensor and a resistor. The flex sensor is connected to analog input. The Arduino code was basically just receiving the flex sensor value and sending it to p5js.

Here is the Arduino code:

void setup() {
  Serial.begin(9600);
  while (Serial.available() <= 0) {
    Serial.println("0,0");
    delay(300);
  }
}

void loop() {
  int sensorValue = analogRead(A0);
  Serial.println(sensorValue);

Finally, here is the game demo (I shortened the game time to 20 seconds so as to not make the video too long):

Leave a Reply