Alternative Gamepad Support Library "Jamepad"

Jamepad is created by William Hartman

When working on one of my games, I wanted to find a library to support gamepad input that wasn’t GameControlPlus, because I found it hard to use and understand. In my search, I found a java library called Jamepad that provides plug-and-play controller support for java programs. It touts support for Xbox 360 controllers, and it even works with my Nintendo Switch Pro Controller. If it supports both, it’ll likely support every traditional gamepad on the market. However, custom mappings for non-traditional controllers are harder to implement, but not impossible.
Important to note: the latest version that will easily work with processing is Jamepad 1.3.2 because the 1.4 version requires a separate dependency.


Installation instructions:
  1. Download Source code (zip) and Jamepad.jar
  2. Unzip Jamepad-1.3.2.zip
  3. In the Jamepad-1.3.2 folder make a new folder called library
  4. Move Jamepad.jar into the library folder
  5. Rename the Jamepad-1.3.2 folder to just Jamepad because processing won’t recognize the library if the .jar name and folder name don’t match
  6. Move the Jamepad folder into the libraries folder, on windows usually located in Documents/Processing/libraries
  7. In your sketch, go to the “sketch” section of the menu bar, then open the “import library” dropdown, and Jamepad should be in the “Contributed” section. Clicking on it should automatically import everything Jamepad needs to run.

The Using Jamepad section of the Github page gives a good introduction to how to set up Jamepad.

If you want to see every piece of information you can grab from the ControllerState object, you can read its code here.

Benefits of using Jamepad

  • Easy to install and implement into programs
  • Easy to understand its core functionality
  • Rumble support
  • Hotplugging controllers (You can connect and disconnect controllers during runtime)

Possible problems with Jamepad (depends on your use case)

  • Order of controllers (like which one is player 1) is hard to manage when connecting multiple controllers. Recommendations for how to deal with this are on the Github page. Fix for single-player games: you can make a ControllerManager with a connection limit of 1 controller to prevent other connected controllers from taking the player 1 spot.
  • Rumble is simple for compatibility reasons: you can only tell the controller to rumble at a certain strength on a left and right channel for a certain duration, nothing more complex. So, you won’t be able to take advantage of the Nintendo Pro Controller’s HD rumble with this library.
  • No Gyro support
  • No built-in calibration or deadzone checking. You must manually implement these features. For easy deadzones, use the constrain() function. To calibrate controllers on Windows, go to Control Panel > Hardware and Sound > Devices and Printers > Right click on desired game controller > Game Controller Settings > Settings > Calibrate
  • Performance may be an issue on very low-power devices because the ControllerManager.getState() method updates every controller’s connection status and creates a new ControllerState object every time it is called. On my Ryzen 3600 at ~4 GHz, calling getState() once takes 0.04ms. Barely anything, but on a really old CPU, it is something to consider.
  • Speaking of performance, connecting a controller at runtime causes significant slowdown for a few frames, so you may want to consider making your games work with variable framerates using a frame time variable to scale every time-based action by the amount of time it takes to complete 1 frame. Example:
//using millis()
int deltaTime, pTime; //pTime for "previous time"
void draw(){
  deltaTime = millis() - pTime;
  pTime = millis();
}
//using System.nanoTime() is MUCH more accurate, noticeably smoother, but less performant
long pTime;
float deltaTime; //using a float in the previous example doesn't provide any extra precision
void draw(){
  long curTime = System.nanoTime();
  //nanoTime() is slower to call, so calling it once actually improves performance a lot.
  //You can use the same strategy in the previous example, but the difference is negligible
  //because millis() is much faster.
  deltaTime = ((curTime - pTime) * 0.000001); //convert nanoseconds to milliseconds
  pTime = curTime;
}
//keep in mind that at 60fps, deltaTime will be about 16-17 milliseconds
  • Also, the last possible issue is the inability to send lighting data to controllers, so you can’t change the LEDs on the PS4 controller or the Pro controller. In the Pro Controller’s case, the Home button LED with turn on at 100% brightness whenever it connects to Jamepad and will stay lit until the controller gets turned off or goes to sleep.
2 Likes

Respected sir,
I am trying to rumble my xbox 360 controller via your jamepad library. As I am not much into coding and can’t find any examples for your library, I will be glad if you could provide the code required only to rumble the controller.
Also the d-pad has got 8(additional 4 diagonals) button how can I add them in the library
Regards,
sriram

Hey sriram0510,
Concerning rumble, the library includes a method as part of the ControllerManager class called doVibration which is used to rumble a controller. Hopefully it will work for you.

Concerning the controller’s dpad, there is no way to remap them using the ControllerState class in the library. The ControllerState object is implemented using only 4 dpad directions, and you can’t change the implementation just by using the library. However, you can still detect when a “diagonal” is pressed if two directions are pressed at once:

// for example
ControllerState cs; // pretend this has data
if(cs.dpadUp && cs.dpadLeft && !cs.dpadDown && !cs.dpadRight) {
  // then we know they pressed the "diagonal" up+left
}
if(cs.dpadUp && !cs.dpadLeft && !cs.dpadDown && cs.dpadRight) {
  // then we know they pressed the "diagonal" up+right
}
if(!cs.dpadUp && cs.dpadLeft && cs.dpadDown && !cs.dpadRight) {
  // then we know they pressed the "diagonal" down+left
}
if(!cs.dpadUp && !cs.dpadLeft && cs.dpadDown && cs.dpadRight) {
  // then we know they pressed the "diagonal" down+right
}

Looking deeper into the source code, it seems that remapping of the controller is not possible at all, as the only way to access controller buttons by number is through an enum ControllerButton, which only includes standard buttons. With this information, I must clarify that remapping through the library is not possible, and by “custom mappings” I mean ones you code yourself, like what is shown above.

I would also like to clarify that this is not my library. William Hartman made the library, I’m just promoting it as an alternative to GameControlPlus.

Happy coding!
Shwaa

1 Like

Thank you so much sir it all worked out great but still have problems with deadzone correction, I tried calibration but that didn’t work out great. Any other I can fix that?

Regards
sriram

You can fix it in code by constraining the magnitude of the stick values:

PVector applyDeadzone(float stickX, float stickY, float deadzoneMin, float deadzoneMax) {
  PVector v = new PVector(stickX, stickY);
  float mag = v.mag();
  // if value is too small, return 0
  if (mag < deadzoneMin) {
    v.set(0, 0);
    return v;
  }
  // otherwise constrain value between limits
  v.setMag(constrain(mag, deadzoneMin, deadzoneMax));
  return v;
}

it gives syntax error of Syntax Error - Missing name or ; near ‘ PVector applyDeadzone(’?

Is there way to execute the controller commands using void setup() or loop() coz it says using static and active kinda thing and on removing both setup() and loop() function the problem resolves

As a reference, here is a minimal way to set up a sketch using Jamepad for a single controller, which includes the deadzone code:

// If you installed the library correctly, all these imports should show up when you click "include library" through the menus
import com.badlogic.gdx.jnigen.*;
import com.studiohartman.jamepad.*;
import com.studiohartman.jamepad.tester.*;
import com.badlogic.gdx.jnigen.parsing.*;
import com.badlogic.gdx.jnigen.test.*;
// To my knowledge, you can safely remove all com.github imports
import com.github.javaparser.*;
import com.github.javaparser.ast.*;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.comments.*;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.internal.*;
import com.github.javaparser.ast.stmt.*;
import com.github.javaparser.ast.type.*;
import com.github.javaparser.ast.visitor.*;

ControllerManager cm;
ControllerState gamepad;
PVector leftStick, rightStick;

void setup() {
  cm = new ControllerManager(1);
  cm.initSDLGamepad();
}

void draw() {
  // don't accept controller input if window is not focused (optional)
  if (focused) {
    gamepad = cm.getState(0);
  } else {
    // as if there is no active input occurring
    gamepad = cm.getState(-1);
  }
  leftStick = applyDeadzone(gamepad.leftStickX, gamepad.leftStickY, 0.05, 1);
  rightStick = applyDeadzone(gamepad.rightStickX, gamepad.rightStickY, 0.05, 1);
  
  // do other stuff here
}

PVector applyDeadzone(float stickX, float stickY, float deadzoneMin, float deadzoneMax) {
  PVector v = new PVector(stickX, stickY);
  float mag = v.mag();
  // if value is too small, return 0
  if (mag < deadzoneMin) {
    v.set(0, 0);
    return v;
  }
  // otherwise constrain value between limits and return from range 0-1
  v.setMag(map(constrain(mag, deadzoneMin, deadzoneMax), 0, deadzoneMax, 0, 1));
  return v;
}

Hopefully this clears things up.

// If you installed the library correctly, all these imports should show up when you click "include library" through the menus
import com.badlogic.gdx.jnigen.*;
import com.studiohartman.jamepad.*;
import com.studiohartman.jamepad.tester.*;
import com.badlogic.gdx.jnigen.parsing.*;
import com.badlogic.gdx.jnigen.test.*;
import com.github.javaparser.*;
import com.github.javaparser.ast.*;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.comments.*;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.internal.*;
import com.github.javaparser.ast.stmt.*;
import com.github.javaparser.ast.type.*;
import com.github.javaparser.ast.visitor.*;



ControllerManager cm;
ControllerState gamepad;
PVector leftStick, rightStick;

void setup() {
  cm = new ControllerManager(1);
  cm.initSDLGamepad();
  
}

void draw() {
  ControllerIndex currController = cm.getControllerIndex(0);
  // don't accept controller input if window is not focused (optional)
  if (focused) {
    gamepad = cm.getState(0);
  } else {
    // as if there is no active input occurring
    gamepad = cm.getState(-1);
  }
  leftStick = applyDeadzone(gamepad.leftStickX, gamepad.leftStickY, 0.05, 1);
  rightStick = applyDeadzone(gamepad.rightStickX, gamepad.rightStickY, 0.05, 1);
   currController.isButtonJustPressed(ControllerButton.LEFTSTICK);
  println(currController.getAxisState(ControllerAxis.RIGHTY));
}

PVector applyDeadzone(float stickX, float stickY, float deadzoneMin, float deadzoneMax) {
  PVector v = new PVector(stickX, stickY);
  float mag = v.mag();
  // if value is too small, return 0
  if (mag < deadzoneMin) {
    v.set(0, 0);
    return v;
  }
  // otherwise constrain value between limits and return from range 0-1
  v.setMag(map(constrain(mag, deadzoneMin, deadzoneMax), 0, deadzoneMax, 0, 1));
  return v;
}

I tried the above sketch but it gives Unhandled exception type ControllerUnpluggedException
error

Please help me sir

Regards, 
Sriram