Lab — First virtual reality application
Turn a Unity scene into a VR workshop: headset tracking, controllers, teleportation, object manipulation and event-driven interactions with OpenXR and XR Interaction Toolkit.

- Estimated duration
- 3 hours
- Environment
- Unity 6.3 LTS · URP
OpenXR · XRI 3.3.2 - Level
- VR beginner · Unity basics
You will build a small VR cube workshop: look around, teleport, grab and throw objects, spawn a cube with a controller button and change an object’s color by pointing at and selecting it.
This lab usually follows Unity 3D Basics. You should know how to create a scene, edit a Transform, add a component, make a prefab and attach a C# script. If you already have these skills, you can start here directly.
Allow approximately 3 hours, including installation and initial builds; adjust this estimate to the available hardware.
Brest campus only School PC configuration
Lab outline
Continue with: the 3D Navigation lab compares continuous movement, teleportation, multi-scale navigation and Grabbing-the-Air in a city and maze.
1. Prepare the project and packages
Create a simple workshop
- In Unity Hub, create a Universal 3D project with Unity 6.3 LTS (6000.3). Name it
VRBasics. - In
Assets, createScenes,Materials,PrefabsandScriptsfolders. Save the scene asAssets/Scenes/VRBasics.unity. - Add a Plane named
Ground, position(0, 0, 0), scale(2, 1, 2): it measures 20 × 20 meters. Keep its collider. - Add a table using a Cube named
Table, position(0, 0.75, 2), scale(1.5, 0.1, 0.8). Keep its Box Collider; this fixed table does not need a Rigidbody. - Add three cubes with side length
0.15, spaced across the table, at Y0.9. Add a Rigidbody to each, with Use Gravity enabled and Is Kinematic disabled. Create differently colored Universal Render Pipeline/Lit materials and assign them. Here, 1 Unity unit represents 1 meter.
Check: in Play, the cubes drop slightly and rest on the table. Stop Play before continuing.
Install the XR stack
Open Window > Package Management > Package Manager, then Unity Registry. Use the following stable versions as the session reference; avoid pre versions and do not update packages during the lab.
| Package | Identifier | Role / reference |
|---|---|---|
| Input System | com.unity.inputsystem | Actions and bindings; 1.20.0, as in the Unity lab. |
| XR Plug-in Management | com.unity.xr.management | Enable XR providers for each platform; Unity resolves a compatible version. |
| OpenXR Plugin | com.unity.xr.openxr | Communication with the XR system; 1.16.1. |
| XR Interaction Toolkit | com.unity.xr.interaction.toolkit | Interaction and locomotion; 3.3.2. |
The Unity 6.3 pages list XRI 3.3.2 and OpenXR 1.16.1 as the reference versions released for this editor. Newer branches exist; this lab fixes a common baseline so everyone can find the same components and samples.
- Install any missing packages. If needed, use Install package by name with the identifier and version above. Let Unity install their dependencies.
- In Edit > Project Settings > Player > Other Settings > Configuration, check Active Input Handling = Input System Package (New). Accept a restart if requested.
- In XR Interaction Toolkit > Samples, import Starter Assets and XR Interaction Simulator. Choose the latter carefully: XR Device Simulator is a separate sample containing the older simulator.
- Open Edit > Project Settings > XR Plug-in Management > Project Validation. Select the platform to check, read the reported issues and apply the fixes relevant to the installed packages and samples. If a rule asks you to create a Teleport interaction layer, apply it. Return here after choosing the headset platform.
- Record the installed versions. Keep
Packages/manifest.jsonandPackages/packages-lock.jsonwith the project so you can reproduce its configuration.
2. Set up the viewpoint and controllers
- Delete the initial Main Camera, or disable its GameObject. Keep the light and ground.
- In Project, open
Assets/Samples/XR Interaction Toolkit/3.3.2/Starter Assets/Prefabs. Drag XR Origin (XR Rig) into Hierarchy. - Set the rig’s root Transform to position
(0, 0, 0), rotation(0, 0, 0)and scale(1, 1, 1). In its XR Origin component, choose Requested Tracking Origin Mode = Floor for a headset using floor tracking. Check that Camera references the rig’s child camera. - Add GameObject > XR > XR Interaction Manager if the scene does not already contain one. Keep a single manager for this lab. You can assign it to the Interaction Manager field of the interactables you create later.
- Expand the rig and locate the camera, left and right controllers, their interactors and the Locomotion group. Check that the rig’s Input Action Manager references XRI Default Input Actions, supplied by the Starter Assets. Keep the prefab’s action references.
Landmarks to find in Hierarchy — child object names may vary slightly:
VRBasics
├── Directional Light
├── Ground
├── Table
├── Cubes
├── XR Interaction Manager
└── XR Origin (XR Rig)
├── Camera Offset
│ ├── Main Camera
│ ├── Left Controller …
│ └── Right Controller …
└── Locomotion …
First test without a headset
- In
Assets/Samples/XR Interaction Toolkit/3.3.2/XR Interaction Simulator, find the XR Interaction Simulator prefab. Drag it into the scene alongside the rig. - Enter Play and click inside Game. Use the simulator’s on-screen help to move the viewpoint, then simulate each controller. Tab switches between FPS mode and device manipulation; the controls shown in Game let you choose controllers. On an AZERTY keyboard, check or adjust the sample’s bindings if a key is difficult to reach.
- Check that the viewpoint and both controllers respond. The simulator produces XR input; it does not replace the rig’s components.
Do not enable automatic simulator insertion in the XRI settings while also adding its prefab manually. Disable the simulator GameObject whenever testing with a headset and before building. Testing without a headset checks interaction behavior; perceived scale, tracking and comfort must be evaluated in the headset.
Reference: XR Interaction Simulator installation and controls.
3. Run the application on a headset
Perform this first test as soon as hardware is available. If headsets are shared, continue the exercises with the simulator while you wait.
Standalone Meta Quest: build and install an APK
- The instructor or owner must prepare the headset for development: developer mode enabled, a USB data cable and the USB debugging prompt accepted inside the headset. In Unity Hub, check that your Unity installation includes Android Build Support, Android SDK & NDK Tools and OpenJDK.
- Open File > Build Profiles, select Meta Quest, then Switch Platform. If you use the workstation’s Android profile, switch to it and apply the Quest settings below. Unity 6.3 provides a dedicated Meta Quest profile, based on Android, which installs OpenXR and preconfigures several settings.
- In Project Settings > XR Plug-in Management, select the Android tab and enable OpenXR as the provider. Use this stack for the lab; leave the Oculus provider disabled.
- Under XR Plug-in Management > OpenXR, still for Android, enable Meta Quest Support and add Oculus Touch Controller Profile to Interaction Profiles. Here, “Oculus Touch” names an OpenXR controller profile. Consult the Meta Quest Support settings for profiles specific to your hardware.
- In Player > Other Settings, check Scripting Backend = IL2CPP and Target Architectures = ARM64. With the Meta Quest profile, keep its preconfigured graphics settings. With Android, apply OpenXR fixes appropriate to the headset. Return to Project Validation, select Android and fix blocking errors.
- In Build Profiles > Scene List, add the open scene:
VRBasicsmust be checked and first. Give the application a unique identifier, such ascom.yourname.vrbasics, in Player settings. - Select the headset in Run Device, refreshing the list if needed, then choose Build And Run. Save the APK in a
Buildsfolder at the project root, outsideAssets. The first build often takes longer than subsequent builds.
Check: in the headset, look at the table, turn your head and move slightly within your physical play area. The viewpoint should follow your movements; the controllers should follow your hands. The cubes cannot be grabbed yet.
PC headset: test directly in the editor
On a workstation compatible with the headset and its PC software, select the PC platform and enable OpenXR in the PC tab of XR Plug-in Management. Configure a suitable controller profile, start the manufacturer’s OpenXR runtime, then enter Play with the simulator disabled. A Quest in Link mode also needs its connection to the PC software; simply connecting a USB cable does not create a PC VR session. Android settings do not configure PC testing.
4. Move by teleportation
- In the rig’s Locomotion group, locate Locomotion Mediator, XR Body Transformer and Teleportation Provider. Check their references to the rig and mediator. Keep this XRI 3 structure: older tutorials using Locomotion System do not describe this configuration.
- Add Teleportation Area to
Ground. Assign the XR Interaction Manager and the rig’s Teleportation Provider to the corresponding fields. Check that the ground collider appears in Colliders, or leave automatic discovery if it finds it correctly. - Set its Interaction Layer Mask to Teleport. If this layer is missing, create it under Project Settings > XR Plug-in Management > XR Interaction Toolkit > Interaction Layers, then use it on both the surface and teleport ray.
- On the rig’s controllers, find the dedicated teleport interactors. Their Interaction Layer Mask must include Teleport, and their Raycast Mask must include the ground’s physics layer. Keep the sample’s preconfigured Teleport Mode and confirmation actions. On each controller’s Controller Input Action Manager, uncheck Smooth Motion Enabled and Smooth Turn Enabled to start with teleportation and snap turning. The prefab initially enables continuous movement on the left and teleportation on the right.
- In XRI Default Input Actions, find the XRI Left Locomotion and XRI Right Locomotion maps. Read the Teleport Mode and Teleport Mode Cancel bindings. With the standard thumbstick controller bindings, push the thumbstick forward to aim, then release to confirm; check the actual binding and the surface’s Teleport Trigger if behavior differs.
- Test three destinations on the ground. Also aim at the table: it should remain a surface you cannot teleport onto. Watch the XR Origin’s root Transform during the test.
Distinguish two masks: a GameObject’s physics Layer and the Raycast Mask filter Unity collisions/raycasts; the Interaction Layer Mask filters XRI relationships. Creating a physics layer named Teleport does not create an XRI interaction layer. See interaction layers.
Check: a valid destination displays the ray’s visual feedback; confirming moves the rig onto the ground while preserving the headset’s tracked height. If the color does not change, check colliders and masks first, then actions.
Compare navigation modes
Locate Snap Turn Provider, Continuous Turn Provider and the continuous movement provider (Dynamic Move Provider in the Starter Assets). Start with teleportation and snap turning; use the controllers’ Controller Input Action Manager settings to select snap turning and disable continuous movement. Check that only one turning mode is active.
Once the interaction works, briefly try continuous movement if you feel comfortable, then restore the initial configuration. Explain: what moves the camera when you walk physically? What happens when you push the thumbstick? Which mode makes distances and orientation easiest to understand?
Reference: Teleportation Area.
5. Grab and throw cubes
- Add XR Grab Interactable to a cube. Keep its Box Collider, with Is Trigger disabled, and its Rigidbody. Assign the manager. Keep Interaction Layer Mask = Default for manipulable objects and check that the rig’s manipulation interactors accept this layer.
- Choose Select Mode = Single and Movement Type = Velocity Tracking. Leave Throw On Detach enabled. This mode uses the Rigidbody during tracking and lets you start observing collisions; still avoid very fast movements through the table.
- Test grabbing at close range with the controller’s grip, then releasing. Next, try at a distance using the rig’s manipulation ray. Inspect the Select binding in XRI Right Interaction or XRI Left Interaction to confirm which button is used.
- Make a gentle throw away from yourself. The cube should fall and remain on the ground. Its velocity comes from the release tracked by XRI: no projectile script is needed here.
- Turn the cube into a
GrabbableCubeprefab inAssets/Prefabs. Equip the other cubes in the same way or replace them with instances. Keep teleportation objects on their dedicated interaction layer.
Check: you can pick up a cube, put it on the table, pick it up again and throw it. Only one controller holds it at a time. On a copy, compare Velocity Tracking, Kinematic and Instantaneous: observe responsiveness and contact with the table, then restore the initial mode.
Question: how does moving a Transform differ from asking a Rigidbody to follow a target? Why might an object feel more responsive while also passing through obstacles more easily? See the XR Grab Interactable movement modes.
6. Spawn a cube with a controller action
You used a keyboard or mouse action in the Unity lab. The principle remains the same: an action represents an intention, and a binding connects it to a hardware control. Here, you will create a dedicated action so that pressing a button to grab an object does not also spawn a cube.
Define the action
- In
Assets, create anInputfolder, then an asset through Create > Input Actions, namedVRLabActions. - Double-click it and create an Action Map named
Lab, then a Spawn action with Action Type = Button. - Add the binding
<XRController>{RightHand}/secondaryButton: the right controller’s secondary button, usually B on Quest. Keep the action triggered on press; do not use a Value or Pass Through pose action. - Also add a
<Keyboard>/nbinding to check your script without a headset. Save the asset with Save Asset if autosave is disabled.
Write and connect ObjectSpawn
Create ObjectSpawn.cs using Create > Scripting > MonoBehaviour Script, then attach it to an empty GameObject named CubeSpawner. The file and class must have the same name.
Expose these fields in the Inspector:
using UnityEngine;
using UnityEngine.InputSystem;
// Fields to place inside the ObjectSpawn : MonoBehaviour class.
[SerializeField] private InputActionReference spawnAction;
[SerializeField] private GameObject cubePrefab;
[SerializeField] private Transform spawnPoint;
Your task:
- Create an empty GameObject named
SpawnPointat the scene root, position(0, 1.2, 2), rotation(0, 0, 0), scale(1, 1, 1). Check in Scene that it is above the tabletop. It is not a child of the table, so it does not inherit its scale. - Assign VRLabActions > Lab/Spawn, the GrabbableCube prefab from Project and the SpawnPoint Transform to the script’s three fields.
- In OnEnable, subscribe a named method, such as
SpawnCube(InputAction.CallbackContext context), tospawnAction.action.performed, then enable the action withEnable(). - In OnDisable, unsubscribe the same method with
-=, then callDisable(). This component alone owns activation of this dedicated action. Do not callDisable()this way on an action shared by the rig. For this exercise, do not addVRLabActionsto another Input Action Manager. - In the callback, use
Instantiate(cubePrefab, spawnPoint.position, spawnPoint.rotation). Check references before using them and limit tests to a few cubes to avoid accumulating physical objects.
The snippets are guidance: complete the class and methods yourself. As in the Unity lab, wait for compilation and fix the first Console error before entering Play.
Check: pressing N spawns a single cube above the table; holding it should not spawn a cube every frame. Disable and re-enable CubeSpawner in the Inspector during Play: each press should still spawn just one cube. Then test the right secondary button in the headset. The spawned cube must be grabbable.
Explain: why use a named method so you can unsubscribe it? Why create a Spawn action instead of reusing Select? Reference: Input System actions.
7. Respond to hovering and selecting an object
Add a small colored marker to the table beside the cubes and keep it fixed. The goal is to show hover feedback, then persistently change its color with each selection.
- Create a cube named
ColorTarget, position(0.55, 0.95, 2), scale(0.2, 0.3, 0.2). Keep its Box Collider. Add XR Simple Interactable, assign the manager, and set Interaction Layer Mask = Default, Select Mode = Single. Do not add XR Grab Interactable: this marker stays on the table. - Create
TargetFeedback.csand attach it to the marker. Expose a Renderer reference, two persistent colors and a hover color. Assign the Renderer in the Inspector. - Write three public methods without parameters:
BeginHover(),EndHover()andToggleColor(). The first two change the hover state; the third toggles the persistent color. A shared method updates the appearance: hover color while pointing at the marker, persistent color otherwise. Leaving hover then reveals the last selected color correctly. - To isolate this marker’s color, you can obtain a material instance once through
targetRenderer.materialin Awake, keep it, then modify its color property. Destroy this instance in OnDestroy. Modifying sharedMaterial would also affect other objects using that asset; do not create a new instance every frame. - Expand Interactable Events on XR Simple Interactable. For each event, click +, drag the
ColorTargetGameObject into the object field, then choose the appropriate TargetFeedback method:
| XRI event | Method to call | Expected effect |
|---|---|---|
| First Hover Entered | BeginHover() | Enable visual feedback on the first ray/contact. |
| Last Hover Exited | EndHover() | Remove feedback after the last ray/contact leaves. |
| Select Entered | ToggleColor() | Toggle the persistent color once per selection. |
The First/Last events prevent one controller leaving the marker from clearing hover while the other still points at it. In the UnityEvent function menu, select your methods’ parameterless versions.
Check: point at the marker with one controller, select it using the grip mapped to Select, then point away: the new color should remain. Point at it with both controllers and move only one away: hover feedback should remain active.
8. Check the application and report your results
Stop Play, save and disable the simulator. Rebuild the application and repeat the workflow on the target headset.
| Test | Expected result |
|---|---|
| Turn and move your head | Tracked view, consistent height and scale. |
| Move both controllers | Visible poses and rays, without unexplained offsets. |
| Teleport onto the ground, then aim at the table | Ground accepted, table rejected, height preserved. |
| Grab, place and throw a cube | Manipulable object, contact with ground and table. |
| Press the Spawn button | One cube per press, at the intended point, grabbable. |
| Hover over and select the marker | Temporary hover feedback, selected color preserved afterward. |
Follow your session’s lab report guidelines. Present the hardware and versions, a screenshot of the rig hierarchy, your scripts and the test results. Distinguish simulator, Play with a PC headset and standalone APK tests; identify hardware tests you could not perform.
In particular, answer these questions:
- What does the XR Origin do compared with the tracked camera?
- How does a physics layer differ from an XRI interaction layer?
- What connects a physical button to your cube spawning method?
- Why distinguish hovering, selection and activation?
- Which movement mode would you choose for a first visit to your scene, and why?
Further exploration
- Add a Teleportation Anchor to reach a fixed observation point and compare it with a free teleport area.
- Add an XR Socket Interactor to store a cube at a precise location.
- Add a World Space interface with an XR UI Input Module and a Tracked Device Graphic Raycaster; use the ray to operate a reset button.
- Explore haptic feedback and check its effect in the headset, since the simulator cannot reproduce the sensation.
- Continue with the VR comfort lab or the XR Engineering & Debugging lab to structure and instrument a larger application.
Troubleshooting
Fix the first red Console error first. When asking for help, always provide the step, active platform, versions and exact message.
| Symptom | Priority checks |
|---|---|
| Camera does not move or has the wrong height | One rig and one active camera; XR Origin Camera reference; Floor/Device mode; simulator active only without a headset; OpenXR provider for the target platform. |
| Headset detected over USB but no VR in Play | A USB connection for APK installation does not provide PC VR. Check the PC runtime, Link mode if applicable and PC OpenXR provider, or use Build And Run for standalone mode. |
| Hands or rays do not respond | Input System enabled, Input Action Manager and sample actions; suitable OpenXR profiles; controllers awake; no old FPS camera active. |
| Simulator does not respond | Game has focus; only one simulator; preconfigured rig present; sample help and bindings; no competing headset session. |
| Ray visible but no teleportation | Ground collider, XRI Teleport layer on both sides, Raycast Mask, provider and mediator, Teleport Mode action, surface trigger mode. |
| Cube cannot be grabbed or passes through objects | Collider, Rigidbody, XR Grab Interactable, manager, manipulation masks and Select action; check Movement Type and gesture speed. |
| N works but the headset button does not | RightHand/secondaryButton binding, OpenXR profile, correct hand and APK rebuilt after changes. |
| No cube or several cubes per press | ObjectSpawn references, action enabled, single subscription and matching unsubscription; no instantiation in Update; no duplicate CubeSpawner. |
| All objects change color | The script probably modifies sharedMaterial; use a material instance kept for the marker. |
| APK cannot be built or headset is missing | Android modules for the correct editor, Project Validation errors, USB authorization, data cable, Run Device, IL2CPP/ARM64. On Linux, also check USB access permissions and any build tools requested by Unity. |
| Old scene appears in the headset | Active Scene List and profile, VRBasics saved, new Build And Run completed without errors. |
Continue exploring
The 3D interaction: comparing manipulation techniques lab uses a timed construction task to compare direct grabbing, virtual rays, Near Far and two-handed interaction.
Reference documentation
Documentation checked on 15 October 2026. Use the 3.3 XRI branch to match the names in this lab.