T. Rudder Custom HID Input, Axis Bug

Continuing the discussion from Thrustmaster T.Rudder Not Found On Unity's Input System Package:

T.Rudder Project

Hi, we managed to make a Custom HID input using Input System 1.3.0 for ThrustMaster T.Rudder Pedal. The problem is, because all the inputs on the pedals are connected, we cannot seem to create an equal float output for all the axis input.

There are a total of three axis input that we can get from the pedal, such as:

  1. Left Toe Brake
  2. Rudder
  3. Right Toe Brake

The Left and Right Toe Brake is the same input as any pedal where you just press it and it we can get its float. Where the Rudder is the unique one because it is an input where we can move (not press) up and down the Left and Right Toe Brake where if the Right Toe Brake is at bottom and the Left Toe Brake is at the top (' ,) it will have a float value of 1 and vice versa. Now here’s where all the problem are:

  1. When we press either the Left or Right Toe Brake till the limit and get the float value in Unity, it will continue to update the float value up to three times. Basically, even if the value is already at 1 (Max Value), if we press it down again it will go back to -1 (Min Value) and it will continue to do those three more times.
  2. We cannot seem to find a script to get the Rudder axis and tried to change the offset to 2 (Because it uses a Z axis instead of X and Y) and the InputControl name to “rudder/z”, but all it did is making all the other inputs unrecognizable. [InputControl(name = "rudder/z", offset = 2, format = "VC2B", parameter = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
    **EDIT: We managed to find the rudder input, which actually came from the Left Toe Brake X Input and Right Toe Brake Y Input, but when tried to get the float value, it reacted the same way as mentioned in problem number 1. The value it is getting is also not of an axis value, instead it is a button value. Because of that, when pressed, the value it will emit is only 1, 0 and -1. It will not have a decimal number at all (0.2, 0.16, 0.5, etc).

Here’s the full script for the IInputStateTypeInfo:

using System.Runtime.InteropServices;

using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;

[StructLayout(LayoutKind.Explicit, Size = 32)]
struct TRudderHIDInputReport : IInputStateTypeInfo
    // Because all HID input reports are tagged with the 'HID ' FourCC,
    // this is the format we need to use for this state struct.
    public FourCC format => new FourCC('H', 'I', 'D');

    // HID input reports can start with an 8-bit report ID. It depends on the device
    // whether this is present or not. On the PS4 DualShock controller, it is
    // present. We don't really need to add the field, but let's do so for the sake of
    // completeness. This can also help with debugging.
    [FieldOffset(0)] public byte reportId;

    // The InputControl annotations here probably look a little scary, but what we do
    // here is relatively straightforward. The fields we add we annotate with
    // [FieldOffset] to force them to the right location, and then we add InputControl
    // to attach controls to the fields. Each InputControl attribute can only do one of
    // two things: either it adds a new control or it modifies an existing control.
    // Given that our layout is based on Gamepad, almost all the controls here are
    // inherited from Gamepad, and we just modify settings on them.
    [InputControl(name = "rightToeBrake", layout = "Stick", format = "VC2B")]
    [InputControl(name = "rightToeBrake/x", offset = 0, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
    [InputControl(name = "rightToeBrake/left", offset = 0, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0,clampMax=0.5,invert=false")]
    [InputControl(name = "rightToeBrake/right", offset = 0, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0.5,clampMax=1,invert")]
    [FieldOffset(1)] public byte rightToeBrakeX;

    [InputControl(name = "leftToeBrake", layout = "Stick", format = "VC2B")]
    [InputControl(name = "leftToeBrake/y", offset = 1, format = "BYTE",
        parameters = "invert,normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
    [InputControl(name = "leftToeBrake/up", offset = 1, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0,clampMax=0.5,invert=false")]
    [InputControl(name = "leftToeBrake/down", offset = 1, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0.5,clampMax=1,invert")]
    [FieldOffset(2)] public byte leftToeBrakeY;

    //If we use the rudder variable, it will make both the Left and Right Toe unable to be detected by Unity
    [InputControl(name = "rudder", layout = "Stick", format = "VC2B")]
    [InputControl(name = "rudder/z", offset = 2, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
    [InputControl(name = "rudder/forward", offset = 2, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0,clampMax=0.5,invert")]
    [InputControl(name = "rudder/backward", offset = 2, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0.5,clampMax=1,invert=false")]
    [FieldOffset(3)] public byte rudderZ;

And here’s the full script for the getting the float value in game:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class CheckControl : MonoBehaviour
    [SerializeField] private InputActionAsset inputActionAsset;
    private InputActionMap tRudder;
    private InputActionMap t16000;
    private InputAction leftToeBrake;
    private InputAction rightToeBrake;
    private InputAction t16000lever;

    [SerializeField] private float leftToe;
    [SerializeField] private float rightToe;
    [SerializeField] private Vector2 leverInput;

    private void Start()

        t16000 = inputActionAsset.FindActionMap("T.16000M");
        tRudder = inputActionAsset.FindActionMap("TRudder");
        leftToeBrake = tRudder.FindAction("Left Toe Brake");
        rightToeBrake = tRudder.FindAction("Right Toe Brake");
        t16000lever = t16000.FindAction("Lever");


    private void Update()
        leftToe = leftToeBrake.ReadValue<float>();
        rightToe = rightToeBrake.ReadValue<float>();
        leverInput = t16000lever.ReadValue<Vector2>();

The reason we are trying to create the Custom HID Input for the T. Rudder Pedal, is because it is listed in the Input Debugger as one of the Unsupported Devices.


I’ve conversed with our developer and this is what they got back to me regarding this issue:

Based on this video, if the Windows Thrustmaster driver is installed then this device should report 3 axis controls: X=rightToe, Y=leftToe, Z=rudder.
It’s difficult to know what the device reports via HID without having the device myself, but I would imagine the layout would be something like:

[InputControl(layout = "Axis")] 
public float rightToeBrake; 

[InputControl(layout = "Axis")] 
public float leftToeBrake; 

[InputControl(layout = "Axis")] 
public float rudder;

Notice I used Axis rather than Stick
Also I have removed the format and other attributes as there are good defaults for those and need only specified if needed (here format will default to float format="FLT"
Formats are listed here
It looks like they copy-pasted from our docs , on that same page it mentions using the InputDebugger in Unity to try and understand what the device is reporting, they should try that if it somehow the device is using a different layout.

I hope this helps you, if you have any questions feel free to ask!