Custom XRController

I am trying to add a custom XR controller, but I am confused by the usage of the
InputControlAttribute.
In the description for a custom device it is being used on the members of the
IInputStateTypeInfo struct but all the existing XRControllers seem to use it in their XRController class instead (see Oculus)
They also do not seem to have any custom stateType set up at all.
Is there anything special about XRControllers that I dont understand?

Thanks,
Pascal

The XR devices are built on top of their own subsystem in the Unity runtime. Information about these devices is surfaced in XRDeviceDescriptors which are transmitted by the runtime in JSON through the InputDeviceDescription.capabilities field (this goes for all types of devices; the capabilities field, where present, always contains a JSON description with information specific to the type of device).

The information in those descriptors is used by XRLayoutBuilder to build a device layout on-the-fly based on layouts such as the Oculus layout you linked.

That said, there’s nothing special about the Oculus layout or any of the other XR layouts. You can pretty much ignore the stuff that XRLayoutBuilder does and just build your own layouts on top of layouts such as OculusHMD or OculusTouchController.

The way InputControlAttribute works is that if a type is registered as an InputControl/InputDevice layout, the system crawls through the type looking for properties and fields annotated with that attribute. Whenever it finds one of those, it adds a control to the layout. However, if the type has an InputControlLayoutAttribute attached to it and that attribute has stateType pointing to some type, that type is crawled through instead. That’s the IInputStateTypeInfo structs you are referring to.

All in all, it pretty much comes down to

  • InputControlAttribute on properties => it’s some kind of abstract device definition that some kind of actual device will eventually be based on.
  • InputControlAttribute on fields in a struct => it’s an actual kind of device with a very specific memory layout being described.

To the input system, whether it’s one or the other doesn’t really matter. If no explicit offset is set for the control, it will be assigned an automatic offset. If you’re fine with that, you don’t need to worry about the whole IIInputStateTypeInfo struct business. It’s perfectly fine to do something like this to get a working Oculus Touch controller:

// Create controller.
var controller = InputSystem.AddDevice<OculusTouchController>();

// Send some input to it.
using (StateEvent.From(controller, out var eventPtr))
{
    controller.trigger.WriteValueIntoEvent(1.0f, eventPtr);
    InputSystem.QueueEvent(eventPtr);
}

But let’s say you want to implement your own Oculus Touch controller and send input in a very specific format. So you go:

// Struct to describe the memory layout of your device.
// NOTE: If there's controls that are defined by OculusTouchController
//    that are not defined in this struct, they'll end up with some
//     automatic memory offset somewhere at the end of the struct.
public struct MyOculusTouchControllerState : IInputStateTypeInfo
{
    // Name, if not set, is taken from field. So this ends up with "thumbstick".
    // As that control is already defined in OculusTouchController, which we are
    // based on, we're just adding details to that existing definition. In particular,
    // we're setting a specific offset for the control here because we're applying the
    // attribute to a field (which makes the thing look at the offset for the field).
    [InputControl]
    public Vector2 thumbstick;

    // Other controls...
    [InputControl]
    public float trigger;
    //etc...
    
    FourCC format => new FourCC('M', 'Y', 'T', 'C');
}

// And the layout for our custom device simply refers to the struct
[InputControlLayout(stateType = typeof(MyOculusTouchControllerState)]
public class MyOculusTouchController : OculusTouchController
{
}

Thx for the long reply. It was very helpful.

I could get rid of a lot of redundant code because I do not care about the memory layout as I use OnUpdate to set the values. I guess I can also get rid of the Preserve attributes?

I managed to build a temporary bridge to use the knuckles controllers with the new input system.
Its not perfect but its a good workaround till its offically supported (which is hopefuly soon)

The only thing left is to differentiate between the left and right controller.
Would be glad to get a small hint on that too.

So far I really love how flexible the system is.
The ability to create devices with custom classes is a huge plus.

Having a struct will actually make it simpler in general as you can just set values directly instead of having to call various helper methods to indirectly set state.

The attribute is only relevant if you use code stripping. If “Managed Stripping Level” is set to anything other than “Disabled”, the attributes are necessary for preventing code that is discovered through reflection from getting stripped away.

// For left one.
InputSystem.SetDeviceUsage(myController, CommonUsages.LeftHand);

// For right one.
InputSystem.SetDeviceUsage(myController, CommonUsages.RightHand);