It uses a custom Joystick Gremlin plugin to translate controller inputs to a vJoy virtual controller. ATS or ETS2 can then use the vJoy controller as the steering input. Should be compatible with most controllers, though XBox One controllers may require additional steps.
This is a remake of an AutoHotkey script I made years ago that I never made a guide for. The setup was very convoluted, requiring multiple other programs and some guessing and checking with IDs. The new solution should be more efficient and simpler to use.
Below are some temporary instructions, once it's been tested more and I have time, I will add pictures/videos to make it easier to follow.
- Install Joystick Gremlin and vJoy, links: https://whitemagic.github.io/JoystickGremlin/, https://github.com/shauleiz/vJoy/
- Use Configure vJoy to create a virtual controller, it just needs at least one axis
- Copy code below to new text file, and rename the extension from .txt to .py
- Open Joystick Gremlin as admin and add the .py file as a plugin under the plugins tab
- Press the gear icon next to "Default" to configure the parameters (hover over each option for tooltips)
- Press Activate and check if it's working (use vJoy Monitor or Input Viewer tool in Joystick Gremlin)
- If it works, open the game and add the vJoy device as an input in control settings
- Bind the vJoy axis as steering in control settings (if it keeps binding to your controller, temporarily remove your controller from the input list)
- Make sure you use wheel mode, and set visual steering angle to what you want in gameplay options
Code: Select all
import math
import gremlin
from gremlin.user_plugin import *
from vjoy.vjoy import AxisName
# Configurable variables, adjust using GUI
mode = ModeVariable(
"Mode",
"Mode in which to use these settings"
)
phys_x = PhysicalInputVariable(
"Physical X Axis",
"Physical stick X axis input",
[gremlin.common.InputType.JoystickAxis]
)
phys_y = PhysicalInputVariable(
"Physical Y Axis",
"Physical stick Y axis input",
[gremlin.common.InputType.JoystickAxis]
)
vjoy_axis = VirtualInputVariable(
"vJoy Axis",
"vJoy wheel axis output",
[gremlin.common.InputType.JoystickAxis]
)
rec_btn = PhysicalInputVariable(
"Recenter Button",
"Press to reset wheel to center",
[gremlin.common.InputType.JoystickButton]
)
inner_dz = IntegerVariable(
"Inner Deadzone",
"Size of the inner deadzone",
80,
1,
100
)
max_rot = IntegerVariable(
"Max Degrees of Rotation",
"Degrees of rotations from lock to lock",
1800,
1,
99999
)
is_invert = BoolVariable(
"Invert Rotation",
"Whether to reverse the rotation direction",
False
)
# Internal variables
in_x = 0.0
in_y = 0.0
is_steering = False
prev_angle = 0.0
out_axis = 0.0
# Decorators
dec_x = phys_x.create_decorator(mode.value)
dec_y = phys_y.create_decorator(mode.value)
dec_rec = rec_btn.create_decorator(mode.value)
# Main function
def set_axis(vjoy):
global in_x
global in_y
global is_steering
global prev_angle
global out_axis
# Only affect steering if not in deadzone
if math.hypot(in_x, in_y) >= (inner_dz.value/100):
# Current stick angle
curr_angle = math.atan2(in_y, in_x)
# Main steering logic
if is_steering:
# Difference from previous angle moving counterclockwise
diff_angle_ccw = (curr_angle - prev_angle + 2*math.pi) % (2*math.pi)
# Convert to positive or negative wheel rotation
steer_angle = (2*math.pi - diff_angle_ccw) if diff_angle_ccw > math.pi else -diff_angle_ccw
# Increase or decrease output axis according to amount of rotation
out_axis += -((bool(is_invert.value) - 0.5) * 2) * steer_angle / (2*math.pi) / (max_rot.value/720)
# Update vJoy axis to new value, clamped between -1 and 1
vjoy[vjoy_axis.vjoy_id].axis(vjoy_axis.input_id).value = max(-1.0, min(out_axis, 1.0))
else:
is_steering = True
prev_angle = curr_angle
else:
is_steering = False
# When steering stopped, bring axis back in bounds if needed
out_axis = max(-1.0, min(out_axis, 1.0))
# When stick input received, update internal variables and run main function
@dec_x.axis(phys_x.input_id)
def axis1(event, vjoy):
global in_x
in_x = event.value
set_axis(vjoy)
@dec_y.axis(phys_y.input_id)
def axis2(event, vjoy):
global in_y
in_y = -event.value
set_axis(vjoy)
# Recenter function
@dec_rec.button(rec_btn.input_id)
def recenter(event, vjoy):
global out_axis
if event.is_pressed:
out_axis = 0.0
vjoy[vjoy_axis.vjoy_id].axis(vjoy_axis.input_id).value = out_axis
# Press 1 to print variable values to log for debugging, uncomment to use
# @gremlin.input_devices.keyboard("1", mode.value)
# def debug(event):
# global in_x
# global in_y
# global is_steering
# global prev_angle
# global out_axis
# if event.is_pressed:
# gremlin.util.log(" ".join(["in_x:", str(in_x), "in_y:", str(in_y), "prev_angle", str(math.degrees(prev_angle)), "out_axis", str(out_axis)]))
XBox One controllers may have an issue where Joystick Gremlin cannot detect inputs when not in focus. For me, the issue went away after restarting and unplugging/plugging the controller back in. If not, you may have to use UCR to remap the physical controller to a second vJoy controller, then use that as the input in Joystick Gremlin. Link: https://github.com/Snoothy/UCR
To get this working in other games, you may have to bind everything to the vJoy controller if the game doesn't support simultaneous input from multiple devices. Then you'll probably need something like HIDhide to hide your physical controller from the game.
If you try it, let me know how it goes, I'm open to feedback and suggestions.