TL;DR:

  • XRI 3.0 introduces a composable interaction architecture with separate Interactor, Interactable, and Filter components — significantly more flexible than the monolithic approach of 2.x
  • The new Input System integration is mandatory in 3.0; legacy input pathways are removed, which is the primary migration pain point from older projects
  • Cross-platform support has improved significantly: a single project can now target Meta Quest, PSVR2, PC VR, and Magic Leap 2 without custom input handling for each

XR Interaction Toolkit has been Unity’s opinionated framework for XR interaction since 2020. Version 3.0, released with Unity 6, is a significant architectural revision that addresses the main pain points developers hit with 2.x: the rigid coupling between input, hand tracking, and interaction logic, and the difficulty of extending the system without modifying Unity’s source code.

What Changed in XRI 3.0

New interaction architecture: The core abstraction is now clearly separated:

  • XRBaseInteractor: Something that can interact (controllers, hands, gaze)
  • XRBaseInteractable: Something that can be interacted with (grabbable objects, buttons, levers)
  • XRInteractionGroup: Manages priority when multiple interactors compete for the same interactable
  • Interaction Filters: Composable components that modify interaction selection rules without subclassing

In 2.x, most customisation required subclassing XRGrabInteractable or XRDirectInteractor and overriding methods. In 3.0, you attach filter and affordance components, which compose without inheritance hierarchy issues.

Affordances system: A new IXRInteractableAffordanceStateProvider interface and the associated affordance components drive visual and audio feedback from interaction state changes. Instead of writing code to change a material when an object is hovered, you add an XRInteractableAffordanceStateProvider component and configure Material Property Block affordances in the Inspector.

Input System 2.0 integration: XRI 3.0 is built on Unity’s Input System package (not the legacy Input Manager). The InputActionAsset is now the single source of truth for all XR inputs. This is clean architecturally but requires updating any project that still used the legacy Input.GetButton() style calls.

Hands API redesign: The XRHandSubsystem in XRI 3.0 provides a unified API for hand tracking across Meta Quest, Microsoft HoloLens, and Magic Leap. Hand joints are exposed through a consistent XRHandJoint model regardless of the underlying SDK.

Setting Up a New XRI 3.0 Project

In Unity 6 (2023.3+):

  1. Open Package Manager → Add package from registry → search XR Interaction Toolkit
  2. Also add: XR Plugin Management, OpenXR Plugin
  3. In Project Settings → XR Plug-in Management, enable OpenXR for your target platforms
  4. Add OpenXR feature sets for your targets: Meta Quest Features, PSVR2, etc.
  5. In XRI, import the Starter Assets sample (Package Manager → XR Interaction Toolkit → Samples → Starter Assets)

The Starter Assets sample provides XR Origin, controller and hand input action assets, and a basic scene setup. This is the right starting point rather than assembling the pieces manually.

The XR Origin Setup

XRI 3.0’s XR Origin component replaces the older XR Rig:

XR Origin (Transform)
├── Camera Offset
│   ├── Main Camera (with XRCamera component)
│   ├── LeftHand Controller (XRController + XRDirectInteractor + XRRayInteractor)
│   └── RightHand Controller (XRController + XRDirectInteractor + XRRayInteractor)
└── Locomotion System
    ├── Continuous Move Provider
    ├── Snap Turn Provider
    └── Teleportation Provider

The Locomotion System in 3.0 uses a LocomotionMediator that manages conflicts between simultaneous locomotion requests — for example, preventing teleportation while the user is already in a continuous move action.

Making Objects Grabbable

// Add these components to any GameObject to make it grabbable:
// 1. XRGrabInteractable
// 2. Rigidbody (with Use Gravity = true, or false for held objects)
// 3. Collider(s) for the grab hit detection

// For custom grab logic, implement IXRInteractable events:
[RequireComponent(typeof(XRGrabInteractable))]
public class GrabbableObject : MonoBehaviour
{
    private XRGrabInteractable _grabInteractable;

    void Awake()
    {
        _grabInteractable = GetComponent<XRGrabInteractable>();
        _grabInteractable.selectEntered.AddListener(OnGrabbed);
        _grabInteractable.selectExited.AddListener(OnReleased);
    }

    private void OnGrabbed(SelectEnterEventArgs args)
    {
        // args.interactorObject is the XRBaseInteractor that grabbed this
        var isHand = args.interactorObject is XRHandInteractor;
        Debug.Log($"Grabbed by {(isHand ? "hand" : "controller")}");
    }

    private void OnReleased(SelectExitEventArgs args)
    {
        // Handle release logic
    }
}

Cross-Platform Input with OpenXR

The major win in XRI 3.0 is the OpenXR input abstraction. Instead of writing OVRInput.Get(OVRInput.Button.One) for Meta and XRInputSubsystem calls for everything else, you define input bindings in an InputActionAsset and OpenXR maps them to the correct physical buttons on each platform.

The default XRI action asset binds:

  • XRI LeftHand/Select → left trigger on all controllers
  • XRI RightHand/Select → right trigger on all controllers
  • XRI LeftHand/Primary Button → X (Meta), Square (PSVR2), A (generic)

Add your custom actions to the same asset and they’ll work on any OpenXR-compliant device without platform-specific code.

// Reference input actions through the InputActionReference pattern
[SerializeField] private InputActionReference primaryButtonAction;

void OnEnable() => primaryButtonAction.action.performed += OnPrimaryButton;
void OnDisable() => primaryButtonAction.action.performed -= OnPrimaryButton;

private void OnPrimaryButton(InputAction.CallbackContext ctx)
{
    // Works identically on Quest, PSVR2, Reverb G2
}

Migration from XRI 2.x

The main breaking changes:

  1. Input System dependency: If your project uses legacy Input, you must either add the Input System package (both systems can coexist during migration) or use the compatibility shim. Complete migration is recommended.

  2. ActionBasedController removed: The 2.x ActionBasedController component is replaced by the new XRController + Input Action bindings pattern. Reassign your action references.

  3. XR Rig → XR Origin: The XR Rig MonoBehaviour is removed. Replace with XR Origin. The hierarchy structure is the same but the component differs.

  4. Interaction events: Event argument types changed. SelectEnterEventArgs replaces the older event argument classes. Update any code that reads .interactor (now .interactorObject) or .interactable (now .interactableObject).

Unity’s upgrade guide (available in the XRI 3.0 documentation) covers the automated migration script that handles the most common conversions.

Performance Considerations

XR has strict performance requirements: 90Hz on most headsets means 11ms per frame, and dropped frames cause motion sickness. XRI 3.0 improved several hot paths:

  • Interaction detection uses broad-phase culling before per-object overlap tests
  • Affordance updates are batched where possible
  • The Hands API uses burst-compiled job system paths for joint updates

Profile your scenes with Unity’s Frame Debugger and GPU Usage profiler — the single most common performance issue in XR scenes is overdraw from transparent materials, followed by too many realtime lights. XRI itself is rarely the bottleneck.

XRI 3.0 makes cross-platform XR development in Unity genuinely more maintainable. The composition-over-inheritance approach means you spend less time fighting the framework’s architecture and more time building the experience itself.