LITIENGINE Case Study
How LITIENGINE replaced JInput with Input4j for gamepad support
From JInput to Input4j
LITIENGINE, the pure 2D Java game engine, migrated from JInput to Input4j in version 0.11.0. This migration eliminated the need for native third-party binaries and significantly improved cross-platform support.
Background: The JInput Challenge
LITIENGINE originally used JInput, a popular Java library for game controller input. However, JInput has several significant drawbacks that led the LITIENGINE team to seek an alternative:
Native Dependencies
JInput requires native DLL/SO files (jinput.dll, libjinput.so, libjinput.dylib) to be distributed with the application, complicating deployment.
Maintenance Issues
JInput is no longer actively maintained, with the last major updates years ago, leading to compatibility issues with newer Java versions.
JNI Complexity
JInput uses traditional JNI (Java Native Interface), which requires writing C code and managing native build toolchains.
The Migration to Input4j
In LITIENGINE 0.11.0, the team replaced JInput with Input4j, their own custom input library built on the Foreign Function & Memory API (FFM API). This migration was a major milestone that:
- Eliminated all native third-party binaries from the engine distribution
- Improved cross-platform support for Windows, Linux, and macOS
- Enabled the engine to leverage modern Java features
- Simplified the build and release process
// LITIENGINE 0.11.0+ uses Input4j automatically
dependencies {
implementation 'de.gurkenlabs:litiengine:0.11.1'
}
When you use LITIENGINE 0.11.0 or later, Input4j is automatically included as a transitive dependency, providing seamless gamepad support without any additional configuration.
Benefits of Using Input4j
Input4j provides several advantages over traditional JNI-based libraries like JInput:
No Native Artifacts
Input4j uses FFM API instead of JNI, eliminating the need for DLLs, SOs, or DYLIBs. Just add the JAR to your classpath.
Cross-Platform
Single codebase works on Windows (XInput/DirectInput), Linux (evdev), and macOS (IOKit/HID).
High Performance
Direct native calls without JNI overhead provide low-latency input handling for real-time gaming.
Type Safety
FFM API provides compile-time type checking for native function calls, reducing runtime errors.
Future-Proof
Built on standardized Java technology (FFM API in Java 22+) ensuring long-term support.
Modern API
Clean, intuitive API with both polling and event-based input handling patterns.
Code Examples
Here are examples of how Input4j provides gamepad input handling, the same functionality that LITIENGINE leverages:
Polling Input
try (var inputDevices = InputDevices.init()) {
while (gameIsRunning) {
for (var device : inputDevices.getAll()) {
device.poll();
// Check analog sticks
float leftX = device.getComponentValue(Axis.AXIS_X);
float leftY = device.getComponentValue(Axis.AXIS_Y);
// Check buttons
boolean aPressed = device.getComponentValue(Button.BUTTON_0) > 0;
// Use values for game input
if (Math.abs(leftX) > 0.1f) {
player.moveX(leftX * speed);
}
}
Thread.sleep(16); // ~60 FPS polling
}
}
Event-Based Input
try (var devices = InputDevices.init()) {
var device = devices.getAll().get(0);
// Listen for button presses
device.onButtonPressed(Button.BUTTON_0, () -> {
System.out.println("A button pressed!");
player.jump();
});
// Listen for axis changes
device.onAxisChanged(Axis.AXIS_Y, value -> {
System.out.println("Left stick Y: " + value);
});
// Handle controller disconnect
device.onDisconnected(() -> {
System.out.println("Controller disconnected!");
});
// Keep application running
while (device.isConnected()) {
Thread.sleep(100);
}
}
XInput Support (Xbox Controllers)
// Xbox controller specific mappings
device.onButtonPressed(XInput.A, () -> jump());
device.onButtonPressed(XInput.B, () -> attack());
device.onButtonPressed(XInput.X, () -> interact());
device.onButtonPressed(XInput.Y, () -> menu());
// Triggers and vibration
device.onAxisChanged(XInput.TRIGGER_LEFT, value -> {
// Left trigger - typically used for aim/sprint
});
device.onAxisChanged(XInput.TRIGGER_RIGHT, value -> {
// Right trigger - typically used for fire/shoot
});
// Rumble/vibration feedback
device.rumble(0.5f, 0.5f); // left motor, right motor (0.0 - 1.0)
How LITIENGINE Uses Input4j
LITIENGINE wraps Input4j with a high-level API that makes gamepad handling simple for game developers. Here's how you use gamepads in LITIENGINE:
Basic Gamepad Input
// Access gamepads through LITIENGINE's Input API
GamepadManager gamepads = Input.gamepads();
// Get the currently connected gamepad
Gamepad gamepad = gamepads.current();
if (gamepad != null) {
// Check analog stick values for movement
float leftStickX = gamepad.getAxisValue(Gamepad.Axis.LEFT_STICK_X);
float leftStickY = gamepad.getAxisValue(Gamepad.Axis.LEFT_STICK_Y);
// Apply to entity movement
entity.setVelocityX(leftStickX * speed);
entity.setVelocityY(leftStickY * speed);
}
Event-Based Gamepad Input
// Listen for button presses
Input.gamepads().onPressed((button, value) -> {
if (button.equals(Gamepad.Xbox.A)) {
player.jump();
} else if (button.equals(Gamepad.Xbox.B)) {
player.attack();
} else if (button.equals(Gamepad.Xbox.X)) {
player.interact();
} else if (button.equals(Gamepad.Xbox.Y)) {
openMenu();
}
});
// Listen for button releases
Input.gamepads().onReleased((button) -> {
if (button.equals(Gamepad.Xbox.RT)) {
player.stopFiring();
}
});
// Continuous polling for analog inputs
Input.gamepads().onPoll(pollValue -> {
float leftStickY = pollValue.getValue(Gamepad.Axis.LEFT_STICK_Y);
if (leftStickY > 0) {
player.moveDown(leftStickY);
} else if (leftStickY < 0) {
player.moveUp(Math.abs(leftStickY));
}
});
Gamepad Detection
// Detect when gamepads are connected
Input.gamepads().onGamepadAdded(gamepad -> {
System.out.println("Gamepad connected: " + gamepad.getName());
System.out.println("Type: " + gamepad.getType());
});
// Detect when gamepads are disconnected
Input.gamepads().onGamepadRemoved(gamepad -> {
System.out.println("Gamepad disconnected: " + gamepad.getName());
});
// Get all connected gamepads
for (Gamepad gamepad : Input.gamepads().getAll()) {
System.out.println("Found: " + gamepad.getName());
}
Supported Controller Types
LITIENGINE automatically detects and supports various controller types through Input4j:
- Xbox controllers - Xbox One, Series X/S (via XInput on Windows)
- PlayStation controllers - DualShock 4, DualSense
- Generic USB gamepads - Any HID-compliant controller
- Joysticks - Flight sticks, racing wheels, and specialized input devices
// LITIENGINE provides predefined mappings for common controllers
// Xbox controller buttons
Gamepad.Xbox.A // A button
Gamepad.Xbox.B // B button
Gamepad.Xbox.X // X button
Gamepad.Xbox.Y // Y button
Gamepad.Xbox.LEFT_STICK_X // Left stick X axis
Gamepad.Xbox.LEFT_STICK_Y // Left stick Y axis
Gamepad.Xbox.RIGHT_STICK_X // Right stick X axis
Gamepad.Xbox.RIGHT_STICK_Y // Right stick Y axis
Gamepad.Xbox.LT // Left trigger
Gamepad.Xbox.RT // Right trigger
// DualShock4 controller buttons
Gamepad.DualShock4.CROSS
Gamepad.DualShock4.CIRCLE
Gamepad.DualShock4.TRIANGLE
Gamepad.DualShock4.SQUARE
Gamepad.DualShock4.OPTIONS
Gamepad.DualShock4.SHARE
Summary
The migration from JInput to Input4j represents a significant improvement for LITIENGINE and its users:
For Game Developers
Simpler deployment with no native DLLs to manage. Just add LITIENGINE as a dependency and gamepad support works out of the box.
For End Users
Better cross-platform compatibility. Plug in any gamepad and it just works on Windows, Linux, or macOS.
For the LITIENGINE Team
Easier build and release process with fewer dependencies to manage. Direct control over input handling implementation.
Input4j powers the gamepad experience in LITIENGINE.
Learn More About Input4j