26 April 2026

Team 15 Final Project: Buck Up And Drive

Description:

    Buck Up and Drive is a fast-paced arcade racing game where the player controls a self-driving car, avoiding obstacles and earning points through drifting and tricks. Since the car moves forward automatically, our goal was to design an alternate controller that makes the remaining controls feel immersive and intuitive. To match the racing theme, we created a controller shaped like a model car so that the player feels like they are directly controlling the vehicle in the game. The conceptual model of our controller is based on familiar car-driving actions. The player steers by holding the car with one hand and tilting it left or right, using a compass module to detect direction changes. This mimics the natural motion of steering. The other hand operates a rear-mounted lever that activates drifting, similar to using a handbrake in a real car. A button on the hood pauses the game, turning part of the car body into an interactive control surface. This creates a clear input-to-output mapping where each physical action corresponds naturally to an in-game mechanic. The design helps communicate how the controller is meant to be used. The car shape suggests that it should be held and steered like a real vehicle. The rear lever is positioned so that the player’s second hand naturally rests on it, signaling its use for drifting. The hood button is large and easy to identify, inviting the player to press down to pause the game. Feedback is provided through the game’s immediate response: tilting the controller changes the car’s direction, the lever triggers drifting, and pressing the hood pauses game play. This real-time response helps reinforce the connection between the player’s physical movements and the on-screen actions. By mapping real-world driving gestures to in-game mechanics, the controller enhances immersion and supports the arcade racing theme. Its physical layout and intuitive controls create strong affordances, making game play feel more natural and engaging.

Question:

     Is there any way to improve comfort-ability when using the controller during game play?

Images: 

 

 

Schematic: 


 










Video:

Code:

 #include <Adafruit_CircuitPlayground.h>
#include <Keyboard.h>
#include <math.h>
#include <Adafruit_QMC5883P.h>

// Stores compass sensor object
Adafruit_QMC5883P qmc;

// Stores heading and steering values
float heading = 0;
float centerHeading = 0;
float compassInput = 0;

// Tracks if device is shaking
bool isShaking = false;

// Input pins for controls
const int hoodPin = 14;
const int driftPin = 15;

// Starts serial and hardware setup
void setup()
{
  Serial.begin(9600);
  delay(1500);

  Serial.println("Booting system...");

  initializeHardware();
  initializeCompass();

  Serial.println("Setup complete");
}

// Runs steering and input checks
void loop()
{
  updateCompassSteering();
  handleShakeTricks();
  handlePauseButton();
  handleDriftLever();

  delay(50);
}

// Initializes board, keyboard, and pins
void initializeHardware()
{
  CircuitPlayground.begin();
  Keyboard.begin();

  pinMode(hoodPin, INPUT_PULLUP);
  pinMode(driftPin, INPUT_PULLUP);

  Serial.println("Hardware initialized");
}

// Starts and configures compass sensor
void initializeCompass()
{
  Serial.println("Initializing QMC5883P...");

  if (!qmc.begin())
  {
    Serial.println("ERROR: QMC5883P not found!");
    while (1) delay(10);
  }

  Serial.println("QMC5883P Found!");

  qmc.setMode(QMC5883P_MODE_NORMAL);
  qmc.setODR(QMC5883P_ODR_50HZ);
  qmc.setOSR(QMC5883P_OSR_4);
  qmc.setDSR(QMC5883P_DSR_2);
  qmc.setRange(QMC5883P_RANGE_8G);
  qmc.setSetResetMode(QMC5883P_SETRESET_ON);

  delay(200);

  centerHeading = readHeading();

  Serial.print("Center heading: ");
  Serial.println(centerHeading);
}

// Reads heading from compass module
float readHeading()
{
  int16_t x, y, z;

  if (!qmc.isDataReady())
    return heading;

  if (!qmc.getRawMagnetic(&x, &y, &z))
  {
    Serial.println("Compass read failed");
    return heading;
  }

  heading = atan2((float)y, (float)x) * 180.0 / PI;

  if (heading < 0)
    heading += 360;

  Serial.print("Heading: ");
  Serial.println(heading);

  return heading;
}

// Converts compass angle to steering
void updateCompassSteering()
{
  heading = readHeading();

  compassInput = heading - centerHeading;

  if (compassInput > 180) compassInput -= 360;
  if (compassInput < -180) compassInput += 360;

  int deadzone = 5;

  if (compassInput > deadzone)
    Keyboard.press(KEY_RIGHT_ARROW);
  else
    Keyboard.release(KEY_RIGHT_ARROW);

  if (compassInput < -deadzone)
    Keyboard.press(KEY_LEFT_ARROW);
  else
    Keyboard.release(KEY_LEFT_ARROW);
}

// Detects shake and performs trick
void handleShakeTricks()
{
  float mx = CircuitPlayground.motionX();
  float my = CircuitPlayground.motionY();
  float mz = CircuitPlayground.motionZ();

  float magnitude = sqrt(mx * mx + my * my + mz * mz);

  isShaking = (magnitude > 15);

  if (isShaking)
  {
    Serial.println("Shake detected!");

    if (random(0, 2) == 0)
    {
      Keyboard.press(KEY_UP_ARROW);
      delay(100);
      Keyboard.release(KEY_UP_ARROW);
    }
    else
    {
      Keyboard.press(KEY_DOWN_ARROW);
      delay(100);
      Keyboard.release(KEY_DOWN_ARROW);
    }
  }
}

// Checks pause button press
void handlePauseButton()
{
  static int lastState = HIGH;
  int state = digitalRead(hoodPin);

  if (state == LOW && lastState == HIGH)
  {
    Serial.println("Pause button pressed");

    Keyboard.press(KEY_ESC);
    delay(50);
    Keyboard.release(KEY_ESC);
  }

  lastState = state;
}

// Holds space key for drifting
void handleDriftLever()
{
  if (digitalRead(driftPin) == LOW)
  {
    Keyboard.press(' ');
    Serial.println("lever switched on");
  }
  else
  {
    Keyboard.release(' ');
  }
}

No comments:

Post a Comment

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