20 November 2024

Team 1 ∙ Happy Wheels Arm-mounted Controller

Photos
Schematic



Video

Description
            The conceptual model of the design for our hi-fi controller is to mimic the relation to the chaotic nature of Happy Wheels and the “physical” nature of how the game is played. Thus, the use of a leather gauntlet makes this connection by acting similar to how body stunts are performed with padded protection. Wearing the gauntlet, you are able to control the character by doing the following: Rotating your arm to the Right and Left will have the character lean Forward (Right Arrow Key) and Back respectively (Left Arrow Key), Holding your hand over the Ultrasonic Sensor (US) will drive the character backward (10-15cm away from the US) and hovering your hand closer will drive you forward (0-7cm away), “hopping” your arm upwards like a jump will have the character, if applicable, jump (Spacebar), and last, if you Double Tap your character will dismount the vehicle (Z).

The relationship between signifiers and feedback is that you will literally move your arm in a similar caliber to how the character moves within the game. This also allows the player to use the US as a sort of gas pedal where you “push” down on the US to go forward and lift off to go backward with a bit of room to allow for ease of movement into the opposite motion control. The “empty space” between distances of the US prevents accidental movement that the player did not intend to perform on the character.

Altogether, this connects to the game in the sense that you are controlling the character in ways that mimic the movement seen on screen as the character performs a multitude of actions. Thus, the affordance of the design indicates how the player should use the controller based on the game’s layout for which it is played on screen. The design of the controller is intrinsic to the game's chaotic nature by reflecting on the padded clothing that would be worn in stunt situations.

 

Questions

“What aspects of the design would make it relate more to Happy Wheels if changed?”

“What other external inputs would make the Controller more enjoyable if introduced early on?”

Code

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

#include <Keyboard.h>
#include <KeyboardLayout.h>
#include <Keyboard_da_DK.h>
#include <Keyboard_de_DE.h>
#include <Keyboard_es_ES.h>
#include <Keyboard_fr_FR.h>
#include <Keyboard_it_IT.h>
#include <Keyboard_sv_SE.h>

// Final Team 1
// Peter Ochoa & Luke Bono

// Controller Description + Mappings:
  // (+ and - vary depending on the switch toggle as we have an Ambidextrous mode for both L and R handed users)

// Accelerometer Y+ = "Tilt Forward" / Right Arrow Key (for L hand, inverse for R hand)
// Accelerometer Y- = "Tilt Backward" / Left Arrow Key (for L hand, inverse for R hand)

// Accelerometer Z+ = "Jump" / Space Bar (for L hand, inverse for R hand)

// Ultrasonic "Close" = "Forward" / Up Arrow Key (0-7cm)
// Ultrasonic "Far" = "Backward" / Down Arrow Key (10-15cm)
  // Distances in cm for 7 and 10 are intentionally left blank to account for misinput by the player and to prevent accidental key inputs=

// Double Tap = "Eject" / Z

// pin numbers
const int trigPin = A2;
const int echoPin = A3;
const int btnA = 4; // Menu Button for pausing or restarting
const int btnB = 5; // Button for pressing 'R' to restart during Menu Button press
const int flipper = 7; // Determines if the Code runs in Left or right-handed mode for the accessibility of all users! :)

// Tap Variables
const int click = 120;

// Accelerometer Variables
float accelX;
float accelY;
float accelZ;
float flat;
float threshold = 5; // minimum amount of movement to trigger input from accelerometer
int jumpTimer = -1; // ensures that after a jump input is triggered, a tap or another jump can't be triggered for half a second

// Ultrasonic Variables
bool trig; // send message to ultrasound sensor, or not
float echo;
float distanceCM;
float closeMinCM = 0; float closeMaxCM = 7;
float farMinCM = 10; float farMaxCM = 15;

// Misc. Variables
int debounce = 100; // TEMP
bool test = false;
bool killcode = false;
bool isLeft = false;

// put your setup code here, to run once:
void setup() {
  CircuitPlayground.begin();

  // set in and out pins
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  pinMode(btnA, INPUT_PULLDOWN);
  pinMode(btnB, INPUT_PULLDOWN);
  pinMode(flipper, INPUT_PULLUP);

  // Begin
  Keyboard.begin();
  Serial.begin(9600);

  // Initialize
  calibrate();
}

// Runs at the start & reset of the CPE
void calibrate() {
  // Release all keys
  Keyboard.releaseAll();
  // Stop voltage in the Trigger Pin
  digitalWrite(trigPin, LOW);

  // Determine if inputs should be interpreted for the left arm or right arm
  if (digitalRead(flipper) == HIGH) {
    isLeft = true;
  }
  else {
    isLeft = false;
  }

  // Sets the current orientation of the CPE as "flat", and the accelerometer inputs are adjusted based on this
  // (If the controller is on the left hand, usually the CPE isn't flat)
  flat = accelY = CircuitPlayground.motionY();

  // Sets up the booleans for the kill function
  test = false;
  killcode = false;

  // Allow double taps to trigger the tapTime function
  CircuitPlayground.setAccelTap(2, click);
  attachInterrupt(digitalPinToInterrupt(CPLAY_LIS3DH_INTERRUPT), tapTime, FALLING);

  // Audio signifier of calibration
  CircuitPlayground.playTone(50, 100, true);
  delay(10);
  CircuitPlayground.playTone(50, 100, true);
  delay(debounce);
}

// Triggers when the player taps, triggers kill
void tapTime(void) {
  // Ensures the player isn't jumping
  if (jumpTimer <= 0) {
    killcode = true;
  }
}

// Triggers after tapTime, prevents any inputs, except menu buttons
// (This is so tapping doesn't overlap with jumping. When you eject you are rendered useless anyways in game)
void kill(void) {
  Keyboard.releaseAll();
  Keyboard.press('z'); // press Z [EJECT]
  delay(2 * debounce);
  Keyboard.release('z');
  delay(debounce);
  // Stop double tap triggers
  CircuitPlayground.setAccelTap(0, click);
  // Stop the input code & prevent this code from running again
  test = true;
  killcode = false;
  // Turn off all keys
  Keyboard.releaseAll();
}

// put your main code here, to run  repeatedly:
void loop() {
  // PAUSE
  if (digitalRead(btnA) == HIGH) {
    Serial.println("tab");
    Keyboard.press(KEY_TAB);
    delay(debounce * 2);
    Keyboard.release(KEY_TAB);
  }

  // RESTART (if paused)
  if (digitalRead(btnB) == HIGH) {
    Serial.println("r");
    Keyboard.press('r');
    delay(debounce * 2);
    Keyboard.release('r');
    calibrate();
  }

  // Triggers kill() after a tapTime
  if(killcode){
    kill();
  }

  // Will run until kill() is triggered
  if (!test) {
    // GET INPUTS
    // send message to distance sensor for 10 milliseconds
    digitalWrite(trigPin, LOW);
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);
    echo = pulseIn(echoPin, HIGH);
    distanceCM = (echo/2) / 29.1;

    // get values from the accelerometer
    accelX = CircuitPlayground.motionX();
    accelY = CircuitPlayground.motionY();
    accelZ = CircuitPlayground.motionZ();

    // Adjusts values so we can determine rotation 180 degrees left and right from the current position
    if (accelZ < 0) { // if the CPE is upside down
      if (accelY >= 0) {
        accelY = accelY + 10;
      }
      else {
         accelY = accelY - 10;
      }
    }

    // INTERPRET INPUTS
    if (isLeft) {
      // if the accelerometer Y is positive (if tilting right)
      if (accelY > (flat + threshold)) {
        Keyboard.press(KEY_RIGHT_ARROW); // release RIGHT ARROW [TILT BACKWARDS]
        Keyboard.release(KEY_LEFT_ARROW); // press LEFT ARROW [TILT FORWARDS]
      }

      // if the accelerometer Y is negative (if tilting left)
      else if (accelY < (flat - threshold)) {
        Keyboard.press(KEY_LEFT_ARROW); // release LEFT ARROW [TILT BACKWARDS]
        Keyboard.release(KEY_RIGHT_ARROW); // press RIGHT ARROW [TILT FORWARDS]
      }

      // if little to no accelerometer input (if no tilt)
      else { // release both ARROW keys
        Keyboard.release(KEY_LEFT_ARROW);
        Keyboard.release(KEY_RIGHT_ARROW);
      }
    }
                                       
    else { //isRight, inputs are inverse of isLeft
      // (if tilting right)                                                                                                                        
      if (accelY < (flat - threshold)) {
        Keyboard.press(KEY_RIGHT_ARROW); // release RIGHT ARROW [TILT BACKWARDS]
        Keyboard.release(KEY_LEFT_ARROW); // press LEFT ARROW [TILT FORWARDS]
      }                    

      // (if tiliting left)
      else if (accelY > (flat + threshold)) {
        Keyboard.press(KEY_LEFT_ARROW); // release LEFT ARROW [TILT BACKWARDS]
        Keyboard.release(KEY_RIGHT_ARROW); // press RIGHT ARROW [TILT FORWARDS]
      }

      // (if no tilt)
      else { // release both ARROW keys
        Keyboard.release(KEY_LEFT_ARROW);
        Keyboard.release(KEY_RIGHT_ARROW);
      }
    }

    // if accelerometer Z is high (if hand raises up)
    if (isLeft && accelZ > 11 && jumpTimer <= 0) {
      Keyboard.press(' ');
      jumpTimer = 500;
    }

    // isRight, inputs are inverse of isLeft (if hand raises up)
    else if (!isLeft && accelZ < -11 && jumpTimer <= 0) {
      Keyboard.press(' ');
      jumpTimer = 500;
    }

    // if little to no accelerometer Z input (steady hand)
    else {
      Keyboard.release(' ');
    }

    // if ultrasonic senses something close to the accelerometer (0cm - 7cm)
    if (distanceCM >= closeMinCM && distanceCM < closeMaxCM) {
      Keyboard.press(KEY_UP_ARROW); // press UP ARROW [MOVE FORWARD]
      Keyboard.release(KEY_DOWN_ARROW);
      Serial.print(" forward");
    }

    // if ultrasonic senses something far from the accelerometer (10cm - 15cm)
    else if (distanceCM >= farMinCM && distanceCM < farMaxCM) {
      Keyboard.press(KEY_DOWN_ARROW); // press DOWN ARROW [MOVE BACKWARD]
      Keyboard.release(KEY_UP_ARROW);
      Serial.print(" backward");
    }

    // if ultrasonic senses something very far from the accelerometer or in the deadzone (7cm - 10cm && > 15cm)
    else {
      // release both ARROW keys
      Keyboard.release(KEY_UP_ARROW);
      Keyboard.release(KEY_DOWN_ARROW);
    }

    delay(debounce);

    // if jumpTimer is on, jumpTimer decreases by the debounce time
    if (jumpTimer > 0) {
      jumpTimer = jumpTimer - debounce;
    }
    Serial.println("");
  }
}  

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.