Can't use button arrays for a custom device

I’m building a custom device to process DirectInput (input + ffb). I could get Input Debugger to show button arrays by following code:

[InputControl(name = "dpad", layout = "Dpad", displayName = "D-Pad", sizeInBits = 4, bit = 0)]
[InputControl(name = "button", layout = "Button", arraySize = 28, bit = 4)]
public uint buttons;

While looking at Input Debugger I can tell this creates 28 buttons with increasing offset and bit 4 on all of the buttons in the array. It appears though that the input system doesn’t actually map the buttons properly this way. When I press the button on my device, it DOES change the “buttons” variable properly (no issues there) but neither input system or input debugger detects these button presses beyond first button on the array.

If I however do this:

[InputControl(name = "dpad", layout = "Dpad", displayName = "D-Pad", sizeInBits = 4, bit = 0)]
[InputControl(name = "button1", layout = "Button", bit = 4)]
[InputControl(name = "button2", layout = "Button", bit = 5)]
..
[InputControl(name = "button27", layout = "Button", bit = 30)]
[InputControl(name = "button28", layout = "Button", bit = 31)]
public uint buttons;

it does let me set all buttons just fine.

I’m using the following code to set the bits:

void SetButton(int index, bool value, ref DirectInputControllerState state)
{
    var bit = (uint)1 << index;
    if (value)
    {
        state.buttons |= bit;
    }
    else
    {
        state.buttons &= ~bit;
    }
}

Is there any way to go with the array approach? Declaring each button separately (one extra LOC per button) is just ugly when you can have potentially 128 buttons + d-pad on a DirectInput device

Additionally in ideal case, I could set the button amount per detected DirectInput device but current Custom Device structure doesn’t seem to support this. I need to register the button amount ahead of time with IInputStateTypeInfo - well before the device gets detected.

Yeah, unfortunately arrays of bit-addressed controls are not supported. Sounds like something the device builder should at least diagnose.

Individual layouts indeed need to be more or less fixed (there’s support for “variants” where a single layout can produce several different variations of the same structure but would generally recommend steering clear of that). However, there is a feature to allow custom-tailored layouts to be built for device that are fully known only at runtime. Whether that’s worth the cost largely depends on the specific use case.

The way this is usually set up is by hooking into InputSystem.onFindLayoutForDevice

string MyOnFindLayoutForDevice(ref InputDeviceDescription description, string matchedLayout, InputDeviceExecuteCommandDelegate executeDeviceCommand)
{
    // Let's say you're reporting your devices with the "DirectInput" interface.
    if (description.interfaceName != "DirectInput")
        return null;

    // Check if we already generated a layout.
    if (!string.IsNullOrEmpty(matchedLayout))
        return null;

    // See how many buttons this device has. This kind of stuff is usually reported
    // in JSON format in the InputDeviceDescription.capabilities string.
    var caps = JsonUtility.FromJson<MyDeviceCaps>(description.capabilities);

    // Now register a layout builder that will generate a layout with the exact number
    // of buttons.
    var layoutName = "DirectInput:" + description.product;
    InputSystem.RegisterLayoutBuilder(name: layoutName,
        buildMethod: () =>
        {
            var builder = new InputControlLayout.Builder
                .WithName(layoutName)
                /* ... */;

            // Add buttons.
            for (var i = 0; i < caps.numButtons; ++i)
                builder.AddControl("button" + i)
                    .WithLayout("Button")
                    /* ... */;

            return builder.Build();
        },
        matches: InputDeviceMatcher.FromDeviceDescription(description));
}

[Serializable]
struct MyDeviceCaps
{
    public int numButtons;
}

InputSystem.onFindLayoutForDevice += MyOnFindLayoutForDevice;
1 Like