Hogwarts Legacy Broom Flight Controller

Updated 27 June 2023

Introduction

I embarked on this project to enhance my interactive and immersive experience with the video game "Hogwarts Legacy," as a devoted Harry Potter fan. Initially, I used a Kano wand and Raspberry Pi Zero to cast spells in the game. Now, my focus had shifted to broom-flying mechanics.

The project is relatively simple. I transformed a Nimbus 2000 Movie Prop Replica into a giant Xbox 360 controller. By using a 6-DOF IMU device (Accelerometer & Gyrometer), I could emulate the joysticks as the brooms flying in-game are controlled in that manner. Once the sensor is mounted, by pulling up the movie prop broom, it would replicate the action in-game. Now for broom propulsion, I noticed the player character's body movements changed at different speeds (leaning forward for "normal speed" and hugging the broom for "turbo/boost speed") so I incorporated a distance-measuring ultrasonic sensor. This sensor allows me to mimic the character's movements, adding to the immersive experience - If I lean towards the sensor, it will trigger the “normal speed” command and my character would mimic my body positioning - allowing the player to feel as if they are the character. I used Dave Madison's Arduino Xinput Library to achieve all this, which enables the Arduino Pro Micro to emulate an Xbox 360 Controller.

In summary, this project combines elements like the Nimbus 2000 Movie Prop Replica, 6-DOF IMU, an Ultrasonic sensor, and an Arduino Pro Micro to heighten the interactive and immersive gameplay of "Hogwarts Legacy."


Bill Of Materials

Electronics:

Mounting:

  • Paddle Pop Sticks
  • Foam (salvaged from packaging a monitor came in)
  • PVC Wood Glue (or any other adhesive suitable for the sticks, perhaps hot glue)
  • Standoffs - I believe they were around 5mm/found around the house
  • A Rubber Band to support the HC-SR04

Wiring Diagram

Code

My code is based on the example code that was provided by Adafruit for the LSM6DS3TR-C sensor. I removed certain elements that were unnecessary. I also added in the setup required for the ultrasonic sensor and Arduino xInput. Three different libraries are required for my code: Arduino Xinput, Adafruit accelerator, NewPing (ultrasonic).

#include 
#include 
#include 
#define TRIGGER_PIN  2
#define ECHO_PIN     3
#define MAX_DISTANCE 500
// For SPI mode, we need a CS pin
#define LSM_CS 10
// For software-SPI mode we need SCK/MOSI/MISO pins
#define LSM_SCK 13
#define LSM_MISO 12
#define LSM_MOSI 11


NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);
int minVal = 265;
int maxVal = 402;
int mountState = 0;
double x;
double y;
double z;

Adafruit_LSM6DS3TRC lsm6ds3trc;

void setup(void) {
  Serial.begin(115200);
  while (!Serial)
    delay(10); // will pause Zero, Leonardo, etc until serial console opens

  Serial.println("Adafruit LSM6DS3TR-C test!");

  //if (!lsm6ds3trc.begin_I2C()) {
  if (!lsm6ds3trc.begin_SPI(LSM_CS)) {
    Serial.println("Failed to find LSM6DS3TR-C chip");
    while (1) {
      delay(10);
    }
  }

  Serial.println("LSM6DS3TR-C Found!");

  // lsm6ds3trc.setAccelRange(LSM6DS_ACCEL_RANGE_2_G);
  Serial.print("Accelerometer range set to: ");
  switch (lsm6ds3trc.getAccelRange()) {
    case LSM6DS_ACCEL_RANGE_2_G:
      Serial.println("+-2G");
      break;
    case LSM6DS_ACCEL_RANGE_4_G:
      Serial.println("+-4G");
      break;
    case LSM6DS_ACCEL_RANGE_8_G:
      Serial.println("+-8G");
      break;
    case LSM6DS_ACCEL_RANGE_16_G:
      Serial.println("+-16G");
      break;
  }

  // lsm6ds3trc.setAccelDataRate(LSM6DS_RATE_12_5_HZ);
  Serial.print("Accelerometer data rate set to: ");
  switch (lsm6ds3trc.getAccelDataRate()) {
    case LSM6DS_RATE_SHUTDOWN:
      Serial.println("0 Hz");
      break;
    case LSM6DS_RATE_12_5_HZ:
      Serial.println("12.5 Hz");
      break;
    case LSM6DS_RATE_26_HZ:
      Serial.println("26 Hz");
      break;
    case LSM6DS_RATE_52_HZ:
      Serial.println("52 Hz");
      break;
    case LSM6DS_RATE_104_HZ:
      Serial.println("104 Hz");
      break;
    case LSM6DS_RATE_208_HZ:
      Serial.println("208 Hz");
      break;
    case LSM6DS_RATE_416_HZ:
      Serial.println("416 Hz");
      break;
    case LSM6DS_RATE_833_HZ:
      Serial.println("833 Hz");
      break;
    case LSM6DS_RATE_1_66K_HZ:
      Serial.println("1.66 KHz");
      break;
    case LSM6DS_RATE_3_33K_HZ:
      Serial.println("3.33 KHz");
      break;
    case LSM6DS_RATE_6_66K_HZ:
      Serial.println("6.66 KHz");
      break;
  }

  lsm6ds3trc.configInt1(false, false, true);
  XInput.setRange(JOY_LEFT, 60, 115);
  XInput.setRange(JOY_RIGHT, 60, 115);
  XInput.begin();
}

void loop() {
  // Get a new normalized sensor event
  sensors_event_t accel;
  sensors_event_t gyro;
  sensors_event_t temp;
  lsm6ds3trc.getEvent(&accel, &gyro, &temp);


  int xAng = map(accel.acceleration.x, minVal, maxVal, -90, 90);
  int yAng = map(accel.acceleration.y, minVal, maxVal, -90, 90);
  int zAng = map(accel.acceleration.z, minVal, maxVal, -90, 90);

  z = RAD_TO_DEG * (atan2(-accel.acceleration.y, -accel.acceleration.x) + PI);
  x = RAD_TO_DEG * (atan2(-accel.acceleration.y, -accel.acceleration.z) + PI);
  y = RAD_TO_DEG * (atan2(-accel.acceleration.x, -accel.acceleration.z) + PI);


  if (mountState == 0 && ((x >= 80) && (x <= 100)) && (sonar.ping_cm() <= 40)) //to mount the broom, initial mounting needs calibration for person and positioning of sensors on broom - etc.
  {
    XInput.press(BUTTON_LB);
    delay(1002);
    XInput.press(BUTTON_B);
    delay(750);
    XInput.release(BUTTON_LB);
    XInput.release(BUTTON_B);
    mountState = 1;
    delay(100);
  }
  if (mountState == 1) //default stuff that should be running once mounted on broom
  {
    XInput.setJoystickY(JOY_RIGHT, x, false);
    XInput.setJoystickX(JOY_RIGHT, (x) , false);    
    XInput.setJoystickX(JOY_LEFT, z, false);
    XInput.setButton(TRIGGER_RIGHT, ((sonar.ping_cm() >= 2) && (sonar.ping_cm() <= 20))); //normal flight speed
    XInput.setButton(TRIGGER_LEFT, ((sonar.ping_cm() >= 2) && (sonar.ping_cm() <= 10)));  //turbo flight speed
    if ((x >= 170) || (x <= 10) && (sonar.ping_cm() >= 35 || sonar.ping_cm() == 0))
    {
      XInput.press(BUTTON_B);
      delay(2000);
      XInput.release(BUTTON_B);
      mountState = 0;
      delay(3000);
    }
  }

  delay(100);

}

Photo Gallery

Have a question? Ask the Author of this guide today!

Please enter minimum 20 characters

Your comment will be posted (automatically) on our Support Forum which is publicly accessible. Don't enter private information, such as your phone number.

Expect a quick reply during business hours, many of us check-in over the weekend as well.

Comments


Loading...
Feedback

Please continue if you would like to leave feedback for any of these topics:

  • Website features/issues
  • Content errors/improvements
  • Missing products/categories
  • Product assignments to categories
  • Search results relevance

For all other inquiries (orders status, stock levels, etc), please contact our support team for quick assistance.

Note: click continue and a draft email will be opened to edit. If you don't have an email client on your device, then send a message via the chat icon on the bottom left of our website.

Makers love reviews as much as you do, please follow this link to review the products you have purchased.