Ryan Chan
Published © GPL3+

Controlling Minecraft in Real Life

A device that uses an IMU, pressure sensors, and Arduino to map real-life movements to Minecraft

BeginnerFull instructions provided2 hours902

Things used in this project

Hardware components

Arduino MKR Zero
Arduino MKR Zero
×1
Inertial Measurement Unit (IMU) (6 deg of freedom)
Inertial Measurement Unit (IMU) (6 deg of freedom)
MPU 6050
×1
Force Sensitive Resistor
×2
Breadboard (generic)
Breadboard (generic)
×1
Jumper wires (generic)
Jumper wires (generic)
×1
Resistor 10k ohm
Resistor 10k ohm
×2
Pushbutton switch 12mm
SparkFun Pushbutton switch 12mm
×3

Software apps and online services

Arduino IDE
Arduino IDE
Minecraft

Story

Read more

Custom parts and enclosures

EAGLE Board File

EAGLE Schematic File

Schematics

Breadboard Schematic

PCB Schematic

The buttons are Cherry MX Mechanical Keyboard switches, which include an LED light.

Code

Code

Arduino
// Libraries you need to install from Library Manager
#include <AbsMouse.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>

// Default Libraries
#include <Keyboard.h> 
#include <Wire.h>

// User defined variables begin *********************************************

const int buildType = 0; // Put 0 for the breadboard version. Put 1 for the PCB version 
const int screenWidth = 2736;
const int screenHeight = 1824;
const int horizontalRotationSensitivity = 1.5;
const int stepTime = 160; // Step time in milliseconds. Increase to walk more in the game per step
const int stepThreshold = 1000;
const float xThreshold = 0.05; // Increase this if your horizontal rotation drifts

// User defined variables end ***********************************************

const int switchPin = A0;
const int button1 = A3;
const int button2 = 0;
const int button3 = 2;
const int stepPin1 = A5;
const int stepPin2 = A6;

const int led1 = A4;
const int led2 = 1;
const int led3 = 3;
const int led4 = 5;

boolean step1Released = true;
boolean step2Released = true;

unsigned long stepReleaseTimeStamp = 0;

const int valSize = 50;
float xValues[valSize];
float yValues[valSize];

Adafruit_MPU6050 mpu;

int xCenter;
int yCenter;

float mousePosX;
float mousePosY;
const float gravity = 9.7; // in m/s^2. not 9.8 because IMU was slightly off

void setup(void) {
  xCenter = screenWidth/2;
  yCenter = screenHeight/2;

  mousePosX = xCenter;
  mousePosY = yCenter;
  
  Serial.begin(9600);
  Serial.println("Beginning");
  
  //Mouse.begin();
  
  AbsMouse.init(screenWidth, screenHeight);

  Keyboard.begin();
  
  pinMode(switchPin, INPUT_PULLUP);
  pinMode(button1, INPUT_PULLUP);
  pinMode(button2, INPUT_PULLUP);
  pinMode(button3, INPUT_PULLUP);
  pinMode(led1,OUTPUT);
  pinMode(led2,OUTPUT);
  pinMode(led3,OUTPUT);
  pinMode(led4,OUTPUT);
  
  pinMode(stepPin1, INPUT);
  pinMode(stepPin2, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);

  
  if (!mpu.begin()) {
    Serial.println("MPU6050 not found");
  }else{
    Serial.println("MPU6050 found");
  }
  
  delay(100);
}

float led_counter = 0;

void loop() {
  int led_value = 100*sin(led_counter) + 100;
  led_counter += 0.025;
  analogWrite(led1,led_value);
  analogWrite(led2,led_value);
  analogWrite(led3,led_value);
  analogWrite(led4,led_value);
  
  sensors_event_t a, g, temp;
  mpu.getEvent(&a, &g, &temp);

  float gyroX = g.gyro.x;
  float gyroY = g.gyro.y;
  float gyroZ = g.gyro.z;

  float accelX = a.acceleration.x;
  float accelY = a.acceleration.y;
  float accelZ = a.acceleration.z;

  int switchedOn = !digitalRead(switchPin); // opposite because pulled up

  Serial.print("Active: ");
  Serial.print(switchedOn);
  
  Serial.print("GX: ");
  Serial.print(gyroX);
  Serial.print(" GY: ");
  Serial.print(gyroY);
  Serial.print(" GZ: ");
  Serial.print(gyroZ);

  Serial.print(" AX: ");
  Serial.print(accelX);
  Serial.print(" AY: ");
  Serial.print(accelY);
  Serial.print(" AZ: ");
  Serial.print(accelZ);
  Serial.print(" Step1: ");
  Serial.print(analogRead(stepPin1));
  Serial.print(" Step2: ");
  Serial.print(analogRead(stepPin2));
  Serial.print(" B1: ");
  Serial.print(digitalRead(button1));
  Serial.print(" B2: ");
  Serial.print(digitalRead(button2));
  


  if(switchedOn){
    digitalWrite(LED_BUILTIN, HIGH);

    // Mouse Begin *******************************************************************

    // Move mouse cursor based on IMU
    
    if(buildType == 0){ // change IMU axes based on build type (due to different orientations of the IMU on each build)
      if(gyroY > xThreshold || gyroY < -1*xThreshold){
        mousePosX += -1*horizontalRotationSensitivity*gyroY;
      }
    }else if(buildType == 1){
      if(gyroX > xThreshold || gyroX < -1*xThreshold){
        mousePosX += -1*horizontalRotationSensitivity*gyroX;
      }
    }
    
    float angle = getAngle(accelZ, gravity);
    mousePosY = mapfloat(angle,0,180,yCenter - 200,yCenter + 200); 
    float mousePosYAvg = average(yValues,valSize,mousePosY);
    
    AbsMouse.move(mousePosX, mousePosYAvg);

    Serial.print(" angle ");
    Serial.print(angle);
    Serial.print(" MY: ");
    Serial.print(mousePosYAvg);
    Serial.print(" MX: ");
    Serial.print(mousePosX);
    Serial.print(" ");
    
    
    // Right Click Logic
    if(!digitalRead(button1)){
      AbsMouse.press(MOUSE_RIGHT);
      Serial.print(" mouse right hold");
    }else{
      AbsMouse.release(MOUSE_RIGHT);
    }

    // Left Click Logic
    if(!digitalRead(button2)){
      AbsMouse.press(MOUSE_LEFT);
      Serial.print(" mouse left hold");
    }else{
      AbsMouse.release(MOUSE_LEFT);
    }
    // Mouse End *******************************************************************

    

    // Keyboard Begin *******************************************************************

    // Jump (space bar) logic
    if(!digitalRead(button3)){
      Serial.println("button 3 pressed");
      Keyboard.press(' ');
      Serial.println("button 3 done");
    }else{
      Keyboard.release(' ');
    }

    // Step forward (W key) logic
    if((analogRead(stepPin1) > stepThreshold) && step1Released){
      Serial.println("W key");
      
      Keyboard.press('w');
      stepReleaseTimeStamp = millis();
      
      step1Released = false;
    }else if((analogRead(stepPin1) < stepThreshold) && !step1Released){
      step1Released = true;
    }
    
    if((analogRead(stepPin2) > stepThreshold) && step2Released){
      Serial.println("W key");
      
      Keyboard.press('w');
      stepReleaseTimeStamp = millis();
      
      step2Released = false;
    }else if((analogRead(stepPin2) < stepThreshold) && !step2Released){
      step2Released = true;
    }
    
    if((millis() - stepReleaseTimeStamp) > stepTime){
      Keyboard.releaseAll();
      Serial.print(" keys released");
    }
    // Keyboard End *******************************************************************

    
  }else{
    digitalWrite(LED_BUILTIN,LOW);
  }

  Serial.println();
}

// Helper Functions

float average(float* values, int arraySize, float newValue){
  // update values array with newValue
  for(int i = 0; i < arraySize-1; i++){
    values[i] = values[i+1];
  }
  values[arraySize-1] = newValue;

  // calculate and return the average
  float total = 0;
  for(int i = 0; i < arraySize; i++){
    total += values[i];
  }
  return total/float(arraySize);
}

float mapfloat(float x, float in_min, float in_max, float out_min, float out_max){
  if(x < in_min){
    x = in_min;
  }else if(x > in_max){
    x = in_max;
  }
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

float getAngle(float accel, float g){
  if(accel > g){
    accel = g;
  }else if(accel < -1*g){
    accel = -1*g;
  }
  return (180/PI)*acos(accel/g);
}

Credits

Ryan Chan

Ryan Chan

9 projects • 229 followers
I like turtles. I also like robots.

Comments