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
{
}