26 November 2024

Final Controller Project Team 9

 


The conceptual model for our controller was a glow stick that you can dance with at a concert reminiscent of the high energy pop start era featuring lots of holographic projections. The main affordance of the controller are the movement featuring four inputs sent by rotating the controller left, right, and up with the accompaniment of a potentiometer not only mapped to a gradient in the LED stick, but also dictating wether a vertical shake is sending up or down input based on the positioning of the slider. The color of the LEDs is also mapped to the external accelerometer/gyroscope that makes the LEDs blink a greenish color when shaken enough to trigger an input. This gives the player visual feedback to tell whether they’re doing the correct movement. This all connects back to the rhythm aspect of the game since timing is very important and light feedback makes the activity more engaging and visually striking which parallels the striking visuals presented by the game during each song.How well do you think our conceptual model is portrayed through the design choices made, and how would you improve the design to help facilitate engaging gameplay?


Marcus was the programmer/stress tester and Sarah was the designer and modeler. We both worked on the wiring and schematic, as well the concept and mechanics


#include <Keyboard.h>
#include <KeyboardLayout.h>

// Marcus Werner
// Sarah Walenciak

// include libraries
#include <Adafruit_CircuitPlayground.h>
#include <Adafruit_MPU6050.h>

#define NEOPIX_PIN    A2
#define NUM_PIXELS    10
#define DEBUG_MODE    1

Adafruit_CPlay_NeoPixel strip = Adafruit_CPlay_NeoPixel(NUM_PIXELS, NEOPIX_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_MPU6050 mpu;

int sensorPin = A0;   // select the input pin for the potentiometer
int sensorValue = 0;
int r = 255;
int b = 0;
int gPix = 0;

// Booleans for the directional input (WASD) and the slide inputs
bool up = false;
bool down = false;
bool left = false;
bool right = false;
bool slideLeft = false;
bool slideRight = false;

float x, y, z; // Track accel values continuously
float x2, y2, z2; // Update accel values when needed to use in conditionals

const int btnA = 4;
bool lock = false;

float filter_coeff = 0.1;
float filtered_shake = 0;
float threshold = 10;
float thresholdColor = 10;
float thresholdSlide = 10;

void setup()
{
  #if defined DEBUG_MODE
    Serial.begin(115200);
  #endif

  CircuitPlayground.begin();
  strip.begin();
  Keyboard.begin();

  pinMode(btnA, INPUT_PULLDOWN);

  while (!Serial) {
    delay(10); // will pause Zero, Leonardo, etc until serial console opens
  }

  // Try to initialize!
  if (!mpu.begin()) {
    Serial.println("Failed to find MPU6050 chip");
    while (1) {
      delay(10);
    }
  }

  mpu.setAccelerometerRange(MPU6050_RANGE_16_G);
  mpu.setGyroRange(MPU6050_RANGE_250_DEG);
  mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
  Serial.println("");
  delay(100);
}

void loop()
{
  // Initialize values for the potentiometer
  sensors_event_t a, g, temp;
  mpu.getEvent(&a, &g, &temp);

  // Continuously tracks accel values on x, y, and z axes
  x = a.acceleration.x;
  y = a.acceleration.y;
  z = a.acceleration.z;

  // Sets secondary accel values when not actively shaking (to use in conditionals)
  if (filtered_shake < threshold)
  {
    x2 = x;
    y2 = y;
    z2 = z;
  }

  // Determine standardized value for shake detection
  float shake = sqrt(x*x + y*y + z*z);
  shake -= 9.81; // subtract gravity
  shake *= shake;

  filtered_shake = (shake * filter_coeff) + (filtered_shake * (1 - filter_coeff));

  #if defined DEBUG_MODE
  if (filtered_shake > 1)
  {
    Serial.println(filtered_shake);
  }
  delay(10);
  #endif

   /* Print out the values */
  Serial.print("AccelX:");
  Serial.print(x);
  Serial.print(",");
  Serial.print("AccelY:");
  Serial.print(y);
  Serial.print(",");
  Serial.print("AccelZ:");
  Serial.print(z);
  Serial.print(", ");


  //CircuitPlayground.clearPixels();
 
  // Sets external LEDs to on
  strip.show();

  // read the value from the potentiometer:
  sensorValue = analogRead(sensorPin);

  // Map the red and blue value of the LEDs to each extreme of the potentiometer
  r = map(sensorValue, 0, 1023, 255, 0);
  b = map(sensorValue, 0, 1023, 0, 255);

  strip.setPixelColor(0, r, gPix, b);
  strip.setPixelColor(1, r, gPix, b);
  strip.setPixelColor(2, r, gPix, b);
  strip.setPixelColor(3, r, gPix, b);
  strip.setPixelColor(4, r, gPix, b);
  strip.setPixelColor(5, r, gPix, b);
  strip.setPixelColor(6, r, gPix, b);
  strip.setPixelColor(7, r, gPix, b);
  strip.setPixelColor(8, r, gPix, b);
  strip.setPixelColor(9, r, gPix, b);

  // Compares secondary accel x value to threshold of 9.0 and ensures left or right is not also chosen already
  // If all is satisfied, up is set to true
  if (x2 > 8.0 && b > 200 && !left && !right)
  {
    up = true;
  }
  else
  {
    up = false;
  }

  // Compares secondary accel x value to threshold of -8.0 and ensures left or right is not also chosen already
  // If all is satisfied, down is set to true
  if (x2 > 8.0 && r > 200 && !left && !right)
  {
    down = true;
  }
  else
  {
    down = false;
  }

  // Compares secondary accel y value to threshold of 9.0 and ensures up or down is not also chosen already
  // If all is satisfied, right is set to true
  if (y2 > 5.0 && !up && !down)
  {
    right = true;
  }
  else
  {
    right = false;
  }

  // Compares secondary accel y value to threshold of -9.0 and ensures up or down is not also chosen already
  // If all is satisfied, left is set to true
  if (y2 < -5.0 && !up && !down)
  {
    left = true;
  }
  else
  {
    left = false;
  }

  // If the potentiometer is slid to the blue extreme, slide left input is stored
  /*if (b = 255)
  {
    slideLeft = true;
  }
  else
  {
    slideRight = false;
    //Keyboard.release('A');
  }*/

  // The filtered shake value is compared to the predetermined threshold to sense if the controller is being shaken enough
  if (filtered_shake > threshold)
  {
    if (up) // If the controller is aimed up and shaken, 'W' is sent as an input
    {
      if (!lock)
      {
        Keyboard.press('W');
        //Keyboard.press(KEY_UP_ARROW);
      }
    }
    if (down) // If the controller is aimed down and shaken, 'S' is sent as an input
    {
      if (!lock)
      {
        Keyboard.press('S');
        //Keyboard.press(KEY_DOWN_ARROW);
      }
    }
    if (left) // If the controller is tilted left and shaken, 'A' is sent as an input
    {
      if (!lock)
      {
        Keyboard.press('A');
        //Keyboard.press(KEY_LEFT_ARROW);
      }
    }
    if (right) // If the controller is tilted right and shaken, 'D' is sent as an input
    {
      if (!lock)
      {
        Keyboard.press('D');
        //Keyboard.press(KEY_RIGHT_ARROW);
      }
    }
  }
  else // In between shakes, all inputs are released
  {
    Keyboard.release('W');
    Keyboard.release('A');
    Keyboard.release('S');
    Keyboard.release('D');
  }

  // Compared filtered shake to the threshold determined for the LEDs
  if (filtered_shake > thresholdColor)
  {
    if (up) // If shaken enough (less than needed for input to be sent) the green value of LEDs is set to 255
    {
      gPix = 255;
    }
    if (down) // If shaken enough (less than needed for input to be sent) the green value of LEDs is set to 255
    {
      gPix = 255;
    }
    if (left) // If shaken enough (less than needed for input to be sent) the blue value of LEDs is set to 255
    {
      b = 255;
    }
    if (right) // If shaken enough (less than needed for input to be sent) the blue value of LEDs is set to 255
    {
      b = 255;
    }
  }
  else
  {
    gPix = 0;
    b = 0;
  }

  // If the controller is aimed forward then it's free to send the input for the slides
  if (!up && !down && !left && !right)
  {
    if (filtered_shake > thresholdSlide) // Compares filtered shake to the threshold of the slides
    {
      if (slideLeft) // If shaken and slid to the left/blue extreme, the left slide input is sent
      {
        if (!lock)
        {
          //Keyboard.press('A');
        }
      }
      else if (slideRight) // If shaken and slid to the left/blue extreme, the right slide input is sent
      {
        if (!lock)
        {
          //Keyboard.press('D');
        }
      }
    }
    else
    {
      //Keyboard.release('A');
      //Keyboard.release('D');
    }
  }

 https://youtu.be/n8kHLZAKKR4

Final Project Team 16 Doom Gun Controller

    

Our game controller was created for the first person shooter Doom and we wanted the player to feel as if they were the ones running and gunning through the pits of hell killing demons.  To accomplish this goal we have created a controller in the shape of a gun appose to a standard controller, assigning the circuit playground express’s accelerometer movements on the Z and X axis to movement to feel as if the player is traveling through the game leaning to which way they want to travel.  Along with this we have assigned the accelerometer Y axis to the mouse click to fire making the player to make a recoil like motion to simulate as if the weapon is firing.  Since there is no need to look up and down we put a potentiometer on our controller to make the player look left and right along with a switch to interact with items in the game such as doors.  Two questions we have for our peers would be, what other inputs would you use to simulate this game experience and what could we have done to improve the look and feel of our controller to fit more aesthetically with the game we have chosen.






// Libraries
#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_hu_HU.h>
#include <Keyboard_it_IT.h>
#include <Keyboard_pt_PT.h>
#include <Keyboard_sv_SE.h>

#include <Mouse.h>

int debounce = 10;

// Mouse Variables
const int xAxis = A1;         // Analog sensor for X axis
const int yAxis = A2;         // Analog sensor for Y axis
float wheel;                  // Scroll wheel

int range = 12;               // Output range of X or Y movement
int responseDelay = 2;        // Response delay of the mouse, in ms
int threshold = range / 4;    // Resting threshold
int center = range / 2;       // Resting position value
int minima[] = {1023, 1023};  // Actual analogRead minima for (x, y)
int maxima[] = {0, 0};        // Actual analogRead maxima for (x, y)
int axis[] = {xAxis, yAxis};  // Pin numbers for (x, y)
int mouseReading[2];          // Final mouse readings for (x, y)

void setup() {
  // put your setup code here, to run once:
  Mouse.begin(); // initalize mouse library
  Keyboard.begin();
  CircuitPlayground.begin();

  pinMode(A1, INPUT_PULLUP);

}

void loop() {
// put your main code here, to run repeatedly:

// CircuitPlayground accelerometer Z and X will be linked to arrow keys or WASD for player movement
//Serial.println(CircuitPlayground.motionZ());

if(CircuitPlayground.motionX() > 5){
  Keyboard.write('s');
  delay(debounce);
}

if(CircuitPlayground.motionX() < -5){
  Keyboard.write('w');
  delay(debounce);
}

if(CircuitPlayground.motionZ() > 5){
  Keyboard.write('d');
  delay(debounce);
}

if(CircuitPlayground.motionZ() < -5){
  Keyboard.write('a');
  delay(debounce);
}

// Switch is linked to the player interaction button (spacebar)
  if(digitalRead(A1) == LOW){
    Keyboard.write(' ');
    delay(1000);
  }

// Controller is recoiled back on the y-axis to fire in-game weapon
  Serial.println(CircuitPlayground.motionX());
  if(CircuitPlayground.motionY() >= -7){
    Mouse.click(MOUSE_LEFT);
  }

// AccelerometerX is linked to mouse scroll wheel up/down to swich weapons

 Serial.println(CircuitPlayground.motionX());
  // rotate the scroll wheel up
  if(CircuitPlayground.motionX() >= 15){
    wheel++;
    Mouse.move(0,0,1);
  }
  // rotate the scroll wheel down
  if(CircuitPlayground.motionX() <= -15){
    wheel--;
    Mouse.move(0,0,-1);
  }

// Potentiometer is linked to mouseX input to adjust where the player is looking
  // Read and scale the two axes
  int xReading = readAxis(0);
  int yReading = readAxis(1);

  // Move the mouse
  Mouse.move(xReading, 0, 0);
  delay(responseDelay);
  }

// Mouse Function
int readAxis(int axisNumber) {
  int distance = 0; // Distance from center of the output range

  // Read the analog input
  int reading = analogRead(A7);

  // Of the current reading exceeds the max or min for this axis, reset the max or min
  if (reading < minima[A7]) {
    minima[A7] = reading;
  }
  if (reading > maxima[A7]) {
    maxima[A7] = reading;
  }

  // Map the reading from the analog input range to the output range
  reading = map(reading, minima[A7], maxima[A7], 0, range);

  // If the output reading is outside from the rest position threshold,  use it
  if (abs(reading - center) > threshold) {
    distance = (reading - center);
  }

  // The Y axis needs to be inverted in order to map the movement correctly
  if (axisNumber == 1) {
    distance = -distance;
  }

  // Return the distance for this axis
  return distance;
}

 


Final Project Team 20: Minesweeper Controller

 


Minesweeper Controller

Conceptually, our controller is meant to mimic the precise and tedious hand movements of disarming a bomb, as well as physically placing objects in an environment. The cursor is operated with two dials. The left dial controls the horizontal movement, and the right dial controls the vertical movement. Flagging and detonating are operated with a series of pucks which are placed on a metal contact base. The "dig" puck, modeled after a shovel, has a metal contact running horizontally which activates a left click, while the "flag" puck, modeled after a flag, has one running vertically to activate a right click. Using these four inputs, any given game of Minesweeper could be fully completed. The main body of the controller is white and rectangular to resemble the tiles seen through the game. The shape also reminiscent of old-fashioned controllers from the 80's and 90's, the era of when Minesweeper was released. There is an additional tray attached to the side of the controller body to hold the pucks when they are not in use. The shape of the base of the pucks signifies that they can only be placed onto the contact base in a specific direction. Some limitations of this are that only one puck can be used at a time, and it is difficult to move cursor while simultaneously using a puck. 

What are some other solutions we could have explored for cursor control?

Contributions

Andrew Swanstrom: programming
Jackson Sommer: modeling and 3D printing
Collaborative: construction and testing

Schematic


Code

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

bool leftOnce = true;
bool rightOnce = true;

void setup() {
  // put your setup code here, to run once:
  //CPE library
  CircuitPlayground.begin();
  //Mouse library
  Mouse.begin();
  Serial.begin(9600);

  pinMode(A0, INPUT_PULLUP); //declare two switches for left and right clicking/placing pucks
  pinMode(A3, INPUT_PULLUP);

  pinMode(CPLAY_SLIDESWITCHPIN, INPUT_PULLUP); //failsafe switch
}

void loop() {
  // put your main code here, to run repeatedly:

  if (CPLAY_SLIDESWITCHPIN){ //failsafe
    //Prints the potentiometer values
    Serial.println(analogRead(A1));
    Serial.println(analogRead(A2));

    //assigns variables
    int potX = analogRead(A1);
    int potY = analogRead(A2);

    //move mouse left and right with the left knob input
    if(potX < 341){
      Mouse.move(-1, 0, 0);
    }

    if(potX > 682){
      Mouse.move(1, 0, 0);
    }

    //move mouse up and down with the right knob input
    if(potY < 341){
      Mouse.move(0, 1, 0);
    }

    if(potY > 682){
      Mouse.move(0, -1, 0);
    }

    //place the flag puck on the coper tape contact to set the switch to true, and then press right click once
    if (digitalRead(A0)){
      if (rightOnce){
      Mouse.press(MOUSE_RIGHT);
      rightOnce = false;
      delay(50);
      Mouse.release(MOUSE_RIGHT);
      }
    } else {
      rightOnce = true;
      // if (!rightOnce){
      //   Mouse.release(MOUSE_RIGHT);
      //   rightOnce = true;
      //   delay(100);
      // }
    }

    //place the dig puck on the coper tape contact to set the switch to true, and then press left click once
    if (digitalRead(A3)){
      if (leftOnce){
      Mouse.press(MOUSE_LEFT);
      leftOnce = false;
      delay(50);
      Mouse.release(MOUSE_LEFT);
      }
    } else {
      leftOnce = true;
      // if (!leftOnce){
      //   Mouse.release(MOUSE_LEFT);
      //   leftOnce = true;
      //   delay(100);
      // }
    }
  }
}

Video




Finals Team 13


Camilo Miranda

Daniel Diaz-Rivera


Our project is a toy ship controller designed specifically for bullet hell games, with a particular focus on the game Bullet Hell Monday. The concept originated from the nostalgic joy of a child playing with toy planes and spaceships, enhanced by creating a controller that offers more tangible and interactive feedback. The design integrates the controller's functions with the ship's features in the game for a more immersive experience.

The X-axis control is tilting the controller left and right, simulating the ship's lateral movement in the game. Y-axis movement is controlled with a top-mounted potentiometer positioned where the engines of the toy ship are, giving the sensation of controlling its thrust. Meanwhile, the firing mechanism is mapped to a button located under the "pilot hatch." Pressing this button to "close" the hatch activates the ship's weapons, reinforcing the idea that you're piloting an actual spaceship.

This straightforward approach to controls is designed to enhance the experience, offering a unique alternative to traditional gamepads. Bullet hell games rely heavily on players achieving a flow state, where their movements become instinctive and fully aligned with the game's pace. By creating a controller that lowers the mental barrier to engagement, we aim to make it easier for players to immerse themselves in the game world.

Will this make the game easier? No, not at all, most likely the added physical engagement will increase the challenge as players adapt to the unconventional controls. But we believe that this added difficulty is part of the fun, the same way we suffer through a Dark Souls game.

What other games do you think this controller would be beneficial for? Outside of the bullet hell genre


















Schematic:









Overview Video:


https://youtu.be/HzZsbxppZxA



Code:
//camiloMiranda
#include <Adafruit_CircuitPlayground.h>
#include <Keyboard.h>
const int debounce = 100;
const int threshold = 50;
const int buttonPin = A1;
//int pilot = 2;
bool shoot = true;
int accelX;


void setup() {
  CircuitPlayground.begin();
  pinMode(CPLAY_SLIDESWITCHPIN, INPUT_PULLUP);
  pinMode(CPLAY_LEFTBUTTON, INPUT_PULLDOWN);
  pinMode(CPLAY_RIGHTBUTTON, INPUT_PULLDOWN);
  pinMode(buttonPin, INPUT_PULLUP);
 
  Keyboard.begin();
  Serial.begin(9600);
  delay(1000);
}


void loop() {
    // Toggles shooting functionality on button press
 if(!digitalRead(buttonPin) && !shoot){
    shoot = true;
    delay(debounce);
  }
  if(!digitalRead(buttonPin) && shoot){
    shoot = false;
    delay(debounce);
  }
  if(shoot){
    Keyboard.write('z');
    Keyboard.releaseAll();
    delay(debounce);
  }
 
  // Get accelerometer X-axis for left/right movement
  accelX = CircuitPlayground.motionX();
 
  if (accelX > 3) {  // Move right if accelX > 2
      Keyboard.releaseAll();
      Keyboard.press(KEY_RIGHT_ARROW);
      delay(debounce);
  } else if (accelX < -3) { // Move left if accelX < -2
      Keyboard.releaseAll();
      Keyboard.press(KEY_LEFT_ARROW);
      delay(debounce);
  }


  // Check the potentiometer for up/down movement when slide switch is active
  if(digitalRead(CPLAY_SLIDESWITCHPIN)) {  // Safety switch active
    int potValue = analogRead(A3);
   
    if (potValue > 575) { // Move up
      Keyboard.releaseAll();
      Keyboard.press(KEY_UP_ARROW);
     
      delay(debounce);
    } else if (potValue < 490) { // Move down
      Keyboard.releaseAll();
      Keyboard.press(KEY_DOWN_ARROW);
     
      delay(debounce);
    }
  }
 }