The Equalizer, an Arcade Stick Add-On Made to Be Unfair

by thomas.vaneekelen in Circuits > Arduino

879 Views, 5 Favorites, 0 Comments

The Equalizer, an Arcade Stick Add-On Made to Be Unfair

20210808_173526.jpg

When I started playing fighting games I got absolutely rinsed, just like everyone else who starts out in this hard-to-get-into genre. Over the years I improved but I still remember some of my first attempts to get into games like Tekken and Mortal Kombat.
Often when playing against friends they refused to go a little easy on me. This was a fun challenge to overcome, but sometimes I wished for them to be on my level just for a little bit. Not only for my enjoyment but theirs as well, since bodying a new player 20 matches in a row is not the most stimulating way to spend your time.

This thought was my main inspiration when conceptualizing The Equalizer. It aims to level out the playing field between new and advanced players, by modifying the controller of the more advanced player. Through this, the new player gains a fighting chance, and the advanced player has a fresh new challenge to overcome.

Aside from that I also just really wanted to make my own arcade stick.

What The Equalizer became, however, was a dysfunctional mess of sensors, servos, and wires. Theoretically, it could work, everything has been tested on a smaller scale and proven to be functional. In the end what I lacked were resources, which is a very frustrating way to close off an otherwise successful project.
What I did manage to make, however, was a perfectly functional arcade stick. Which is something I plan to use and upgrade as time goes on.

In the end, experimentation and making something that is useable is what mattered most to me for this project. So, at least to me, this project was a great success despite failing to produce The Equalizer's final iteration.

A playlist containing all videos linked in this Instructable can be found here

Supplies

Parts and Materials

  • An arcade stick (indented parts below are in case you want to make one as I did)
    • 10 arcade buttons (I used Sanwa OBSF-30 buttons)
    • 1 arcade joystick (I used a Sanwa JLF-TP-8YT)
    • An Arduino UNO
    • USB cable type A/B (at the very least half a meter in length)
    • 5-pin F/whatever cable, you'll have to splice it anyways
    • 11+ connection Daisy chain ground cable with .110 quick disconnect
    • 12 cables with .110 quick disconnect
    • 20+ M/M jumper wires for splicing
    • large sheet of 2-4mm thick MDF
    • 2x2cm square wooden slat
    • 4 washers, thumbscrews, and bolts for securing the joystick
    • Some way of making the top openable (I used magnets but you could use hinges as well)
  • An additional Arduino UNO
  • 8 servo motors
  • At least 8 fun or interesting Arduino-compatible sensors, switches, and buttons
  • M/F and M/M jumper wires, number depending on your chosen sensors, switches and buttons
  • Resistors and other components according to the needs of your chosen sensors, switches and buttons

Tools

  • A soldering setup
  • A drill with a 30mm drill bit
  • Wood glue
  • A multimeter, optional but very useful to check for continuity

Arcade Stick Software

To make an Arcade Stick using Arduino as the PCB, you need to use UnoJoy, a by now almost decade-old resource that can trick your PC into thinking that your Arduino is actually a Joystick. You can still follow the linked resource's tutorial with great results, although I strongly advise to use D-Pad input for the joystick instead of either of the two thumbsticks. It's simply easier and translates better into an arcade joystick's 8-way operation.

After setting up the code I suggest first making a small test button to see if it all works according to your needs. In this video you can see me testing out a button through Windows' game controller panel.

The final code on my stick is mostly the same as the default that comes with the UnoJoy resource, with some changes based on the number of inputs possible on my Arcade Stick.

#include "UnoJoy.h"

// Define our pins
// frontface button pins
int CirclePin = 2;
int CrossPin = 3;
int TrianglePin = 4;
int SquarePin = 5;

//shoulder and trigger pins
int r1Pin = 6;
int r2Pin = 7;
int l1Pin = A1;
int l2Pin = A2;

//joystick pins
int LeftPin = 8;
int UpPin = 9;
int RightPin = 10;
int DownPin = 11;

//menu button pins
int StartPin =  12;
int SelectPin = A3;

void setup(){
  setupPins();
  setupUnoJoy();
}

void loop(){
  // Always be getting fresh data
  dataForController_t controllerData = getControllerData();
  setControllerData(controllerData);

  //debug
  Serial.print(CirclePin)
}

void setupPins(void){
  // Set all the digital pins as inputs
  // with the pull-up enabled, except for the 
  // two serial line pins
  for (int i = 2; i <= 12; i++){
    pinMode(i, INPUT);
    digitalWrite(i, HIGH);
  }
  pinMode(A1, INPUT);
  digitalWrite(A1, HIGH);
  pinMode(A2, INPUT);
  digitalWrite(A2, HIGH);
   pinMode(A3, INPUT);
  digitalWrite(A3, HIGH);
}

dataForController_t getControllerData(void){
  
  // Set up a place for our controller data
  //  Use the getBlankDataForController() function, since
  //  just declaring a fresh dataForController_t tends
  //  to get you one filled with junk from other, random
  //  values that were in those memory locations before
  dataForController_t controllerData = getBlankDataForController();
  // Since our buttons are all held high and
  //  pulled low when pressed, we use the "!"
  //  operator to invert the readings from the pins
  controllerData.triangleOn = !digitalRead(TrianglePin);
  controllerData.circleOn = !digitalRead(CirclePin);
  controllerData.squareOn = !digitalRead(SquarePin);
  controllerData.crossOn = !digitalRead(CrossPin);
  
  controllerData.r1On = !digitalRead(r1Pin);
  controllerData.r2On = !digitalRead(r2Pin);
  controllerData.l1On = !digitalRead(l1Pin);
  controllerData.l2On = !digitalRead(l2Pin);
  
  
  
  controllerData.dpadUpOn = !digitalRead(UpPin);
  controllerData.dpadDownOn = !digitalRead(DownPin);
  controllerData.dpadLeftOn = !digitalRead(LeftPin);
  controllerData.dpadRightOn = !digitalRead(RightPin);
  
  controllerData.startOn = !digitalRead(StartPin);
  controllerData.selectOn = !digitalRead(SelectPin);
  
  // And return the data!
  return controllerData;
}<br>

Note that a header file is needed to run this script, this header file is included in the UnoJoy resource and remained unchanged in my build

Arcade Stick Hardware

20210714_152923.jpg
20210714_163302.jpg
20210714_163321.jpg
20210714_175734.jpg
20210714_180642.jpg

Bringing an Arcade Stick's parts together is relatively easy, considering you don't need to mess around with resistors or pinboards. The most labour-intensive part of the hardware is splicing the quick disconnect wires with the male end of a jumper wire, so that the buttons and joystick can be connected to the Arduino board. When this is done you can connect the buttons and joystick to test them out before starting with the casing.

Here's a video where you can see me testing out the joystick in Windows' game controller panel, and here in an actual game!
After wiring everything up I was able to test out the buttons as well, which can be seen here.

Most of this, together with the previous step, can be skipped by purchasing a fight stick PCB like the ones from Brook. These can cost a bit but save you a lot of trouble and will last you a lifetime.

I personally had the most fun with this step, seeing everything work and come together like this felt very gratifying!

Arcade Stick Casing

20210718_224048.jpg
20210717_201921.jpg
20210717_222547.jpg
20210717_201937.jpg
20210718_145801.jpg
20210717_222554.jpg
20210718_145635.jpg
20210718_145707.jpg

There are prebuilt cases that can be bought, and laser cutting templates that can be downloaded online and used to make this step much easier. If you are able to do those things I'd suggest you go for those options instead.

To build this casing I used a large sheet of 4mm thick MDF. This was a good thickness for the push-in buttons to sit relatively securely but I would go 3mm thickness if you're able to, thinner would probably turn out a bit too flexible though.

I used wood glue and sawed pieces of the wooden slats to reinforce the casing in the corners. The measurements were eyeballed and based on a button layout template. It's advisable to use a template for tidiness and to avoid hand cramps due to being forced into weird hand positions.

Holes for the buttons were drilled using a drill with a 30mm drillbit, the hole for the joystick is roughly 15mm but could be a bit more spacious, around 20mm maybe. If you're not sure what bolts to get to secure your joystick ask your local hardware store for advice, the mounting plate of your joystick shall be your guide.

To make the arcade stick's top removable I used 4 screw-in magnets in the corners. Considering the wiring you should be able to use something more standard like hinges as well.

To secure your Arduino in place you can secure it using screws, but because I'd have to take it out a bunch in the future I instead opted to use stick-on velcro to secure it. I also added an additional piece of the slat to wrap the wire around, so that when the USB cable is tugged it won't risk damaging the Arduino.

With this step, the arcade stick is finished. A video of me using it can be seen here.

You can further customize it to your liking (by spraypainting the MDF for instance) but the basic stick is now complete. From here on out it will be the Equalizer, the fall from its conceptual grace, and the salvaging I did to try and squeeze out a passing grade.

Equalizer Script

The idea behind The Equalizer's functionality was that it would use 8 servos to manually press the buttons of the underlying arcade stick, following one of the standard button layouts called Noir. These servos would move based on 8 sensors and switches that were installed on the top of The Equalizer.

Following is the script used to make this functionality possible.

#include <Servo.h>

//Servos
Servo myServos[7];
int pos = 0;    // variable to store the servo position

//digital input pins
const int ignition_pin = 10;
const int tilt_pin = 11;
const int pressureS_pin = 12;
const int pressureL_pin = 13;
const int switch_pin = A0;
const int sound_pin = A1;

//analog input pins
const int light_pin = A2;
const int pot_pin = A3;

//analog threshold values
const int light_thrVal = 50<br>const int pot_thrVal = 612<br>
void setup() {
  Serial.begin(9600);
  
  //setup servos
  for (int i=0;i<=7;i++){
    myServos[i].attach(i+2);  // attaches the 8 servos to the pins 2-9
    myServos[i].write(0); //sets servo to 0 degrees
    String printString = " servo attached to pin ";
    Serial.println(i+printString+(i+2));<br>    delay(5); // waits 5ms to reach 0 and set the next servo
  }
}

void loop() {
  //digital input
  if (digitalRead(switch_pin) == 1){
    triggerServo(0);
  }
  if (digitalRead(sound_pin) == 1){
    triggerServo(1);
  }
  if (digitalRead(ignition_pin) == 1){
    triggerServo(2);
  }
  if (digitalRead(pressureS_pin) == 1){
    triggerServo(4);
  }
  if (digitalRead(pressureL_pin) == 1){
    triggerServo(6);
  }
  if (digitalRead(tilt_pin) == 1){
    triggerServo(7);
  }
  //analog input
  if (analogRead(light_pin) <= light_thrVal){<br>    triggerServo(5);
  }
  if (analogRead(pot_pin) >= pot_thrVal){<br>    triggerServo(3);
  }
}

void triggerServo(int servoNum){
if (myServos[servoNum].read() == 0){    
    for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
      myServos[servoNum].write(pos);      // tell servo to go to position in variable 'pos'
      delay(1);                           // waits 1ms for the servo to reach the position
    }
  }
  else if (myServos[servoNum].read() == 180){
    for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
      myServos[servoNum].write(pos);      // tell servo to go to position in variable 'pos'
      delay(1);                           // waits 1ms for the servo to reach the position
    }
  }
}<br>

This script was very rushed so there's a lot of room for improvement, especially the contents of the loop function are something I'm pretty embarrassed about. But in the end, it's functional for my specific combination of sensors and switches, so that's good!

Equalizer Hardware and Casing

fritzingSketch.PNG

I planned out all of the hardware using Fritzing, a very useful piece of software not only for Arduino but for making your own PCBs as well. It does cost 8 euros to get, but it's definitely worth that low price.
After planning out the build I started work on the casing and construction of the final iteration, but here's where issues started cropping up.

At this point I ran out of funds to buy additional necessary parts. My soldering iron's tip was gunked up and needed replacement, my pinboard turned out to be faulty, wires didn't work, sensors I bought turned out to be flipped from AliExpress and stopped working after a few tests, all in all it quickly turned into a disaster.

I don't like making excuses, but with the approaching deadline and an ever-shrinking willingness to continue this project I made some drastic changes to try and meet the project criteria, which will be the final step of this Instructable.

Salvaging the Project With the Equalizer Lite

20210808_222314.jpg
20210808_222307.jpg
20210808_222329.jpg
20210808_222340.jpg

My plan for this final part of the project was to use the second Arduino to integrate at least one alternative input and one actuator into the arcade stick I made, to still try and carry on the idea behind The Equalizer. An Equalizer Lite of some sort. It would definitely be lesser, but doable within the few hours I had left.

The idea I finally continued on was to create a kind of remote that is given to the new player. This remote has a potentiometer that can be dialled to return values. These values are used to drive a servo motor that moves an element (currently a plastic ruler) over the arcade stick's buttons to mess up the experienced player's flow. The dial can be turned higher to make the servo move faster and lower to make it go slower or stop completely.
The 'minigame' around this is that the plastic ruler can detach when the player doesn't remove their hands in time, after which they'll have to reattach it before they can make additional moves. This way there's enough incentive to play safe and hold back against the new player, who controls the game's pace through their remote.

To contain the remote I used a small plastic tub I had laying around. It could fit a battery pack, the Arduino UNO, and the potentiometer perfectly. Using my soldering iron I melted holes for the servo's wires, the battery pack's power switch, and the potentiometer so everything could function while the remote was closed.

After finishing this rough last-ditch attempt I playtested it a bit with my girlfriend (who has supported me so much during this project, love you lots Elise) to see if it was tricky enough to deal with. What we found was that it was actually quite fun, almost like a tiny rhythm game integrated into the controller! This was a very pleasant surprise and we messed around with it for quite a while, two videos of this can be seen here (me struggling) and here (her thriving).

Here's the (messy) code:

#include <Servo.h>

Servo myservo;
const int mysensor = A0;

int pos = 0;    // variable to store the servo position

void setup() {
  Serial.begin(9600);
<br>  myservo.attach(9);  // attaches the servo on pin 9 to the servo object
  myservo.write(0);
}

void loop() {
  Serial.println(analogRead(mysensor));
<br>  if (analogRead(mysensor) <= 10){<br>
  }
  else if (analogRead(mysensor) > 10 && analogRead(mysensor) <= 300){
    if (myservo.read() == 0){<br>      for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
        myservo.write(pos);      // tell servo to go to position in variable 'pos'
        delay(5);                           // waits 15ms for the servo to reach the position
      }
    }
    else if (myservo.read() == 180){
      for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
        myservo.write(pos);      // tell servo to go to position in variable 'pos'
        delay(5);                           // waits 15ms for the servo to reach the position
      }
    }
  } else if (analogRead(mysensor) > 300){
    if (myservo.read() == 0){<br>      for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
        myservo.write(pos);      // tell servo to go to position in variable 'pos'
        delay(2);                           // waits 15ms for the servo to reach the position
      }
    }
    else if (myservo.read() == 180){
      for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
        myservo.write(pos);      // tell servo to go to position in variable 'pos'
        delay(2);                           // waits 15ms for the servo to reach the position
      }
    }
  }
}<br>

Conclusion

The Equalizer Lite

Despite not being able to finish this project the way I originally wanted to, I was surprised by something new at the very last moment and able to make something my girlfriend and I enjoyed playing with. It has also given me a few new ideas for alternative controllers that could be cool but I honestly just want to rest for now, definitely have had enough of controllers for at least a while.

So if you're one of my teachers, thanks for reading/watching and I hope I was able to garner your interest!

And if you're just a random passerby reading this, I hope you enjoyed reading this and try making your own arcade stick, it's definitely worth the effort and a lot of fun!