Final – Shereena AlNuaimi

VR Archery

My game was all about creating a virtual archery game and linking a flex sensor to the bow, so it detects the bending motion and once bent the arrow shoots onto the target. For the theme of the entire game, I decided to link it with my childhood as well and use Minecraft as the main theme. As a 9 year old, I used to go to the archery range and play Minecraft at the time so it brought up a sense of nostalgic feeling.

For the bow, in order to give it a pixelated look, I decided to super glue the tiny wooden square blocks and paint over it. I attached the flex sensor on the top of the bow and attached a string to the flex sensor so when the string is pulled it shoots the arrow.

Future Improvements:

For future improvements, I’d like to add more visuals. I felt like it would’ve been more engaging if I did.

Pictures and Documentation:

Soldering documentation :

https://youtube.com/shorts/Cn9bonfPVeI?feature=share

Game play:

https://youtube.com/shorts/6olYsuVx5k8?feature=share

The Game & Codes:

<iframe src=”https://editor.p5js.org/sa6607/full/wcM5zq8Fr”></iframe>

P5 Code:

const serial = new p5.WebSerial();
let startButton;
let portButton;
let closeButton;
let sensorValue = 0;
let width = 900;
let height = 506;
let arrowSpeed = 5; // Speed at which arrow moves
let arrowDirection = 1;
let score = 0;
let shooting = false; // Indicates whether the arrow is currently being shot
let arrowX = width / 2; // X-coordinate of the arrow
let arrowY = height; // Y-coordinate of the arrow
let arrowScaleX = 1;
let arrowScaleY = 1;
let arrowWidth = 60;
let arrowHeight = 120;
let targetX = width / 2;
let targetY = height / 2;
let targetRadius = 100;

let started = false;

let bgImg;
let arrowImg;
let targetImg;
let sliderX = width - 50;
let sliderY = height / 2;
let lastScore = 0;
let textOpacity = 0;
let sliderHeight = 0;
let startShootFlag = 0;
let sliderIncrementor = 1;
let sliderTotalHeight = 100;
let startShootThreshHold = 40; //set flex sensor value at which we start for targetting

function allSerialStuff() {
  if (!navigator.serial) {
    alert("WebSerial is not supported in this browser. Try Chrome or MS Edge.");
  }
  // check for any ports that are available:
  serial.getPorts();
  // if there's no port chosen, choose one:
  serial.on("noport", makePortButton);
  // open whatever port is available:
  serial.on("portavailable", openPort);
  // handle serial errors:
  serial.on("requesterror", portError);
  // handle any incoming serial data:
  serial.on("data", serialEvent);
  serial.on("close", makePortButton);
  // add serial connect/disconnect listeners:
  navigator.serial.addEventListener("connect", portConnect);
  navigator.serial.addEventListener("disconnect", portDisconnect);
}

function serialEvent() {
  sensorValue = Number(serial.read());
  console.log(sensorValue);
  //if certain value from flex sensor get passed we get prepared for the shoot
  if (!shooting && sensorValue > startShootThreshHold) {
    startShootFlag = 1;
    sliderHeight = sensorValue + 20; // add 20 to elevate the value we need something in between 0-120
  }
  //if that value again crossed then we shoot
  if (!shooting && sensorValue < startShootThreshHold && startShootFlag) {
    startShootFlag = 0;
    if (!shooting && arrowY == height) {
      shooting = true; // Start shooting
    }
  }
}

// if there's no port selected,
// make a port select button appear:
function makePortButton() {
  // create and position a port chooser button:
  portButton = createButton("Choose Port");
  portButton.position(innerWidth / 2, 10);
  portButton.center("horizontal");
  // give the port button a mousepressed handler:
  portButton.mousePressed(choosePort);
}

// make the port selector window appear:
function choosePort() {
  if (portButton) portButton.show();
  serial.requestPort();
}

// open the selected port, and make the port
// button invisible:
// open the selected port, and make the port
// button invisible:
function openPort() {
  // wait for the serial.open promise to return,
  // then call the initiateSerial function
  serial.open().then(initiateSerial);

  // once the port opens, let the user know:
  function initiateSerial() {
    console.log("port open");
  }
  // hide the port button once a port is chosen:
  if (portButton) portButton.hide();
  makeCloseButton();
  if (closeButton) closeButton.show();
}

// pop up an alert if there's a port error:
function portError(err) {
  alert("Serial port error: " + err);
}
// read any incoming data as a string
// (assumes a newline at the end of it):

// try to connect if a new serial port
// gets added (i.e. plugged in via USB):
function portConnect() {
  console.log("port connected");
  serial.getPorts();
}

// if a port is disconnected:
function portDisconnect() {
  serial.close();
  console.log("port disconnected");
}

// if there's no port selected,
// make a port select button appear:
function makeCloseButton() {
  // create and position a port chooser button:
  closeButton = createButton("Close Port");
  closeButton.position(innerWidth / 2, 10);
  closeButton.center("horizontal");
  // give the close port button a mousepressed handler:
  closeButton.mousePressed(closePort);
}

function closePort() {
  serial.close();
  if (closeButton) closeButton.hide();
}

function preload() {
  bgImg = loadImage("/assets/background.jpg");
  targetImg = loadImage("/assets/target.png");
  arrowImg = loadImage("/assets/arrow.png");
}

function setup() {
  createCanvas(width, height);

  startButton = createButton("Start Game");
  startButton.addClass("start-button");
  startButton.position(innerWidth / 2, innerHeight / 2 + 10);
  startButton.center("horizontal");
  startButton.mousePressed(startGame);

  allSerialStuff();
}

function draw() {
  imageMode(CORNERS);
  image(bgImg, 0, 0, width, height);

  if (!started) {
    drawMenu();
  } else {
    // Draw target
    drawTarget();

    if (startShootFlag) {
      drawSlider();
    }

    //Draw score
    textAlign(LEFT, TOP);
    textSize(26);
    fill("red");

    text("Score: " + score, 10, 10);

    makeShooting();
    drawArrow();
    drawAddedScore();
  }
}

function drawMenu() {
  textSize(48);
  fill("#ff0033");
  textStyle(BOLD);
  textAlign(CENTER, BASELINE);
  text("VR Archery", width / 2, height / 2 - 100);
}

function startGame() {
  if (startButton) startButton.hide();
  started = true;
}

function makeShooting() {
  if (shooting) {
    // Calculate the trajectory towards the target
    let deltaY = height - targetY; // Difference in y-coordinates between arrow and target
    arrowY -= deltaY / 50; // Move the arrow towards the target
    if (arrowScaleY > 0.4) {
      arrowScaleY -= 0.004;
    }
    if (arrowScaleX > 0.4) {
      arrowScaleY -= 0.005;
    }
    // Stop shooting when arrow reaches the target
    if (arrowY - (arrowHeight * arrowScaleY) / 2 <= targetY) {
      shooting = false;

      // Check if the arrow hits the target
      let distance = dist(arrowX, arrowY - (arrowHeight * arrowScaleY) / 2, targetX, targetY); // Calculate distance between arrow tip and target center
      if (distance <= targetRadius - 20) {
        console.log("Hit!");
        arrowSpeed = 0;
        textOpacity = 255;
        updateScore(distance);
        //reset arrow after 2 seconds
        setTimeout(() => {
          arrowY = height;
          arrowScaleX = 1;
          arrowScaleY = 1;
          arrowSpeed = 5;
          textOpacity = 0;
        }, 2000);
      } else {
        console.log("Miss!");
        arrowY = height;
        arrowScaleX = 1;
        arrowScaleY = 1;
        shooting = false;
      }
    }
  } else {
    // Move arrow
    arrowX += arrowSpeed * arrowDirection;
    if (arrowX >= width || arrowX <= 0) {
      arrowDirection = -arrowDirection; // Reset arrow when it goes beyond the canvas
    }
  }
}

function drawTarget() {
  imageMode(CENTER);
  image(targetImg, targetX, targetY, targetRadius * 2, targetRadius * 2);
}

function drawArrow() {
  imageMode(CENTER);
  image(arrowImg, arrowX, arrowY, arrowWidth * arrowScaleX, arrowHeight * arrowScaleY);
}

function drawAddedScore() {
  fill(80, textOpacity);
  text("+ " + lastScore, targetX + 70, targetY - 60);
}

function drawSlider() {
  line(sliderX, sliderY, sliderX, sliderY + 120);
  line(sliderX - 10, sliderY, sliderX + 10, sliderY);
  line(sliderX - 10, sliderY + 60, sliderX + 10, sliderY + 60);
  line(sliderX - 10, sliderY + 120, sliderX + 10, sliderY + 120);
  fill(sliderHeight + 100, 200, 0);
  rect(sliderX - 10, sliderY, 20, sliderHeight);
}

//update score
function updateScore(distance) {
  console.log(distance);
  if (distance < 10) {
    score += 100;
    lastScore = 100;
  } else if (distance < 25) {
    score += 80;
    lastScore = 80;
  } else if (distance < 40) {
    score += 60;
    lastScore = 60;
  } else if (distance < 55) {
    score += 40;
    lastScore = 40;
  } else {
    score += 20;
    lastScore = 20;
  }
}

//handle mouse click event
function mouseClicked() {
  if (!shooting && arrowY == height) {
    //If arrow is not currently being shot
    shooting = true; // Start shooting
  }
}

function windowResized() {
  if (startButton) startButton.position(innerWidth / 2, innerHeight / 2 + 10).center("horizontal");
  if (portButton) portButton.center("horizontal");
  if (closeButton) closeButton.center("horizontal");
}

Arduino Code:

void setup() {
  Serial.begin(9600);
}

void loop() {
  int analogValue = analogRead(A0);
  byte byteToSend = map (analogValue, 0, 1023, 0, 255);
  Serial.write(byteToSend);
  delay(50);
}

 

Leave a Reply