04 December 2023

Game Controller - The Bite of '87 (Clair Elso)


 

Hello hello? Hey, you’re doing great! Most people don’t last this long!…I mean, y’know. They usually move on to other classes by now. I’m not implying that they failed, that’s not what I meant…anyways, I better not take up too much of your time, things start getting real on this blog post - at least you’d hope so, given that it’s the final one. We’ve reached the end of this semester, which means it’s time for the final controller design!

 


This controller is meant to mimic the layout of the office you’re located in during FNAF 1, for a more immersive experience. Unfortunately, having tiny little door buttons would be far too finesse for a game where you’ll likely be jumping any time you need to press a button. So, I chose touch sensors for the door buttons to mimic the idea of slamming a door button. Using light sensors seemed like an obvious choice, but wound up having some ironic logic: you cover the light sensor in order to turn on the lights in-game. Maybe it’s something like turning out the overhead lights to find something glowing in your room. There are also 4 total LEDs (two on each side) used to signal the state of the doors & lights in-game: one RGB LED that changes between red and green depending on the door’s status of open or closed, and one white LED that lights up/flickers when the lights are active.

 

//left half, CPE & pot

#include <Mouse.h>
#include <Adafruit_CircuitPlayground.h>
#include <Adafruit_Circuit_Playground.h>

// inputs
int touchSen = 6;
int lightSen = A2;
int pot = A7;

// outputs
int wLED = 10;
int rLED = A6;
int gLED = A5;

// variables for running
bool doorShut;
bool lightOn;
int potVal;

void setup() {
  Serial.begin(9600);
  Mouse.begin();
  Mouse.move(0, 0, 0);    // I think??? this moves the mouse to the bottom left corner of the screen
  pinMode(touchSen, INPUT);
  pinMode(wLED, OUTPUT);
  doorShut = false;
  lightOn = false;
  analogWrite(rLED, 255);
  delay(100);
}

void loop() {
  if(digitalRead(touchSen)) {
    Mouse.move(30, 500, 0);    // I'll be honest I'm estimating values here bc my CPE broke
    Mouse.click();
    delay(20);
    if(doorShut == false) {
      analogWrite(gLED, 255);
      analogWrite(rLED, 0);
      doorShut = true;
      delay(100);
    } else if(doorShut == true) {
      analogWrite(rLED, 255);
      analogWrite(gLED, 0);
      doorShut = false;
      delay(100);
    }
  }
 
  if(lightOn == false) {
    Mouse.move(40, 550, 0);
    Mouse.click();
    delay(20);
    if(map(analogRead(lightSen), 0, 1023, 0, 10) < 4) {
      digitalWrite(wLED, HIGH);
      lightOn = true;
      delay(100);
    }
  }
  else if(lightOn == true) {
    Mouse.move(40, 550, 0);
    Mouse.click();
    delay(20);
    if(map(analogRead(lightSen), 0, 1023, 0, 10) >= 4) {
      digitalWrite(wLED, LOW);
      lightOn = false;
      delay(100);
    }
  }

  potVal = map(analogRead(pot), 0. 1023, 0, 11);    // cycle through 11 cams, plus first option for shutting them
  switch(potVal) {
    case 1:
      // move mouse & click for cam 1a
      delay(100);
      break;
    case 2:
      // move mouse & click for cam 1b
      delay(100);
      break;
    case 3:
      // move mouse & click for cam 1c
      delay(100);
      break;
    case 4:
      // move mouse & click for cam 2a
      delay(100);
      break;
    case 5:
      // move mouse & click for cam 2b
      delay(100);
      break;
    case 6:
      // move mouse & click for cam 3
      delay(100);
      break;
    case 7:
      // move mouse & click for cam 4a
      delay(100);
      break;
    case 8:
      // move mouse & click for cam 4b
      delay(100);
      break;
    case 9:
      // move mouse & click for cam 5
      delay(100);
      break;
    case 10:
      // move mouse & click for cam 6
      delay(100);
      break;
    case 11:
      // move mouse & click for cam 7
      delay(100);
      break;
    default:
      // move mouse to shut cameras
      delay(100);
      break;
  }
}

 

 
//right half, CPB & toggle

#include <Mouse.h>
#include <Adafruit_TinyUSB.h>
#include <Adafruit_CircuitPlayground.h>
#include <Adafruit_Circuit_Playground.h>

// inputs
int touchSen = 1;
int lightSen = A3;
int tog = 12;

// outputs
int wLED = 3;
int rLED = A1;
int gLED = A2;

// variables for running
bool doorShut;
bool lightOn;
bool togOn;
int togVal;

void setup() {
  Serial.begin(9600);
  Mouse.begin();
  Mouse.move(0, 0, 0); // I'm assuming this moves it to bottom left corner
  delay(20);
  pinMode(touchSen, INPUT);
  pinMode(wLED, OUTPUT);
  pinMode(togVal, INPUT_PULLUP);
  doorShut = false;
  lightOn = false;
  togOn = false;
  analogWrite(rLED, 255);
  delay(100);
}

void loop() {
  if(digitalRead(touchSen)) {
    Mouse.move(1000, 500, 0);   // estimating values again
    Mouse.click();
    if(doorShut == false) {
      analogWrite(gLED, 255);
      analogWrite(rLED, 0);
      doorShut = true;
      delay(100);
    } else if(doorShut == true) {
      analogWrite(rLED, 255);
      analogWrite(gLED, 0);
      doorShut = false;
      delay(100);
    }
  }
 
  if(lightOn == false) {
    Mouse.move(990, 550, 0);   // estimating values again
    Mouse.click();
    if(map(analogRead(lightSen), 0, 1023, 0, 10) < 4) {
      digitalWrite(wLED, HIGH);
      lightOn = true;
      delay(100);
    }
  }
  else if(lightOn == true) {
    Mouse.move(990, 550, 0);   // estimating values again
    Mouse.click();
    if(map(analogRead(lightSen), 0, 1023, 0, 10) >= 4) {
      digitalWrite(wLED, LOW);
      lightOn = false;
      delay(100);
    }
  }

  togVal = digitalRead(tog); // I'm realizing now that I mis-wired this; probably needed two different values sent to the CPB to designate direction

  Mouse.move(0, 0, 0);   // reset mouse position

}

 

The potentiometer and toggle switch in the stage floor are parallels, yet opposites. The toggle switch allows for camera movement; since the game is controlled with the mouse, this is the only way you’re able to move the game camera without pressing buttons you don’t want to. The potentiometer is how you control the cameras; clamped into 10 sections, with the first one meaning that the cameras are closed, and the subsequent 9 opening the 9 security cameras you see in the game. It’ll lead to a lot of flashing cameras, but luckily with FNAF 1 it’s not seeing the big picture that’s important, it’s tricking the animatronics - specifically Foxy - into thinking you’re looking at him.

 

(Video)

 

For my design question: what kinds of props should I add to the miniature office?

 

Thanks for sticking with this project for this long. Catch you on the flip side!

27 November 2023

Game Controller: Five Night's at Freddy's

Vision & Disclaimer 

For the final project I decided to create a game controller for Five Nights at Freddy's. Taking into consideration the gameplay and theming I decided to turn the laptop the player uses in-game into the controller in an attempt to better immerse the player in the gameplay. One of the biggest challenges I faced was realizing that FnAF isn't actually played using any hotkeys or keyboard controls and is only controlled using the mouse. Given that, I must state as a disclaimer, that I found external code that takes keyboard commands and converts it into mouse movements on the screen, thus emulating how FnAF could be played with only the keyboard.

Controller Description

 With that sorted, here's the description for the actual controller. The "laptop" would sit on the player's lap or on the desk in front of them and then be controlled using the various sensors. When the "laptop" is opened, the laptop in game is opened to view the cameras, and then using the potentiometer and the position switch the player can cycle through each of the cameras; closing the laptop of course closes it in game as well. The position switch is used to control the doors while the "laptop" is closed, and the potentiometer and photoresistor work in tandem to turn the lights on and off depending on brightness and position of the potentiometer.

Peer Question

Although the player would be looking at the actual computer screen most times do you think more visual aids on the controller itself would be useful?












#include <bluefruit.h>
#include <Adafruit_TinyUSB.h>
#include <Adafruit_CircuitPlayground.h>
#include <Adafruit_Circuit_Playground.h>

//HID report descriptor using TinyUSB's template
//Single report (no ID) descriptor
uint8_t const desc_hid_report[] =
{
  TUD_HID_REPORT_DESC_KEYBOARD()
};

// USB HID object. For ESP32 these values cannot be changed after this declaration
// desc report, desc len, protocol, interval, use out endpoint
Adafruit_USBD_HID usb_hid(desc_hid_report, sizeof(desc_hid_report), HID_ITF_PROTOCOL_KEYBOARD, 2, false);
//https://github.com/hathach/tinyusb/blob/master/src/class/hid/hid.h


float y; //CPB Accelerometer
bool computerOpen = false; //check if screen is opened
bool leftDoorClosed = false; // check if left door is closed
bool rightDoorClosed = false; // check if right door is closed

//Check if cameras are active
bool cam0 = false;
bool cam1 = false;
bool cam2 = false;
bool cam3 = false;
bool cam4 = false;
bool cam5 = false;
bool cam6 = false;
bool cam7 = false;
bool cam8 = false;
bool cam9 = false;
bool cam1A = false;
bool cam2B = false;
//check if lights are on
bool leftLight = false;
bool rightLight = false;
void setup() {
  CircuitPlayground.begin();
  usb_hid.begin();
  pinMode(CPLAY_SLIDESWITCHPIN, INPUT_PULLUP);
  pinMode(A1, INPUT_PULLUP); //Toggle Switch input 1
  pinMode(A5, INPUT_PULLUP);//Toggle Switch input 2
  Serial.begin(115200);

  delay(1000);

}

void loop() {
  uint8_t const report_id = 0;  //unsigned 8bit (1 byte) int
  uint8_t modifier = 0;         //used by libraries to save space
  uint8_t keycode[6] = { 0, 0 };   //normal int is 16 bit (4 bytes)

    y = CircuitPlayground.motionY() * -1; //Accelerometer Y value
    int toggleValue1 = analogRead(A1); //toggle switch 1 output
    int toggleValue2 = analogRead(A5); //toggle switch 2 output
    int knobValue = analogRead(A3); //potentiometer values
    int brightness = analogRead(A4); //Photoresistor value
  if(digitalRead(CPLAY_SLIDESWITCHPIN))
  {
    Serial.println(y);
  //Opens and closes computer screen
   if(y > 7.2  && computerOpen == false)
   {
    computerOpen = true;
    keycode[0] = HID_KEY_SPACE;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(50);
    usb_hid.keyboardRelease(0);
    }
   if(y < 4 && computerOpen == true)
   {
    computerOpen = false;
    keycode[0] = HID_KEY_SPACE;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(50);
    usb_hid.keyboardRelease(0);
   }

   //controls left and right doors
   //Left Door close
   if (toggleValue1 >= 900 && toggleValue2 <= 350 && computerOpen == false && leftDoorClosed == false) {
    keycode[0] = HID_KEY_Q;
    leftDoorClosed = true;
    rightDoorClosed = false;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(5);
    usb_hid.keyboardRelease(0);
  }
  //Do nothing if both doors open and switch in middle
  if (toggleValue1 >= 900 && toggleValue2 >= 900 && computerOpen == false) {
    keycode[0] = HID_NULL_STATE;
    leftDoorClosed = false;
    rightDoorClosed = false;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(5);
    usb_hid.keyboardRelease(0);
  }
  //Right Door close
  if (toggleValue2 >= 900 && toggleValue1 <= 350 && computerOpen == false && rightDoorClosed == false) {
    keycode[0] = HID_KEY_P;
    leftDoorClosed = false;
    rightDoorClosed = true;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(5);
    usb_hid.keyboardRelease(0);
  }


  //Controls left and right lights
  //Left light on
   if(knobValue < 500 && brightness >= 850 && computerOpen == false && leftLight == false)
   {
    keycode[1] = HID_KEY_A;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(75);
    leftLight = true;
    usb_hid.keyboardRelease(0);
   }
   //Right light on
    if(knobValue > 600 && brightness >= 850 && computerOpen == false && rightLight == false)
   {
    keycode[1] = HID_KEY_L;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(75);
    rightLight = true;
    usb_hid.keyboardRelease(0);
   }
   //left light off
    if(knobValue < 500 && brightness <= 750 && computerOpen == false && leftLight == true)
   {
    keycode[1] = HID_KEY_A;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(75);
    leftLight = false;
    usb_hid.keyboardRelease(0);
   }
   //Right light off
    if(knobValue > 600 && brightness < 750 && computerOpen == false && rightLight == true)
   {
    keycode[0] = HID_KEY_L;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(75);
    rightLight = false;
    usb_hid.keyboardRelease(0);
   }
   
  //If statements for Num Pad to select cameras
   if(knobValue <=99 && knobValue > 10 && computerOpen == true && cam0 == false)
   {
    cam0 = true;
    cam1 = false;
    cam2 = false;
    cam3 = false;
    cam4 = false;
    cam5 = false;
    cam6 = false;
    cam7 = false;
    cam8 = false;
    cam9 = false;
    keycode[0] = HID_KEY_KEYPAD_0;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(10);
    usb_hid.keyboardRelease(0);
   }
   if(knobValue <=199 && knobValue >99 && computerOpen == true && cam1 == false)
   {
    cam0 = false;
    cam1 = true;
    cam2 = false;
    cam3 = false;
    cam4 = false;
    cam5 = false;
    cam6 = false;
    cam7 = false;
    cam8 = false;
    cam9 = false;
    keycode[0] = HID_KEY_KEYPAD_1;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(10);
    usb_hid.keyboardRelease(0);
   }
   if(knobValue <=299 && knobValue >199 && computerOpen == true && cam2 == false)
   {
    cam0 = false;
    cam1 = true;
    cam2 = false;
    cam3 = false;
    cam4 = false;
    cam5 = false;
    cam6 = false;
    cam7 = false;
    cam8 = false;
    cam9 = false;
    keycode[0] = HID_KEY_KEYPAD_2;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(10);
    usb_hid.keyboardRelease(0);
   }
   if(knobValue <=399 && knobValue >299 && computerOpen == true && cam3 == false)
   {
    cam0 = false;
    cam1 = false;
    cam2 = false;
    cam3 = true;
    cam4 = false;
    cam5 = false;
    cam6 = false;
    cam7 = false;
    cam8 = false;
    cam9 = false;
    keycode[0] = HID_KEY_KEYPAD_3;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(10);
    usb_hid.keyboardRelease(0);
   }
   if(knobValue <=499 && knobValue >399 && computerOpen == true && cam4 == false)
   {
    cam0 = false;
    cam1 = false;
    cam2 = false;
    cam3 = false;
    cam4 = true;
    cam5 = false;
    cam6 = false;
    cam7 = false;
    cam8 = false;
    cam9 = false;
    keycode[0] = HID_KEY_KEYPAD_4;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(10);
    usb_hid.keyboardRelease(0);
   }
   if(knobValue <=599 && knobValue >499 && computerOpen == true && cam5 == false)
   {
    cam0 = false;
    cam1 = false;
    cam2 = false;
    cam3 = false;
    cam4 = false;
    cam5 = true;
    cam6 = false;
    cam7 = false;
    cam8 = false;
    cam9 = false;
    keycode[0] = HID_KEY_KEYPAD_5;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(10);
    usb_hid.keyboardRelease(0);
   }
   if(knobValue <=699 && knobValue >599 && computerOpen == true && cam6 == false)
   {
    cam0 = false;
    cam1 = false;
    cam2 = false;
    cam3 = false;
    cam4 = false;
    cam5 = false;
    cam6 = true;
    cam7 = false;
    cam8 = false;
    cam9 = false;
    keycode[0] = HID_KEY_KEYPAD_6;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(10);
    usb_hid.keyboardRelease(0);
   }
   if(knobValue <=799 && knobValue >699 && computerOpen == true && cam7 == false)
   {
    cam0 = false;
    cam1 = false;
    cam2 = false;
    cam3 = false;
    cam4 = false;
    cam5 = false;
    cam6 = false;
    cam7 = true;
    cam8 = false;
    cam9 = false;
    keycode[0] = HID_KEY_KEYPAD_7;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(10);
    usb_hid.keyboardRelease(0);
   }
   if(knobValue <=899 && knobValue >799 && computerOpen == true && cam8 == false)
   {
    cam0 = false;
    cam1 = false;
    cam2 = false;
    cam3 = false;
    cam4 = false;
    cam5 = false;
    cam6 = false;
    cam7 = false;
    cam8 = true;
    cam9 = false;
    keycode[0] = HID_KEY_KEYPAD_8;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(10);
    usb_hid.keyboardRelease(0);
   }
   if(knobValue >=900 && computerOpen == true && cam9 == false)
   {
    cam0 = false;
    cam1 = false;
    cam2 = false;
    cam3 = false;
    cam4 = false;
    cam5 = false;
    cam6 = false;
    cam7 = false;
    cam8 = false;
    cam9 = true;
    keycode[0] = HID_KEY_KEYPAD_9;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(10);
    usb_hid.keyboardRelease(0);
   }
   if(toggleValue1 >= 900 && toggleValue2 <= 200 && computerOpen == true && knobValue < 20)
   {
      cam0 = false;
    cam1 = false;
    cam2 = false;
    cam3 = false;
    cam4 = false;
    cam5 = false;
    cam6 = false;
    cam7 = false;
    cam8 = false;
    cam9 = false;
    cam1A = true;
    cam2B = false;
    keycode[0] = HID_KEY_1;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(10);
    usb_hid.keyboardRelease(0);
   }
      if(toggleValue2 >= 900 && toggleValue1 <= 200 && computerOpen == true && knobValue < 20)
   {
    cam0 = false;
    cam1 = false;
    cam2 = false;
    cam3 = false;
    cam4 = false;
    cam5 = false;
    cam6 = false;
    cam7 = false;
    cam8 = false;
    cam9 = false;
    cam1A = false;
    cam2B = true;
     keycode[0] = HID_KEY_2;
    usb_hid.keyboardReport(report_id, modifier, keycode);
    delay(10);
    usb_hid.keyboardRelease(0);
   }
   }
}