25 April 2026

Final Team 4: Fireboy and Watergirl Game Controller

 


Description:
Do you remember the good old days of sitting down with a friend of a sibling and playing a 2 player game on a single keyboard, having to share the space and getting in each other's way? Our controller seeks to ease that experience by giving each player their own controller. The game we decided to make our custom controller for is Fireboy and Watergirl. Taking inspiration from the game's themes, each player uses the gem-like controller to pilot each of the 2 characters.
The technical aspects of the controller consist of 2 character controllers and 1 home console used for mouse movements. The home console uses 2 potentiometers mapped to the X and Y axis of a mouse. A photoresistor is used for left mouse clicking by checking the light level to be below a certain threshold. The Fireboy controller contains our CPE which uses its built in accelerometer for jumping by sending a rapid shift in motion and triggering a jump when shaken and a potentiometer mapped for right and left movement. Our water girl controller uses the same potentiometer mechanic for right and left movement, but uses a microphone for jumping by measuring the noise levels to be above a certain level to trigger the jump action. While building the casing we made sure to use plastic that would partially isolate outside noise to avoid accidental triggers of the jump button.
The controller thematically looks like the gemstones in the game to represent each character. The red gemstone is for Fireboy, and the blue gemstone is for Watergirl. While the central console is modeled after the yellowish stone of the jungle temple.

Our question for the class: 
If you had to improve on this design do you think having the mouse controller being a part of the individual character controllers would be better, or is having the 3 modules set up better?


Specific Contributions:
Mateo Rios: Made 3D models of casings and printed them, created initial schematic, built physical prototype, wrote blog description, aided in final controller assembly.
Brody Townsend: Designed the initial idea of the game controller, wrote the code, tested code, and made a breadboard prototype. Soldered the components together and aided in the final assembly of the controller. Took photos of the controller, recorded the video, and made the blog post.


Schematic:

Code:
// Brody Townsend
// Mateo Rios

#include <Mouse.h>

#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>

//Normal Game Controls
//Watergirl---------------------------
//(W) = Jump
//(A) = Move Left
//(D) = Move Right
//Fireboy---------------------------
//(Up Arrow) = Jump
//(Left Arrow) = Move Left
//(Right Arrow) = Move Right

//-------------------------------------------------------------------------------
int WaterPot = A0; ////Potentiometer for Watergirl movement. Set to A0.

int FirePot = A1; //Potentiometer for Fireboy movement. Set to A1.

int potXPin = A2; //Potentiometer for X mouse movement. Set to A2.

int potYPin = A3; //Potentiometer for Y mouse movement. Set to A3.

int micPin = A4; //Sound Sensor for watergirl jump. Set to A4.
int micValue = 0;
int soundThreshold = 600;
int jumpcooldown = 300;
unsigned long lastJumpTime = 0;

unsigned long lastFireJump = 0;
int fireJumpCooldown = 300;  // milliseconds between jumps
float shakeThreshold = 15.0; // adjust for sensitivity

// For reading accelerometer
float accelX, accelY, accelZ;
float accelTotal;

const int photoPin = A5; //Photoresistor for left mouse click. Set to A5.
int photoValue = 0;
int photoThreshold = 600;


String WaterDirection = "";
String WaterPrevious = "";
String FireDirection = "";
String FirePrevious = "";




void setup(){
  Serial.begin(9600);
  CircuitPlayground.begin();
  Keyboard.begin();
  Mouse.begin();
 
 
}

void loop() {

if (!CircuitPlayground.slideSwitch()) {
    Keyboard.releaseAll();  // make sure no keys are stuck
    return;                 // skip the rest of the code
  }

  int waterValue = analogRead(WaterPot);
  int fireValue = analogRead(FirePot);
  int xValue = analogRead(potXPin);
  int yValue = analogRead(potYPin);
  micValue = analogRead(micPin);
  if (micValue > 520){
  Serial.println(micValue);
  }


//Watergirl Controls
//Jumping with Sound Sensor
  if (micValue > soundThreshold && millis() - lastJumpTime > jumpcooldown) {
    Keyboard.press('W');
    delay(50); // short tap
    Keyboard.release('W');

    lastJumpTime = millis();
  }
 
//Moving with Potentiometer
  if (waterValue < 400){
     WaterDirection = "Left";}
else if (waterValue > 600){
   WaterDirection = "Right";}
else {WaterDirection = "Middle";}

if (WaterDirection != WaterPrevious) {
  Keyboard.release('A');
  Keyboard.release('D');

  if (WaterDirection == "Left") Keyboard.press('A');
  else if (WaterDirection == "Right") Keyboard.press('D');

  WaterPrevious = WaterDirection;
}


//FireBoy Controls
//Jumping
// Read accelerometer
accelX = CircuitPlayground.motionX();
accelY = CircuitPlayground.motionY();
accelZ = CircuitPlayground.motionZ();

// Calculate total movement force
accelTotal = sqrt(accelX * accelX + accelY * accelY + accelZ * accelZ);

// Shake to Jump
if (accelTotal > shakeThreshold && millis() - lastFireJump > fireJumpCooldown){
  Keyboard.press(KEY_UP_ARROW);
  delay(50);                // short tap
  Keyboard.release(KEY_UP_ARROW);

  lastFireJump = millis();  // reset cooldown
}




//Moving with Potentiometer
if (fireValue < 400){
   FireDirection = "Left";}
else if (fireValue > 600){
   FireDirection = "Right";}
else {FireDirection = "Middle";}

if (FireDirection != FirePrevious) {
  Keyboard.release(KEY_LEFT_ARROW);
  Keyboard.release(KEY_RIGHT_ARROW);

  if (FireDirection == "Left") Keyboard.press(KEY_LEFT_ARROW);
  else if (FireDirection == "Right") Keyboard.press(KEY_RIGHT_ARROW);

  FirePrevious = FireDirection;
}



  // Map the analog value to mouse movement speed (-10 to 10)
  int moveX = map(xValue, 0, 1023, -10, 10);
  int moveY = map(yValue, 0, 1023, -10, 10);

  // Ccreate deadzone in center
  if (abs(moveX) < 2) moveX = 0;
  if (abs(moveY) < 2) moveY = 0;

  // Move the mouse
  Mouse.move(moveX, moveY);

  delay(20); // adjust speed, lower is faster




// Read photoresistor
    photoValue = analogRead(photoPin);

    // If the photo value crosses the threshold, then left click
    if(photoValue > photoThreshold){
        Mouse.press(MOUSE_LEFT);
    } else {
        Mouse.release(MOUSE_LEFT);
    }



}

Video:


No comments:

Post a Comment

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