<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Virtual Reality | Étienne Peillard</title><link>https://www.etiennepeillard.com/category/virtual-reality/</link><atom:link href="https://www.etiennepeillard.com/category/virtual-reality/index.xml" rel="self" type="application/rss+xml"/><description>Virtual Reality</description><generator>Wowchemy (https://wowchemy.com)</generator><language>en-us</language><lastBuildDate>Thu, 15 Oct 2026 00:00:00 +0000</lastBuildDate><image><url>https://www.etiennepeillard.com/media/sharing.png</url><title>Virtual Reality</title><link>https://www.etiennepeillard.com/category/virtual-reality/</link></image><item><title>Lab — 3D interaction: manipulation techniques</title><link>https://www.etiennepeillard.com/teaching/lab/3d-interaction/</link><pubDate>Thu, 15 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.etiennepeillard.com/teaching/lab/3d-interaction/</guid><description>&lt;section class="unity-intro" aria-label="Lab overview">
&lt;div class="unity-intro__top">
&lt;span class="unity-intro__eyebrow">RVRA · RVSI · 3D interaction&lt;/span>
&lt;img class="unity-intro__logo" src="../intro-unity/images/figure-01.png" alt="Unity" width="345" height="146">
&lt;/div>
&lt;dl class="unity-intro__facts">
&lt;div>&lt;dt>Experimental task&lt;/dt>&lt;dd>Build a tower · 2 min per trial&lt;/dd>&lt;/div>
&lt;div>&lt;dt>Provided project&lt;/dt>&lt;dd>Unity 6.3 · XRI 3.3.0&lt;br>OpenXR 1.16.0&lt;/dd>&lt;/div>
&lt;div>&lt;dt>Prerequisites&lt;/dt>&lt;dd>Unity and VR basics · Headset and controllers&lt;/dd>&lt;/div>
&lt;/dl>
&lt;div class="unity-intro__body">&lt;p>You will compare manipulation techniques using a common task: &lt;strong>build a tower of blue and yellow blocks in two minutes&lt;/strong>. The aim is to understand how selection, attachment points and distant interaction affect precision, speed and effort.&lt;/p>
&lt;p>This lab follows the Unity and VR introductions. The task scene and scripts are provided: your work focuses on configuring and evaluating interactions.&lt;/p>
&lt;/div>
&lt;div class="unity-intro__links">
&lt;a class="unity-intro__card" href="Interaction3D_Unity6000.3.zip">
&lt;span class="unity-intro__card-label">Lab files&lt;/span>
&lt;strong>Unity 6.3 project &lt;span aria-hidden="true">↓&lt;/span>&lt;/strong>
&lt;span>Scene, scripts and prefabs · ZIP · approx. 79 MB&lt;/span>
&lt;/a>
&lt;a class="unity-intro__card" href="RVRA_TP_3Dint_report.docx">
&lt;span class="unity-intro__card-label">Lab report&lt;/span>
&lt;strong>Original report template &lt;span aria-hidden="true">↓&lt;/span>&lt;/strong>
&lt;span>Questions and results · DOCX in English&lt;/span>
&lt;/a>
&lt;/div>
&lt;div class="unity-intro__references">
&lt;span>Getting started&lt;/span>
&lt;a href="../intro-unity/">Unity 3D Basics&lt;/a>
&lt;a href="../intro-vr/">VR introduction&lt;/a>
&lt;a href="RVRA_TP_3DInteraction.pdf">Original handout · English PDF&lt;/a>
&lt;/div>
&lt;p class="unity-intro__campus">&lt;span>IMT Atlantique · Brest&lt;/span> Original lab by &lt;strong>Cédric Fleury&lt;/strong>, for the &lt;strong>RVRA&lt;/strong> course, &lt;strong>RVSI&lt;/strong> track of the &lt;strong>FISE&lt;/strong> programme.&lt;/p>
&lt;details class="unity-intro__outline">
&lt;summary>Lab outline&lt;/summary>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#the-task-a-two-coloured-tower">The task: a two-coloured tower&lt;/a>&lt;/li>
&lt;li>&lt;a href="#1-explore-the-project-and-basic-interactions">1. Explore the project and basic interactions&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#open-the-appropriate-version">Open the appropriate version&lt;/a>&lt;/li>
&lt;li>&lt;a href="#try-the-task">Try the task&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#2-set-up-technique-switching-on-each-controller">2. Set up technique switching on each controller&lt;/a>&lt;/li>
&lt;li>&lt;a href="#3-change-the-blocks-manipulation-point">3. Change the blocks’ manipulation point&lt;/a>&lt;/li>
&lt;li>&lt;a href="#4-add-a-virtual-ray">4. Add a virtual ray&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#configure-distant-selection">Configure distant selection&lt;/a>&lt;/li>
&lt;li>&lt;a href="#compare-variants">Compare variants&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#5-test-near-far">5. Test Near Far&lt;/a>&lt;/li>
&lt;li>&lt;a href="#6-use-both-hands">6. Use both hands&lt;/a>&lt;/li>
&lt;li>&lt;a href="#compare-results-and-write-your-report">Compare results and write your report&lt;/a>&lt;/li>
&lt;li>&lt;a href="#troubleshooting">Troubleshooting&lt;/a>&lt;/li>
&lt;li>&lt;a href="#author-and-original-resources">Author and original resources&lt;/a>&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;/details>
&lt;/section>
&lt;h2 id="the-task-a-two-coloured-tower">The task: a two-coloured tower&lt;/h2>
&lt;p>The &lt;strong>blue generator on the left&lt;/strong> produces blue blocks; the &lt;strong>yellow generator on the right&lt;/strong> produces yellow blocks. Move them to the central platform to build your tower.&lt;/p>
&lt;ul>
&lt;li>Each level contains &lt;strong>two blocks of the same colour&lt;/strong>.&lt;/li>
&lt;li>The blocks in each level are &lt;strong>perpendicular&lt;/strong> to those in the previous level.&lt;/li>
&lt;li>Colours alternate between levels: blue, yellow, blue…&lt;/li>
&lt;li>Each trial lasts &lt;strong>2 minutes&lt;/strong>. At the end, count the complete levels still standing that meet these rules.&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure >
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="The lab scene: generators on the left and right, central platform and timer." srcset="
/teaching/lab/3d-interaction/images/tower-task_hu84f045da1e61d9be029ebaf330479c03_81590_5b41dd9788c322c3d4d48b24941eda25.webp 400w,
/teaching/lab/3d-interaction/images/tower-task_hu84f045da1e61d9be029ebaf330479c03_81590_756a7de1c71c8ed4a83a9ff59eeec264.webp 760w,
/teaching/lab/3d-interaction/images/tower-task_hu84f045da1e61d9be029ebaf330479c03_81590_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://www.etiennepeillard.com/teaching/lab/3d-interaction/images/tower-task_hu84f045da1e61d9be029ebaf330479c03_81590_5b41dd9788c322c3d4d48b24941eda25.webp"
width="760"
height="351"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;p>&lt;em>Scene from Cédric Fleury’s original lab. The handout mentions a record of 19 levels after two attempts: a historical reference, without a required target for this session.&lt;/em>&lt;/p>
&lt;h2 id="1-explore-the-project-and-basic-interactions">1. Explore the project and basic interactions&lt;/h2>
&lt;h3 id="open-the-appropriate-version">Open the appropriate version&lt;/h3>
&lt;ol>
&lt;li>Download the &lt;a href="Interaction3D_Unity6000.3.zip">Unity 6.3 project&lt;/a>, extract it and add the &lt;code>Interaction3D&lt;/code> folder through &lt;strong>Unity Hub &amp;gt; Add project from disk&lt;/strong>. Select the folder containing &lt;code>Assets&lt;/code>, &lt;code>Packages&lt;/code> and &lt;code>ProjectSettings&lt;/code>.&lt;/li>
&lt;li>The project was saved with &lt;strong>6000.3.1f1&lt;/strong>. Use the version assigned for your session and keep a copy before converting it. Wait for package resolution and check &lt;strong>Console&lt;/strong> for errors.&lt;/li>
&lt;li>Open &lt;code>Assets/Scenes/Interaction.unity&lt;/code>. Locate both generators, the platform, timer and controllers.&lt;/li>
&lt;li>For a standalone Quest, check Unity’s Android modules, &lt;strong>File &amp;gt; Build Profiles&lt;/strong>, the Android/Meta Quest platform, build scene and XR settings. If needed, follow the deployment section of the &lt;a href="../intro-vr/">VR introduction&lt;/a>. Use &lt;strong>Build And Run&lt;/strong> with a headset prepared for development.&lt;/li>
&lt;/ol>
&lt;aside class="unity-concept unity-concept--advice" role="note" aria-labelledby="unity-concept-1">
&lt;p class="unity-concept__label">Provided configuration&lt;/p>
&lt;h4 id="unity-concept-1" class="unity-concept__title">Keep the project’s package versions&lt;/h4>
&lt;div class="unity-concept__body">&lt;p>The Unity 6.3 project manifest contains &lt;strong>XRI 3.3.0&lt;/strong>, &lt;strong>OpenXR 1.16.0&lt;/strong> and &lt;strong>Input System 1.17.0&lt;/strong>. The imported Starter Assets remain in a folder named &lt;strong>3.1.2&lt;/strong>: this records the sample version when it was imported. Use these existing resources; do not automatically reimport samples from another version.&lt;/p>
&lt;p>The project also contains the Oculus package. Check the active XR provider for your platform and avoid enabling multiple providers for the same headset. The VR introduction uses slightly different versions: follow its configuration method without blindly replacing this project’s packages or rig.&lt;/p>
&lt;p>A &lt;a href="Interaction3D_Unity2022.3.zip">Unity 2022.3.49f1 archive&lt;/a> is also available for sessions using that editor; it contains XRI 3.1.2. Choose one archive as your starting point.&lt;/p>
&lt;/div>
&lt;/aside>
&lt;h3 id="try-the-task">Try the task&lt;/h3>
&lt;p>The controls below follow the supplied handout for Touch controllers. Check the project’s bindings if you use other hardware.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Control&lt;/th>
&lt;th>Action&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Left thumbstick&lt;/td>
&lt;td>Move through the scene.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>X&lt;/strong> button&lt;/td>
&lt;td>Switch navigation mode, including teleportation.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Right controller contact + index trigger&lt;/td>
&lt;td>Grab a block or select a generator / Reset button.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Right &lt;strong>B&lt;/strong> / left &lt;strong>Y&lt;/strong> button&lt;/td>
&lt;td>Switch the technique on the corresponding hand once several modes are configured.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ol>
&lt;li>Try continuous movement and teleportation. Find a position from which you can reach the generators and tower.&lt;/li>
&lt;li>Generate a few blocks, grab and rotate them, then release them on the platform.&lt;/li>
&lt;li>Reset the scene and complete a first timed trial. The supplied script starts the timer when the first block is generated after a reset.&lt;/li>
&lt;li>Record your first score, then your best score after a few attempts. Use the same number of trials when comparing configurations.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Record — report part 1:&lt;/strong> both scores, your preferred navigation mode and why it suits this task.&lt;/p>
&lt;h2 id="2-set-up-technique-switching-on-each-controller">2. Set up technique switching on each controller&lt;/h2>
&lt;p>The &lt;strong>Controller Manager&lt;/strong> component (&lt;code>ControllerManager.cs&lt;/code>) lets you switch interactors at runtime without rebuilding the application for each comparison.&lt;/p>
&lt;ol>
&lt;li>Select the right controller, then the left, and locate &lt;strong>Controller Manager&lt;/strong> and its &lt;strong>Switch Mode&lt;/strong> action.&lt;/li>
&lt;li>Each mode must be a &lt;strong>direct child&lt;/strong> of its controller and have the &lt;strong>ControllerMode&lt;/strong> tag. Use the existing &lt;strong>Direct Interactor&lt;/strong> as an example.&lt;/li>
&lt;li>Add subsequent modes as siblings of the Direct Interactor, in the order you want to test them. A tagged mode root can contain several components or child objects.&lt;/li>
&lt;li>At startup, the script collects these children, disables the modes and enables the first one. &lt;strong>B&lt;/strong> or &lt;strong>Y&lt;/strong> cycles to the next while disabling the others. With only one mode, pressing the button makes no visible change.&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure >
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Example of modes under the right controller: Direct, Ray, Ray Force Grab and Near Far." srcset="
/teaching/lab/3d-interaction/images/controller-modes_hu3f23090c82a8fa253f1dd77630bdcddc_25952_cf757ee361caf10e6d60752b5d27e0bd.webp 400w,
/teaching/lab/3d-interaction/images/controller-modes_hu3f23090c82a8fa253f1dd77630bdcddc_25952_cf41912f1bb6ad118e84f2a39c7d83bd.webp 760w,
/teaching/lab/3d-interaction/images/controller-modes_hu3f23090c82a8fa253f1dd77630bdcddc_25952_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://www.etiennepeillard.com/teaching/lab/3d-interaction/images/controller-modes_hu3f23090c82a8fa253f1dd77630bdcddc_25952_cf757ee361caf10e6d60752b5d27e0bd.webp"
width="480"
height="206"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;p>&lt;em>Hierarchy example from the original handout. Object names may vary slightly.&lt;/em>&lt;/p>
&lt;p>&lt;strong>Check:&lt;/strong> after adding a second mode, confirm in Play that only one mode root is active on each hand. Release held objects before switching. Tag the direct child: the script does not search grandchildren when building its mode list.&lt;/p>
&lt;h2 id="3-change-the-blocks-manipulation-point">3. Change the blocks’ manipulation point&lt;/h2>
&lt;p>The initial grab brings a block towards its configured attachment point. Compare this with a grab that preserves the contact point.&lt;/p>
&lt;ol>
&lt;li>Duplicate both generators. Make the new versions visually distinguishable while retaining a blue family and a yellow family.&lt;/li>
&lt;li>In &lt;code>Assets/Prefabs&lt;/code>, duplicate &lt;strong>Blue_cube&lt;/strong> and &lt;strong>Yellow_cube&lt;/strong>. Name the copies, for example, &lt;code>Blue_cube_Dynamic&lt;/code> and &lt;code>Yellow_cube_Dynamic&lt;/code>.&lt;/li>
&lt;li>On each copy, enable &lt;strong>Use Dynamic Attach&lt;/strong> in &lt;strong>XR Grab Interactable&lt;/strong>. Explore &lt;strong>Match Position&lt;/strong>, &lt;strong>Match Rotation&lt;/strong> and &lt;strong>Snap To Collider Volume&lt;/strong>. Change one setting at a time.&lt;/li>
&lt;li>In each new generator’s &lt;strong>Generate Cube&lt;/strong> component, assign the corresponding prefab to &lt;strong>Cube&lt;/strong>. Keep the timer reference.&lt;/li>
&lt;li>Grab blocks near their centre and near an end. Compare positioning and orientation with the original blocks, then repeat the task.&lt;/li>
&lt;/ol>
&lt;aside class="unity-concept" role="note" aria-labelledby="unity-concept-2">
&lt;p class="unity-concept__label">Key concept&lt;/p>
&lt;h4 id="unity-concept-2" class="unity-concept__title">Attachment point and precision&lt;/h4>
&lt;div class="unity-concept__body">The grab point belongs to the &lt;strong>interactable&lt;/strong>; the selection technique belongs to the &lt;strong>interactor&lt;/strong>. Keep the same technique and change only the prefabs to isolate the effect of attachment. &lt;strong>Use Dynamic Attach&lt;/strong> computes an attachment at selection; its options determine the resulting alignment.&lt;/div>
&lt;/aside>
&lt;p>&lt;strong>Record — part 3:&lt;/strong> benefits and limitations of Dynamic Attach when grabbing, rotating and placing blocks. Use concrete observations.&lt;/p>
&lt;p>Reference: &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/xr-grab-interactable.html" target="_blank" rel="noopener">XR Grab Interactable — Unity documentation&lt;/a>.&lt;/p>
&lt;h2 id="4-add-a-virtual-ray">4. Add a virtual ray&lt;/h2>
&lt;h3 id="configure-distant-selection">Configure distant selection&lt;/h3>
&lt;ol>
&lt;li>Add an &lt;strong>XR Ray Interactor&lt;/strong> as a child of the right controller: right-click in Hierarchy, &lt;strong>XR &amp;gt; Ray Interactor&lt;/strong>. Name the mode &lt;code>Ray&lt;/code> and tag its root &lt;strong>ControllerMode&lt;/strong>.&lt;/li>
&lt;li>In &lt;strong>Interaction Layer Mask&lt;/strong>, allow the blocks’ and generators’ layers, but exclude the layer reserved for teleportation (&lt;code>Teleport&lt;/code> or &lt;code>Teleport Layer&lt;/code>, depending on the project).&lt;/li>
&lt;li>Under &lt;strong>Input Configuration&lt;/strong>, configure &lt;strong>Select Input&lt;/strong> for the right trigger, reusing the action reference from the supplied Direct Interactor. Do not assume Select uses the same button as in the previous lab.&lt;/li>
&lt;li>Disable &lt;strong>UI Interaction&lt;/strong> for this activity. Check the ray’s origin, direction and line components.&lt;/li>
&lt;li>Run the application, switch to Ray with &lt;strong>B&lt;/strong>, and test distant generation and grabbing.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Check:&lt;/strong> the ray selects blocks and generators without triggering teleportation. A target must share an XRI interaction layer with the interactor and be detected by its physics raycast: &lt;strong>Interaction Layer Mask&lt;/strong> and &lt;strong>Raycast Mask&lt;/strong> are separate filters.&lt;/p>
&lt;h3 id="compare-variants">Compare variants&lt;/h3>
&lt;p>Duplicate the Ray mode under the same controller and name each variant clearly. Keep the inputs and filters unchanged so that only the option under study differs.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Variant&lt;/th>
&lt;th>Setting to compare&lt;/th>
&lt;th>Expected observation&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Basic ray&lt;/td>
&lt;td>&lt;strong>Force Grab&lt;/strong> disabled&lt;/td>
&lt;td>Manipulate the block at a distance.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ray with retrieval&lt;/td>
&lt;td>&lt;strong>Force Grab&lt;/strong> enabled&lt;/td>
&lt;td>The selected block comes to the hand.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Adjustable attachment&lt;/td>
&lt;td>&lt;strong>Manipulate Attach Transform&lt;/strong> enabled&lt;/td>
&lt;td>Move the attachment using the thumbstick; check translation/rotation inputs.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Repeat a trial with each variant. Examine generator access, transport to the centre, block rotation and release accuracy. If the thumbstick controls several actions, inspect their bindings and possible conflicts with locomotion.&lt;/p>
&lt;p>&lt;strong>Record — part 4:&lt;/strong> benefits of the ray over direct grabbing, benefits of direct grabbing, advantages and drawbacks of Force Grab, usefulness of Manipulate Attach Transform, preferred configuration and best score.&lt;/p>
&lt;p>Reference: &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/xr-ray-interactor.html" target="_blank" rel="noopener">XR Ray Interactor — Unity documentation&lt;/a>.&lt;/p>
&lt;h2 id="5-test-near-far">5. Test Near Far&lt;/h2>
&lt;ol>
&lt;li>Find &lt;strong>Right_NearFarInteractor&lt;/strong> in &lt;code>Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Prefabs/Interactors&lt;/code>. Add it under the right controller as a new mode.&lt;/li>
&lt;li>Tag its root &lt;strong>ControllerMode&lt;/strong>. Check right-hand references, Select inputs and interaction filters, as you did for the ray. Disable UI interaction to keep conditions consistent.&lt;/li>
&lt;li>Select a nearby block, then a distant one. Observe how the technique combines near and distant interaction.&lt;/li>
&lt;li>Compare &lt;strong>Far Attach Mode = Near&lt;/strong> and &lt;strong>Far&lt;/strong>: in the XRI 3.3 documentation, this setting controls retrieval to the hand or manipulation at a distance. The original handout calls this “Force Grab”; locate the corresponding setting in your installed component.&lt;/li>
&lt;li>Complete the task with both variants. Create two modes if you want to compare them using B without rebuilding.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Record — part 5:&lt;/strong> the technique covered in class that is most similar, benefits and drawbacks for this task, and any difficulties when transitioning between near and far interaction. Support your answer with observations.&lt;/p>
&lt;p>Reference: &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/near-far-interactor.html" target="_blank" rel="noopener">Near-Far Interactor — Unity documentation&lt;/a>.&lt;/p>
&lt;h2 id="6-use-both-hands">6. Use both hands&lt;/h2>
&lt;ol>
&lt;li>Add manipulation modes to the left controller too. Use &lt;strong>Left_NearFarInteractor&lt;/strong> for Near Far, or adapt a ray’s inputs and handedness to the left hand.&lt;/li>
&lt;li>Check tags and actions. &lt;strong>Y&lt;/strong> switches the left mode and &lt;strong>B&lt;/strong> the right; do not leave right-hand actions assigned to both hands.&lt;/li>
&lt;li>Try a symmetric combination, then assign different roles to the hands: for example, retrieve blocks with one and position them with the other.&lt;/li>
&lt;li>Compare these combinations with your best one-handed configuration using the same rules and duration.&lt;/li>
&lt;/ol>
&lt;p>The goal is to manipulate two blocks or distribute actions between hands. You do not need to enable simultaneous selection of the same block by both controllers.&lt;/p>
&lt;p>&lt;strong>Record — part 6:&lt;/strong> benefits of two techniques, most efficient combination, justification and best number of levels.&lt;/p>
&lt;h2 id="compare-results-and-write-your-report">Compare results and write your report&lt;/h2>
&lt;p>Use the &lt;a href="RVRA_TP_3Dint_report.docx">original report template in English&lt;/a> and the questions on this page. See also the &lt;a href="../../tutorial/rapport-tp/">general report guidelines&lt;/a>. Question numbering follows the handout: part 2 prepares the interactions and has no separate section in the template.&lt;/p>
&lt;p>For each configuration, record the settings you actually tested:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Configuration&lt;/th>
&lt;th>Left / right hand&lt;/th>
&lt;th>Grab point&lt;/th>
&lt;th>Options&lt;/th>
&lt;th>Trial 1&lt;/th>
&lt;th>Best score&lt;/th>
&lt;th>Observations&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Initial direct&lt;/td>
&lt;td>Specify&lt;/td>
&lt;td>Initial&lt;/td>
&lt;td>Navigation mode&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Dynamic direct&lt;/td>
&lt;td>Specify&lt;/td>
&lt;td>Dynamic&lt;/td>
&lt;td>Attachment settings&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ray&lt;/td>
&lt;td>Specify&lt;/td>
&lt;td>Specify&lt;/td>
&lt;td>Force Grab / attachment&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Near Far&lt;/td>
&lt;td>Specify&lt;/td>
&lt;td>Specify&lt;/td>
&lt;td>Far Attach Mode&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Two hands&lt;/td>
&lt;td>Specify&lt;/td>
&lt;td>Specify&lt;/td>
&lt;td>Chosen combination&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Allow comparable short practice periods, reset between trials and record the number of repetitions. Separate measured performance from subjective experience: a technique can be fast while requiring more effort. Learning may improve scores throughout the session; a single best trial does not establish that one technique is generally superior.&lt;/p>
&lt;p>&lt;strong>Before finishing:&lt;/strong> save the scene, modified prefabs and &lt;code>Packages&lt;/code> files. Your application should switch modes without rebuilding, generate both colours, reset the task and support one-handed and two-handed trials.&lt;/p>
&lt;h2 id="troubleshooting">Troubleshooting&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Symptom&lt;/th>
&lt;th>Check&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>B / Y does nothing&lt;/td>
&lt;td>Multiple direct children tagged ControllerMode, Switch Mode action and hierarchy order.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>The ray is visible but cannot grab&lt;/td>
&lt;td>Select Input, colliders, Interaction Layer Mask, Raycast Mask and the target’s interactable component.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>The ray triggers teleportation&lt;/td>
&lt;td>Exclude its layer from manipulation modes and check shared inputs.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>A new generator produces the original blocks&lt;/td>
&lt;td>The Generate Cube component’s Cube field and the prefab you actually edited.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Near Far uses the wrong hand&lt;/td>
&lt;td>Left/right prefab, Handedness and action references.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Generation stops after time runs out&lt;/td>
&lt;td>Use Reset before starting again: the supplied timer requires a reset.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>The headset does not launch the scene&lt;/td>
&lt;td>Build scene, platform, XR provider and connection; revisit the VR introduction.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="author-and-original-resources">Author and original resources&lt;/h2>
&lt;p>Original lab designed by &lt;strong>Cédric Fleury&lt;/strong> for the &lt;strong>RVRA&lt;/strong> course, &lt;strong>RVSI&lt;/strong> track of the &lt;strong>FISE programme at IMT Atlantique, Brest campus&lt;/strong>. Contact listed in the handout: &lt;a href="mailto:cedric.fleury@imt-atlantique.fr">cedric.fleury@imt-atlantique.fr&lt;/a>.&lt;/p>
&lt;p>This page provides a French and English web adaptation of the supplied handout and resources for the &lt;strong>2025–2026&lt;/strong> academic year. The task and controller illustrations come from the original handout.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="RVRA_TP_3DInteraction.pdf">Original handout — English PDF&lt;/a>.&lt;/li>
&lt;li>&lt;a href="RVRA_TP_3Dint_report.docx">Original report template — English DOCX&lt;/a>.&lt;/li>
&lt;li>&lt;a href="Interaction3D_Unity6000.3.zip">Unity 6.3 project&lt;/a>: project files retained; precompiled APK and macOS metadata removed to reduce the download size.&lt;/li>
&lt;li>&lt;a href="Interaction3D_Unity2022.3.zip">Original Unity 2022.3 project&lt;/a>.&lt;/li>
&lt;/ul></description></item><item><title>Lab — 3D navigation in virtual reality</title><link>https://www.etiennepeillard.com/teaching/lab/3d-navigation/</link><pubDate>Thu, 15 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.etiennepeillard.com/teaching/lab/3d-navigation/</guid><description>&lt;section class="unity-intro" aria-label="Lab overview">
&lt;div class="unity-intro__top">
&lt;span class="unity-intro__eyebrow">RVRA · RVSI · 3D navigation&lt;/span>
&lt;img class="unity-intro__logo" src="../intro-unity/images/figure-01.png" alt="Unity" width="345" height="146">
&lt;/div>
&lt;dl class="unity-intro__facts">
&lt;div>&lt;dt>Goal&lt;/dt>&lt;dd>Compare locomotion techniques&lt;/dd>&lt;/div>
&lt;div>&lt;dt>Environment&lt;/dt>&lt;dd>Unity 6.3 · XRI 3.3.0&lt;br>Provided project · VR headset&lt;/dd>&lt;/div>
&lt;div>&lt;dt>Prerequisites&lt;/dt>&lt;dd>Unity and VR basics&lt;/dd>&lt;/div>
&lt;/dl>
&lt;div class="unity-intro__body">&lt;p>How can you move efficiently through a virtual environment? In this lab, you will test several &lt;strong>navigation techniques&lt;/strong>, tune their parameters and compare their effects on target search, orientation and comfort.&lt;/p>
&lt;p>The project provides a &lt;strong>city&lt;/strong> and a &lt;strong>small maze&lt;/strong> containing red buttons called “buzzers”. In the maze, measure the time needed to activate &lt;strong>8 buzzers&lt;/strong>. In the city, activate as many as possible in &lt;strong>2 minutes&lt;/strong>.&lt;/p>
&lt;p>This lab builds on &lt;a href="../intro-unity/">Unity 3D Basics&lt;/a> and &lt;a href="../intro-vr/">A First Virtual Reality Application&lt;/a>. The search task is already implemented: your work focuses on locomotion techniques and their evaluation.&lt;/p>
&lt;/div>
&lt;div class="unity-intro__links">
&lt;a class="unity-intro__card" href="#resources-and-starter-project">
&lt;span class="unity-intro__card-label">Lab files&lt;/span>
&lt;strong>Download the project &lt;span aria-hidden="true">↓&lt;/span>&lt;/strong>
&lt;span>Unity 6.3 / 2022.3 · PDF · Report template&lt;/span>
&lt;/a>
&lt;a class="unity-intro__card" href="../intro-vr/">
&lt;span class="unity-intro__card-label">Before you start&lt;/span>
&lt;strong>A first VR application &lt;span aria-hidden="true">→&lt;/span>&lt;/strong>
&lt;span>Tracking, controllers, teleportation and headset builds&lt;/span>
&lt;/a>
&lt;/div>
&lt;p class="unity-intro__help">Original lab by &lt;strong>Cédric Fleury&lt;/strong> for the &lt;strong>RVRA&lt;/strong> course, &lt;strong>RVSI&lt;/strong> track, &lt;strong>FISE, IMT Atlantique, Brest campus&lt;/strong>. Bilingual web adaptation of the supplied handout and projects.&lt;/p>
&lt;details class="unity-intro__outline">
&lt;summary>Lab outline&lt;/summary>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#resources-and-starter-project">Resources and starter project&lt;/a>&lt;/li>
&lt;li>&lt;a href="#1-test-basic-navigation">1. Test basic navigation&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#open-and-run-the-scene">Open and run the scene&lt;/a>&lt;/li>
&lt;li>&lt;a href="#interact-and-measure">Interact and measure&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#2-configure-continuous-movement">2. Configure continuous movement&lt;/a>&lt;/li>
&lt;li>&lt;a href="#3-switch-between-navigation-techniques">3. Switch between navigation techniques&lt;/a>&lt;/li>
&lt;li>&lt;a href="#4-add-teleportation">4. Add teleportation&lt;/a>&lt;/li>
&lt;li>&lt;a href="#5-add-multi-scale-navigation">5. Add multi-scale navigation&lt;/a>&lt;/li>
&lt;li>&lt;a href="#6-test-grabbing-the-air">6. Test Grabbing-the-Air&lt;/a>&lt;/li>
&lt;li>&lt;a href="#7-bonus-world-in-miniature-with-teleportation">7. Bonus: World-in-Miniature with teleportation&lt;/a>&lt;/li>
&lt;li>&lt;a href="#report-and-discussion">Report and discussion&lt;/a>&lt;/li>
&lt;li>&lt;a href="#troubleshooting-and-reminders">Troubleshooting and reminders&lt;/a>&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;/details>
&lt;/section>
&lt;h2 id="resources-and-starter-project">Resources and starter project&lt;/h2>
&lt;p>Download &lt;strong>both archives for one editor version&lt;/strong> and extract them into &lt;strong>the same parent folder&lt;/strong>: their contents merge into &lt;code>Navigation3D&lt;/code>. The resulting folder must contain &lt;code>Assets&lt;/code>, &lt;code>Packages&lt;/code> and &lt;code>ProjectSettings&lt;/code>, including &lt;code>Assets/VRTemplateAssets&lt;/code>. Add &lt;strong>this &lt;code>Navigation3D&lt;/code> folder&lt;/strong> to Unity Hub. Keep the two editor versions in separate folders.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Version&lt;/th>
&lt;th>Project and scene&lt;/th>
&lt;th>VR template assets&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Unity 6.3 — main route&lt;/strong>&lt;/td>
&lt;td>&lt;a href="resources/navigation3d-unity6000.3-project.zip">6000.3 project&lt;/a>&lt;/td>
&lt;td>&lt;a href="resources/navigation3d-unity6000.3-template-assets.zip">6000.3 assets&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Unity 2022.3 — alternative&lt;/td>
&lt;td>&lt;a href="resources/navigation3d-unity2022.3-project.zip">2022.3 project&lt;/a>&lt;/td>
&lt;td>&lt;a href="resources/navigation3d-unity2022.3-template-assets.zip">2022.3 assets&lt;/a>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ul>
&lt;li>&lt;a href="resources/RVRA_TP_3DNavigation.pdf">Original handout by Cédric Fleury (PDF, in English)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="resources/RVRA_TP_3Dnav_report.docx">Original report template (DOCX, in English)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="../../tutorial/rapport-tp/">General lab report guidelines&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>The downloads preserve the teaching files from the supplied projects. Builds, caches and local editor files have been omitted; &lt;code>Assets&lt;/code>, &lt;code>Packages&lt;/code> and &lt;code>ProjectSettings&lt;/code> files are retained without modification.&lt;/p>
&lt;aside class="unity-concept unity-concept--advice" role="note" aria-labelledby="unity-concept-1">
&lt;p class="unity-concept__label">Key concept&lt;/p>
&lt;h4 id="unity-concept-1" class="unity-concept__title">Keep the supplied project versions&lt;/h4>
&lt;div class="unity-concept__body">&lt;p>The main project was saved with &lt;strong>Unity 6000.3.1f1&lt;/strong>, &lt;strong>XRI 3.3.0&lt;/strong>, &lt;strong>OpenXR 1.16.0&lt;/strong> and &lt;strong>Input System 1.17.0&lt;/strong>. The alternative uses &lt;strong>Unity 2022.3.49f1&lt;/strong> and &lt;strong>XRI 3.1.2&lt;/strong>. Samples in the Unity 6 project remain under &lt;code>Assets/Samples/XR Interaction Toolkit/3.1.2&lt;/code>: this folder name does not identify the installed package version.&lt;/p>
&lt;p>Open the supplied project without recreating an empty project or updating packages during the lab. The introductory VR lab uses a slightly different configuration; use it for the deployment workflow, then keep this project’s versions and references.&lt;/p>
&lt;/div>
&lt;/aside>
&lt;h2 id="1-test-basic-navigation">1. Test basic navigation&lt;/h2>
&lt;h3 id="open-and-run-the-scene">Open and run the scene&lt;/h3>
&lt;ol>
&lt;li>Open &lt;code>Assets/Scenes/Navigation.unity&lt;/code>. Wait for importing to finish and resolve blocking &lt;strong>Console&lt;/strong> errors before building the application.&lt;/li>
&lt;li>For a standalone Android headset, check the Android modules in Unity Hub and the target platform’s XR configuration. In &lt;strong>File &amp;gt; Build Profiles&lt;/strong> (Unity 6) or &lt;strong>Build Settings&lt;/strong> (2022.3), select Android or the appropriate headset profile. Make sure the &lt;code>Navigation&lt;/code> scene is enabled.&lt;/li>
&lt;li>If necessary, revisit the &lt;a href="../intro-vr/">deployment steps in the introductory VR lab&lt;/a>. Select the headset and choose &lt;strong>Build And Run&lt;/strong>. Android settings apply to standalone builds; PC VR testing in Play requires the appropriate PC configuration and runtime.&lt;/li>
&lt;li>In the headset, locate the score panel, timer, city, maze entrance and maze exit.&lt;/li>
&lt;/ol>
&lt;h3 id="interact-and-measure">Interact and measure&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Initial control&lt;/th>
&lt;th>Effect&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Left thumbstick&lt;/td>
&lt;td>Move forward, backward and sideways.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Right thumbstick&lt;/td>
&lt;td>Turn, in addition to physically rotating your head.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Right controller touching a buzzer + index trigger&lt;/td>
&lt;td>Activate the target: it turns green.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ol>
&lt;li>Practise freely to understand the task and controls.&lt;/li>
&lt;li>&lt;strong>Maze:&lt;/strong> activate the buzzer in front of the score panel, find and activate the 8 buzzers in the maze, then record the time when you exit.&lt;/li>
&lt;li>&lt;strong>City:&lt;/strong> start a fresh attempt and record the number of buzzers activated in 2 minutes. Use the panel’s reset control between attempts.&lt;/li>
&lt;li>Record your baseline results in part 1 of the report.&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure >
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Overview of the virtual city" srcset="
/teaching/lab/3d-navigation/images/navigation-city_hu2f28cd99afaf552a292242aefacb1ece_384843_42ded451db25207caa92544e208bcbc0.webp 400w,
/teaching/lab/3d-navigation/images/navigation-city_hu2f28cd99afaf552a292242aefacb1ece_384843_31e2f5198cb6a429f7a97458386a4998.webp 760w,
/teaching/lab/3d-navigation/images/navigation-city_hu2f28cd99afaf552a292242aefacb1ece_384843_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://www.etiennepeillard.com/teaching/lab/3d-navigation/images/navigation-city_hu2f28cd99afaf552a292242aefacb1ece_384843_42ded451db25207caa92544e208bcbc0.webp"
width="663"
height="460"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;p>&lt;em>Environments used for the search task. Illustration from Cédric Fleury’s original handout.&lt;/em>&lt;/p>
&lt;aside class="unity-concept" role="note" aria-labelledby="unity-concept-2">
&lt;p class="unity-concept__label">Key concept&lt;/p>
&lt;h4 id="unity-concept-2" class="unity-concept__title">Make comparison conditions explicit&lt;/h4>
&lt;div class="unity-concept__body">Keep the same starting rules and stopping criteria. Record the participant, settings, number of attempts and any problems. Learning the map affects scores: distinguish practice from measured attempts. Compare orientation, precision and discomfort as well, without reducing a technique’s quality to its best score.&lt;/div>
&lt;/aside>
&lt;h2 id="2-configure-continuous-movement">2. Configure continuous movement&lt;/h2>
&lt;ol>
&lt;li>In &lt;strong>Hierarchy&lt;/strong>, expand &lt;strong>XR Origin&lt;/strong>, then &lt;strong>Locomotion&lt;/strong>. Find &lt;strong>Move&lt;/strong> and &lt;strong>Turn&lt;/strong>, their providers and their action references.&lt;/li>
&lt;li>Explore the inputs on both components to choose how commands are distributed between the left and right thumbsticks. Change one setting at a time and keep a way to restore the initial configuration.&lt;/li>
&lt;li>Tune translation and turn speeds. Find a compromise between fast movement through streets, precision in corridors and comfort.&lt;/li>
&lt;li>Perform both tasks with &lt;strong>the same configuration and parameters in the city and maze&lt;/strong>.&lt;/li>
&lt;li>In part 2 of the report, record the actions assigned to each thumbstick, the selected speeds, maze completion time and city score. Explain your choices.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Check:&lt;/strong> movement and turning respond independently; one input should not trigger competing behaviours.&lt;/p>
&lt;h2 id="3-switch-between-navigation-techniques">3. Switch between navigation techniques&lt;/h2>
&lt;p>The supplied &lt;strong>&lt;code>NavigationModeManager&lt;/code>&lt;/strong> script on &lt;code>Locomotion&lt;/code> lets you switch techniques without rebuilding the application for each trial.&lt;/p>
&lt;ol>
&lt;li>Inspect its &lt;strong>Switch Mode&lt;/strong> action reference. The handout uses the &lt;strong>Y button on the left controller&lt;/strong>; check the actual binding for your hardware.&lt;/li>
&lt;li>Open the script: it checks the hosting object’s &lt;strong>direct children&lt;/strong> and selects those tagged &lt;strong>&lt;code>NavigationTechnique&lt;/code>&lt;/strong>. At startup, it enables the first one; each press enables the next and disables the others.&lt;/li>
&lt;li>Check the tag on &lt;strong>Move&lt;/strong>, then on the techniques you want to include. Keep at least one correctly tagged child. Child order determines cycling order.&lt;/li>
&lt;li>Keep the manager, shared actions and components required by all techniques enabled. Utility objects should not receive this tag.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Check:&lt;/strong> in Play or in the headset, press Y several times and observe which objects are active. Exactly one technique in the list should be active at a time. &lt;strong>Turn&lt;/strong> can remain shared: check its inputs before reusing the right thumbstick for scaling.&lt;/p>
&lt;h2 id="4-add-teleportation">4. Add teleportation&lt;/h2>
&lt;p>The &lt;strong>Teleportation&lt;/strong> object is already present under &lt;code>Locomotion&lt;/code>. You need to associate a selection ray with it and check its connections to teleportable surfaces.&lt;/p>
&lt;ol>
&lt;li>Find &lt;strong>Teleport Interactor&lt;/strong> under &lt;code>Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Prefabs/Interactors&lt;/code> and add it beneath the supplied rig’s left controller.&lt;/li>
&lt;li>On &lt;strong>XR Ray Interactor&lt;/strong>, set &lt;strong>Interaction Layer Mask&lt;/strong> to include &lt;strong>Teleport&lt;/strong>. Also check &lt;strong>Raycast Mask&lt;/strong> and the ground colliders. XRI interaction layers and physics layers are separate filters.&lt;/li>
&lt;li>Disable &lt;strong>UI Interaction&lt;/strong> and &lt;strong>Manipulate Attach Transform&lt;/strong> for this exercise. Assign &lt;strong>Attach Transform&lt;/strong> and &lt;strong>Ray Origin Transform&lt;/strong> to the appropriate left-controller transforms.&lt;/li>
&lt;li>Under &lt;strong>Input Configuration&lt;/strong>, assign both &lt;strong>Select&lt;/strong> inputs to &lt;strong>XRI Left Locomotion/Teleport Mode&lt;/strong>, as specified in the handout. Check the references in the project’s input action asset.&lt;/li>
&lt;li>In the &lt;strong>Disable Interactor&lt;/strong> script on &lt;code>Teleportation&lt;/code>, assign the &lt;strong>Teleport Interactor&lt;/strong> GameObject to &lt;strong>Associated Interactor&lt;/strong>. Its active state will then follow the technique’s active state.&lt;/li>
&lt;li>Check &lt;code>Teleportation&lt;/code>’s &lt;code>NavigationTechnique&lt;/code> tag, its provider and locomotion references. Check that the ground has a teleportation surface compatible with the &lt;strong>Teleport&lt;/strong> interaction layer.&lt;/li>
&lt;li>Switch to this mode with Y. Test several ground destinations, then return to continuous movement: the teleportation ray should be disabled.&lt;/li>
&lt;li>Compare ray shapes and their range settings. For a straight ray, inspect &lt;strong>Max Raycast Distance&lt;/strong>; for a curve, inspect its shape-specific parameters. Repeat both tasks with your chosen settings.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Record in part 4:&lt;/strong> ray shape and range, justification, maze time, city score, and benefits and drawbacks compared with continuous movement.&lt;/p>
&lt;p>Reference: &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/xr-ray-interactor.html" target="_blank" rel="noopener">XR Ray Interactor — parameters and line types&lt;/a>.&lt;/p>
&lt;h2 id="5-add-multi-scale-navigation">5. Add multi-scale navigation&lt;/h2>
&lt;p>The goal is to change the user’s scale relative to the world to gain different viewpoints and travel greater distances. This control must remain available with continuous movement and teleportation.&lt;/p>
&lt;ol>
&lt;li>Create a new child under &lt;code>Locomotion&lt;/code> for scale control. &lt;strong>Do not tag it &lt;code>NavigationTechnique&lt;/code>&lt;/strong>: it must remain active when Y switches techniques.&lt;/li>
&lt;li>Add the supplied &lt;strong>&lt;code>ContinuousScaleChangeProvider&lt;/code>&lt;/strong> script and assign its reference to the rig’s &lt;strong>Locomotion Mediator&lt;/strong>.&lt;/li>
&lt;li>In Input Actions, look for the scale action described in the handout, &lt;strong>&lt;code>XRI RightHand Locomotion/scale&lt;/code>&lt;/strong>, and check its existence and binding. If it is missing from your archive, create a &lt;strong>Value / Vector2&lt;/strong> action bound to the right thumbstick and enable its action map through the Input Action Manager. Assign it to &lt;strong>Right Hand Scale Action&lt;/strong>. Avoid simultaneously triggering &lt;code>Turn&lt;/code> with this input.&lt;/li>
&lt;li>Read the script: action properties and thumbstick reading are supplied, but &lt;code>Update()&lt;/code> still contains &lt;strong>TODO&lt;/strong> steps.&lt;/li>
&lt;li>Implement input reading, then uniform scaling of the &lt;strong>XR Origin Transform&lt;/strong>. Use elapsed time so the rate is independent of frame rate. Apply strictly positive limits and gradual changes, and check the mediator reference before using it.&lt;/li>
&lt;li>Start with small changes. Observe height, the point about which scaling occurs, interaction reach, apparent speed and collisions.&lt;/li>
&lt;li>Adjust the &lt;strong>Teleport Interactor&lt;/strong>, particularly its range and ray reference frame, to keep destinations usable at different scales. Check the calculated ray and its visible representation separately.&lt;/li>
&lt;li>If possible, add a control that restores the initial scale, then test its usefulness when you lose your bearings.&lt;/li>
&lt;li>Repeat the &lt;strong>2-minute city task&lt;/strong> with continuous movement + scaling, then teleportation + scaling.&lt;/li>
&lt;/ol>
&lt;aside class="unity-concept" role="note" aria-labelledby="unity-concept-3">
&lt;p class="unity-concept__label">Key concept&lt;/p>
&lt;h4 id="unity-concept-3" class="unity-concept__title">Scaling changes several frames of reference&lt;/h4>
&lt;div class="unity-concept__body">The rig changes size relative to the environment. Ray reach, controller size, collisions and travelled distance do not necessarily behave in the same way. Identify what should remain constant in world space and what should follow the rig’s scale. Explain your observations before adjusting parameters.&lt;/div>
&lt;/aside>
&lt;p>&lt;strong>Record in part 5:&lt;/strong> scores for both combinations, benefits and drawbacks of multi-scale navigation, which technique works best with it, and the usefulness of restoring the initial scale. Describe the adjustments you needed.&lt;/p>
&lt;h2 id="6-test-grabbing-the-air">6. Test Grabbing-the-Air&lt;/h2>
&lt;p>This technique lets users move the virtual world through grabbing gestures, in translation and rotation.&lt;/p>
&lt;ol>
&lt;li>Inspect &lt;strong>Grab Move&lt;/strong>, already present under &lt;code>Locomotion&lt;/code>, and its components’ actions. Check that it is included in the technique list.&lt;/li>
&lt;li>Enable this mode with Y and identify the grabbing controls. Test movement with &lt;strong>one hand&lt;/strong>, then &lt;strong>two hands&lt;/strong>; observe movement direction and its effects on orientation.&lt;/li>
&lt;li>Repeat at several scales, then find the &lt;strong>8 maze buzzers&lt;/strong>. Record the time and difficulties you encounter.&lt;/li>
&lt;li>In the &lt;strong>city&lt;/strong>, combine this technique with scale control and measure the score over &lt;strong>2 minutes&lt;/strong>.&lt;/li>
&lt;li>To explore movement without being blocked by the ground or buildings, the handout suggests disabling the XR Origin’s &lt;strong>CharacterController&lt;/strong>. Treat this as a separate trial, record the change in your report and re-enable it to restore the previous conditions.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Record in part 6:&lt;/strong> benefits, drawbacks, precision, physical effort and suitability for multi-scale navigation. Support your answers with situations observed in both environments.&lt;/p>
&lt;p>Handout reference: &lt;a href="https://hci-museum.lri.fr/grabbing-the-air" target="_blank" rel="noopener">Grabbing-the-Air — HCI Museum&lt;/a>.&lt;/p>
&lt;h2 id="7-bonus-world-in-miniature-with-teleportation">7. Bonus: World-in-Miniature with teleportation&lt;/h2>
&lt;p>Create a miniature representation of the city to help locate buzzers and select a distant destination.&lt;/p>
&lt;ol>
&lt;li>Display a miniature world, separate from the full-size environment. Avoid duplicating the score logic, rig or main-scene interactions inside it.&lt;/li>
&lt;li>Allow users to point at a location in the miniature with a virtual ray.&lt;/li>
&lt;li>Convert the selected point from the miniature’s coordinate system to the world’s coordinate system, then move the rig to a valid destination. Account for the miniature’s translation, rotation and scale.&lt;/li>
&lt;li>Check that the destination is accessible and that the miniature remains usable after travelling.&lt;/li>
&lt;li>Compare this search aid with multi-scale navigation: target discovery, orientation and destination precision.&lt;/li>
&lt;/ol>
&lt;p>Sources from the handout: &lt;a href="https://www.cs.cmu.edu/~stage3/publications/95/conferences/chi/paper.html" target="_blank" rel="noopener">Stoakley, Conway and Pausch, &lt;em>Virtual Reality on a WIM&lt;/em>, 1995&lt;/a>, &lt;a href="https://youtu.be/Ytc3ix-He4E" target="_blank" rel="noopener">video demonstration&lt;/a> and &lt;a href="https://github.com/AnBenLa/UnityWorldInMiniature" target="_blank" rel="noopener">UnityWorldInMiniature example&lt;/a>.&lt;/p>
&lt;h2 id="report-and-discussion">Report and discussion&lt;/h2>
&lt;p>Complete the &lt;a href="resources/RVRA_TP_3Dnav_report.docx">original report template&lt;/a>. Its questions are also covered throughout this page. Use the table below to gather results; include units, parameters and the number of attempts.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Condition&lt;/th>
&lt;th>Maze: time for 8 buzzers&lt;/th>
&lt;th>City: buzzers in 2 min&lt;/th>
&lt;th>Settings and observations&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Initial navigation&lt;/td>
&lt;td>Measure&lt;/td>
&lt;td>Measure&lt;/td>
&lt;td>Supplied configuration&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Configured continuous movement&lt;/td>
&lt;td>Measure&lt;/td>
&lt;td>Measure&lt;/td>
&lt;td>Same parameters in both environments&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Teleportation&lt;/td>
&lt;td>Measure&lt;/td>
&lt;td>Measure&lt;/td>
&lt;td>Ray shape and range&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Continuous + multi-scale&lt;/td>
&lt;td>—&lt;/td>
&lt;td>Measure&lt;/td>
&lt;td>Scale limits and control&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Teleportation + multi-scale&lt;/td>
&lt;td>—&lt;/td>
&lt;td>Measure&lt;/td>
&lt;td>Ray adjustments&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Grabbing-the-Air&lt;/td>
&lt;td>Measure&lt;/td>
&lt;td>Measure with multi-scale&lt;/td>
&lt;td>One/two hands, collisions&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Conclude by relating performance to each environment’s characteristics. Which technique would you choose for searching the city? For travelling through a narrow space? Discuss the trade-off between efficiency, orientation and comfort, and the limitations of your trials.&lt;/p>
&lt;h2 id="troubleshooting-and-reminders">Troubleshooting and reminders&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Problem&lt;/th>
&lt;th>Checks&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Camera stays still in the headset&lt;/td>
&lt;td>Disable the simulator before building; check XR tracking and the target platform’s provider.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Y does not switch techniques&lt;/td>
&lt;td>Active Switch Mode action, controller binding, direct children tagged &lt;code>NavigationTechnique&lt;/code>, non-empty list.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ray visible but teleportation fails&lt;/td>
&lt;td>XRI Teleport layer, physics mask, ground collider and surface, Select actions, provider and mediator.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ray appears in every mode&lt;/td>
&lt;td>Associated Interactor reference and activation of Teleportation by the manager.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Scaling has no effect or throws an exception&lt;/td>
&lt;td>Completed script, assigned mediator, active Vector2 action, correct XR Origin target.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Turning and scaling happen together&lt;/td>
&lt;td>Right-thumbstick actions shared by Turn and the scale provider.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Changes do not appear in the headset&lt;/td>
&lt;td>Save the scene, rebuild and install the correct scene from the correct project copy.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The supplied &lt;strong>XR Interaction Simulator&lt;/strong> lets you check logic in the editor: enable it for testing without a headset and consult its control overlay. Disable it before headset trials and builds. Performance, perceived scale and comfort must be evaluated in the headset. Take breaks and stop a trial if you feel discomfort.&lt;/p>
&lt;p>Documentation: &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/locomotion.html" target="_blank" rel="noopener">XRI 3.3 locomotion&lt;/a> for the Unity 6 project; &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.1/manual/index.html" target="_blank" rel="noopener">XRI 3.1 manual&lt;/a> for the 2022.3 alternative.&lt;/p>
&lt;p>&lt;strong>Credits:&lt;/strong> original handout, search task and starter projects by &lt;strong>Cédric Fleury&lt;/strong>, &lt;strong>RVRA&lt;/strong> course, &lt;strong>RVSI&lt;/strong> track, &lt;strong>FISE, IMT Atlantique — Brest campus&lt;/strong>. Script and action names are preserved to make it easy to connect this page to the original resources.&lt;/p></description></item><item><title>Lab — First virtual reality application</title><link>https://www.etiennepeillard.com/teaching/lab/intro-vr/</link><pubDate>Thu, 15 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.etiennepeillard.com/teaching/lab/intro-vr/</guid><description>&lt;section class="unity-intro" aria-label="Lab overview">
&lt;div class="unity-intro__top">
&lt;span class="unity-intro__eyebrow">Getting started · Virtual reality&lt;/span>
&lt;img class="unity-intro__logo" src="../intro-unity/images/figure-01.png" alt="Unity" width="345" height="146">
&lt;/div>
&lt;dl class="unity-intro__facts">
&lt;div>&lt;dt>Estimated duration&lt;/dt>&lt;dd>3 hours&lt;/dd>&lt;/div>
&lt;div>&lt;dt>Environment&lt;/dt>&lt;dd>Unity 6.3 LTS · URP&lt;br>OpenXR · XRI 3.3.2&lt;/dd>&lt;/div>
&lt;div>&lt;dt>Level&lt;/dt>&lt;dd>VR beginner · Unity basics&lt;/dd>&lt;/div>
&lt;/dl>
&lt;div class="unity-intro__body">&lt;p>You will build a small &lt;strong>VR cube workshop&lt;/strong>: look around, teleport, grab and throw objects, spawn a cube with a controller button and change an object&amp;rsquo;s color by pointing at and selecting it.&lt;/p>
&lt;p>This lab usually follows &lt;a href="../intro-unity/">Unity 3D Basics&lt;/a>. 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.&lt;/p>
&lt;p>Allow approximately 3 hours, including installation and initial builds; adjust this estimate to the available hardware.&lt;/p>
&lt;/div>
&lt;div class="unity-intro__links">
&lt;a class="unity-intro__card" href="../intro-unity/">
&lt;span class="unity-intro__card-label">Before you start&lt;/span>
&lt;strong>Unity 3D Basics &lt;span aria-hidden="true">→&lt;/span>&lt;/strong>
&lt;span>Scenes, components, prefabs and first C# scripts&lt;/span>
&lt;/a>
&lt;a class="unity-intro__card" href="../../tutorial/rapport-tp/">
&lt;span class="unity-intro__card-label">Read first&lt;/span>
&lt;strong>Lab report guidelines &lt;span aria-hidden="true">↗&lt;/span>&lt;/strong>
&lt;span>Report expectations and outline&lt;/span>
&lt;/a>
&lt;/div>
&lt;div class="unity-intro__references">
&lt;span>Documentation&lt;/span>
&lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/">Unity 6.3 manual&lt;/a>
&lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/index.html">XR Interaction Toolkit 3.3&lt;/a>
&lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.openxr@1.16/manual/index.html">OpenXR 1.16&lt;/a>
&lt;/div>
&lt;p class="unity-intro__campus">&lt;span>Brest campus only&lt;/span> &lt;a href="../../tutorial/unity-instructions-pc-ecole/">School PC configuration &lt;span aria-hidden="true">→&lt;/span>&lt;/a>&lt;/p>
&lt;details class="unity-intro__outline">
&lt;summary>Lab outline&lt;/summary>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#1-prepare-the-project-and-packages">1. Prepare the project and packages&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#create-a-simple-workshop">Create a simple workshop&lt;/a>&lt;/li>
&lt;li>&lt;a href="#install-the-xr-stack">Install the XR stack&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#2-set-up-the-viewpoint-and-controllers">2. Set up the viewpoint and controllers&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#first-test-without-a-headset">First test without a headset&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#3-run-the-application-on-a-headset">3. Run the application on a headset&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#standalone-meta-quest-build-and-install-an-apk">Standalone Meta Quest: build and install an APK&lt;/a>&lt;/li>
&lt;li>&lt;a href="#pc-headset-test-directly-in-the-editor">PC headset: test directly in the editor&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#4-move-by-teleportation">4. Move by teleportation&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#compare-navigation-modes">Compare navigation modes&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#5-grab-and-throw-cubes">5. Grab and throw cubes&lt;/a>&lt;/li>
&lt;li>&lt;a href="#6-spawn-a-cube-with-a-controller-action">6. Spawn a cube with a controller action&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#define-the-action">Define the action&lt;/a>&lt;/li>
&lt;li>&lt;a href="#write-and-connect-objectspawn">Write and connect ObjectSpawn&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#7-respond-to-hovering-and-selecting-an-object">7. Respond to hovering and selecting an object&lt;/a>&lt;/li>
&lt;li>&lt;a href="#8-check-the-application-and-report-your-results">8. Check the application and report your results&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#further-exploration">Further exploration&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#troubleshooting">Troubleshooting&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#continue-exploring">Continue exploring&lt;/a>&lt;/li>
&lt;li>&lt;a href="#reference-documentation">Reference documentation&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;/details>
&lt;/section>
&lt;p>&lt;strong>Continue with:&lt;/strong> the &lt;a href="../3d-navigation/">3D Navigation lab&lt;/a> compares continuous movement, teleportation, multi-scale navigation and Grabbing-the-Air in a city and maze.&lt;/p>
&lt;h2 id="1-prepare-the-project-and-packages">1. Prepare the project and packages&lt;/h2>
&lt;h3 id="create-a-simple-workshop">Create a simple workshop&lt;/h3>
&lt;ol>
&lt;li>In &lt;strong>Unity Hub&lt;/strong>, create a &lt;strong>Universal 3D&lt;/strong> project with &lt;strong>Unity 6.3 LTS (6000.3)&lt;/strong>. Name it &lt;code>VRBasics&lt;/code>.&lt;/li>
&lt;li>In &lt;code>Assets&lt;/code>, create &lt;code>Scenes&lt;/code>, &lt;code>Materials&lt;/code>, &lt;code>Prefabs&lt;/code> and &lt;code>Scripts&lt;/code> folders. Save the scene as &lt;code>Assets/Scenes/VRBasics.unity&lt;/code>.&lt;/li>
&lt;li>Add a &lt;strong>Plane&lt;/strong> named &lt;code>Ground&lt;/code>, position &lt;code>(0, 0, 0)&lt;/code>, scale &lt;code>(2, 1, 2)&lt;/code>: it measures 20 × 20 meters. Keep its collider.&lt;/li>
&lt;li>Add a table using a &lt;strong>Cube&lt;/strong> named &lt;code>Table&lt;/code>, position &lt;code>(0, 0.75, 2)&lt;/code>, scale &lt;code>(1.5, 0.1, 0.8)&lt;/code>. Keep its Box Collider; this fixed table does not need a Rigidbody.&lt;/li>
&lt;li>Add three cubes with side length &lt;code>0.15&lt;/code>, spaced across the table, at Y &lt;code>0.9&lt;/code>. Add a &lt;strong>Rigidbody&lt;/strong> to each, with &lt;strong>Use Gravity&lt;/strong> enabled and &lt;strong>Is Kinematic&lt;/strong> disabled. Create differently colored &lt;strong>Universal Render Pipeline/Lit&lt;/strong> materials and assign them. Here, &lt;strong>1 Unity unit represents 1 meter&lt;/strong>.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Check:&lt;/strong> in Play, the cubes drop slightly and rest on the table. Stop Play before continuing.&lt;/p>
&lt;aside class="unity-concept unity-concept--advice" role="note" aria-labelledby="unity-concept-1">
&lt;p class="unity-concept__label">After the Unity lab&lt;/p>
&lt;h4 id="unity-concept-1" class="unity-concept__title">You can reuse your scene&lt;/h4>
&lt;div class="unity-concept__body">&lt;p>Work in a &lt;strong>copy of the project&lt;/strong>, then save a new scene named &lt;code>VRBasics&lt;/code>. Keep the ground, materials and a few objects. Disable the entire FPS player, its camera, its Cinemachine virtual camera and any keyboard/mouse click or spawning scripts. Remove rotation scripts from objects you want to grab. The XR rig provides its own camera and input.&lt;/p>
&lt;p>Scale a few cubes down to a size you can handle and add the table described above. There is no need to import the car or FPS controller again. If your scene is difficult to isolate, start with the small workshop proposed here.&lt;/p>
&lt;/div>
&lt;/aside>
&lt;h3 id="install-the-xr-stack">Install the XR stack&lt;/h3>
&lt;p>Open &lt;strong>Window &amp;gt; Package Management &amp;gt; Package Manager&lt;/strong>, then &lt;strong>Unity Registry&lt;/strong>. Use the following stable versions as the session reference; avoid &lt;code>pre&lt;/code> versions and do not update packages during the lab.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Package&lt;/th>
&lt;th>Identifier&lt;/th>
&lt;th>Role / reference&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Input System&lt;/td>
&lt;td>&lt;code>com.unity.inputsystem&lt;/code>&lt;/td>
&lt;td>Actions and bindings; &lt;strong>1.20.0&lt;/strong>, as in the Unity lab.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>XR Plug-in Management&lt;/td>
&lt;td>&lt;code>com.unity.xr.management&lt;/code>&lt;/td>
&lt;td>Enable XR providers for each platform; Unity resolves a compatible version.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OpenXR Plugin&lt;/td>
&lt;td>&lt;code>com.unity.xr.openxr&lt;/code>&lt;/td>
&lt;td>Communication with the XR system; &lt;strong>1.16.1&lt;/strong>.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>XR Interaction Toolkit&lt;/td>
&lt;td>&lt;code>com.unity.xr.interaction.toolkit&lt;/code>&lt;/td>
&lt;td>Interaction and locomotion; &lt;strong>3.3.2&lt;/strong>.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The Unity 6.3 pages list &lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/com.unity.xr.interaction.toolkit.html" target="_blank" rel="noopener">XRI 3.3.2&lt;/a> and &lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/com.unity.xr.openxr.html" target="_blank" rel="noopener">OpenXR 1.16.1&lt;/a> 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.&lt;/p>
&lt;ol>
&lt;li>Install any missing packages. If needed, use &lt;strong>Install package by name&lt;/strong> with the identifier and version above. Let Unity install their dependencies.&lt;/li>
&lt;li>In &lt;strong>Edit &amp;gt; Project Settings &amp;gt; Player &amp;gt; Other Settings &amp;gt; Configuration&lt;/strong>, check &lt;strong>Active Input Handling = Input System Package (New)&lt;/strong>. Accept a restart if requested.&lt;/li>
&lt;li>In &lt;strong>XR Interaction Toolkit &amp;gt; Samples&lt;/strong>, import &lt;strong>Starter Assets&lt;/strong> and &lt;strong>XR Interaction Simulator&lt;/strong>. Choose the latter carefully: &lt;strong>XR Device Simulator&lt;/strong> is a separate sample containing the older simulator.&lt;/li>
&lt;li>Open &lt;strong>Edit &amp;gt; Project Settings &amp;gt; XR Plug-in Management &amp;gt; Project Validation&lt;/strong>. 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 &lt;strong>Teleport&lt;/strong> interaction layer, apply it. Return here after choosing the headset platform.&lt;/li>
&lt;li>Record the installed versions. Keep &lt;code>Packages/manifest.json&lt;/code> and &lt;code>Packages/packages-lock.json&lt;/code> with the project so you can reproduce its configuration.&lt;/li>
&lt;/ol>
&lt;aside class="unity-concept" role="note" aria-labelledby="unity-concept-2">
&lt;p class="unity-concept__label">Key concept&lt;/p>
&lt;h4 id="unity-concept-2" class="unity-concept__title">What does each part of the XR stack do?&lt;/h4>
&lt;div class="unity-concept__body">&lt;p>&lt;strong>OpenXR&lt;/strong> connects the application to the headset runtime. &lt;strong>Input System&lt;/strong> exposes poses and controls as actions. &lt;strong>XRI&lt;/strong> uses this input to select, grab or move the player.&lt;/p>
&lt;p>The &lt;strong>OpenXR runtime&lt;/strong> is the software that provides the XR session on the machine running the application. On a standalone Quest, it runs in the headset; to test a PC headset in the editor, it must be installed and active on the PC.&lt;/p>
&lt;/div>
&lt;/aside>
&lt;h2 id="2-set-up-the-viewpoint-and-controllers">2. Set up the viewpoint and controllers&lt;/h2>
&lt;ol>
&lt;li>Delete the initial &lt;strong>Main Camera&lt;/strong>, or disable its GameObject. Keep the light and ground.&lt;/li>
&lt;li>In &lt;strong>Project&lt;/strong>, open &lt;code>Assets/Samples/XR Interaction Toolkit/3.3.2/Starter Assets/Prefabs&lt;/code>. Drag &lt;strong>XR Origin (XR Rig)&lt;/strong> into Hierarchy.&lt;/li>
&lt;li>Set the rig&amp;rsquo;s root Transform to position &lt;code>(0, 0, 0)&lt;/code>, rotation &lt;code>(0, 0, 0)&lt;/code> and scale &lt;code>(1, 1, 1)&lt;/code>. In its &lt;strong>XR Origin&lt;/strong> component, choose &lt;strong>Requested Tracking Origin Mode = Floor&lt;/strong> for a headset using floor tracking. Check that &lt;strong>Camera&lt;/strong> references the rig&amp;rsquo;s child camera.&lt;/li>
&lt;li>Add &lt;strong>GameObject &amp;gt; XR &amp;gt; XR Interaction Manager&lt;/strong> if the scene does not already contain one. Keep a single manager for this lab. You can assign it to the &lt;strong>Interaction Manager&lt;/strong> field of the interactables you create later.&lt;/li>
&lt;li>Expand the rig and locate the camera, left and right controllers, their interactors and the &lt;strong>Locomotion&lt;/strong> group. Check that the rig&amp;rsquo;s &lt;strong>Input Action Manager&lt;/strong> references &lt;strong>XRI Default Input Actions&lt;/strong>, supplied by the Starter Assets. Keep the prefab&amp;rsquo;s action references.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Landmarks to find in Hierarchy&lt;/strong> — child object names may vary slightly:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">VRBasics
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── Directional Light
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── Ground
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── Table
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── Cubes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── XR Interaction Manager
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── XR Origin (XR Rig)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── Camera Offset
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├── Main Camera
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├── Left Controller …
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └── Right Controller …
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── Locomotion …
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;aside class="unity-concept" role="note" aria-labelledby="unity-concept-3">
&lt;p class="unity-concept__label">Key concept&lt;/p>
&lt;h4 id="unity-concept-3" class="unity-concept__title">Moving the player or tracking their head?&lt;/h4>
&lt;div class="unity-concept__body">&lt;p>The child camera receives the headset&amp;rsquo;s &lt;strong>tracked pose&lt;/strong>: its position and rotation change as the user moves. The &lt;strong>XR Origin&lt;/strong> transforms these poses into Unity world space. Teleportation will move this origin.&lt;/p>
&lt;p>Do not control the XR camera with the previous lab&amp;rsquo;s FPS script. In &lt;strong>Floor&lt;/strong> mode, height comes from real tracking: do not arbitrarily add 1.70 m to the rig. &lt;strong>Device&lt;/strong> mode instead uses a height offset, including the &lt;strong>Camera Y Offset&lt;/strong> setting.&lt;/p>
&lt;/div>
&lt;/aside>
&lt;h3 id="first-test-without-a-headset">First test without a headset&lt;/h3>
&lt;ol>
&lt;li>In &lt;code>Assets/Samples/XR Interaction Toolkit/3.3.2/XR Interaction Simulator&lt;/code>, find the &lt;strong>XR Interaction Simulator&lt;/strong> prefab. Drag it into the scene alongside the rig.&lt;/li>
&lt;li>Enter Play and click inside &lt;strong>Game&lt;/strong>. Use the simulator&amp;rsquo;s on-screen help to move the viewpoint, then simulate each controller. &lt;strong>Tab&lt;/strong> 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&amp;rsquo;s bindings if a key is difficult to reach.&lt;/li>
&lt;li>Check that the viewpoint and both controllers respond. The simulator produces XR input; it does not replace the rig&amp;rsquo;s components.&lt;/li>
&lt;/ol>
&lt;p>Do not enable automatic simulator insertion in the XRI settings while also adding its prefab manually. &lt;strong>Disable the simulator GameObject whenever testing with a headset and before building.&lt;/strong> Testing without a headset checks interaction behavior; perceived scale, tracking and comfort must be evaluated in the headset.&lt;/p>
&lt;p>Reference: &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/xr-interaction-simulator-overview.html" target="_blank" rel="noopener">XR Interaction Simulator installation and controls&lt;/a>.&lt;/p>
&lt;h2 id="3-run-the-application-on-a-headset">3. Run the application on a headset&lt;/h2>
&lt;p>Perform this first test as soon as hardware is available. If headsets are shared, continue the exercises with the simulator while you wait.&lt;/p>
&lt;h3 id="standalone-meta-quest-build-and-install-an-apk">Standalone Meta Quest: build and install an APK&lt;/h3>
&lt;ol>
&lt;li>The instructor or owner must prepare the headset for development: &lt;strong>developer mode enabled&lt;/strong>, a USB data cable and the USB debugging prompt accepted inside the headset. In Unity Hub, check that your Unity installation includes &lt;strong>Android Build Support&lt;/strong>, &lt;strong>Android SDK &amp;amp; NDK Tools&lt;/strong> and &lt;strong>OpenJDK&lt;/strong>.&lt;/li>
&lt;li>Open &lt;strong>File &amp;gt; Build Profiles&lt;/strong>, select &lt;strong>Meta Quest&lt;/strong>, then &lt;strong>Switch Platform&lt;/strong>. If you use the workstation&amp;rsquo;s &lt;strong>Android&lt;/strong> profile, switch to it and apply the Quest settings below. Unity 6.3 provides a &lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/xr-meta-quest-build-profile.html" target="_blank" rel="noopener">dedicated Meta Quest profile&lt;/a>, based on Android, which installs OpenXR and preconfigures several settings.&lt;/li>
&lt;li>In &lt;strong>Project Settings &amp;gt; XR Plug-in Management&lt;/strong>, select the &lt;strong>Android&lt;/strong> tab and enable &lt;strong>OpenXR&lt;/strong> as the provider. Use this stack for the lab; leave the &lt;strong>Oculus&lt;/strong> provider disabled.&lt;/li>
&lt;li>Under &lt;strong>XR Plug-in Management &amp;gt; OpenXR&lt;/strong>, still for Android, enable &lt;strong>Meta Quest Support&lt;/strong> and add &lt;strong>Oculus Touch Controller Profile&lt;/strong> to &lt;strong>Interaction Profiles&lt;/strong>. Here, “Oculus Touch” names an OpenXR controller profile. Consult the &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.openxr@1.16/manual/features/metaquest.html" target="_blank" rel="noopener">Meta Quest Support settings&lt;/a> for profiles specific to your hardware.&lt;/li>
&lt;li>In &lt;strong>Player &amp;gt; Other Settings&lt;/strong>, check &lt;strong>Scripting Backend = IL2CPP&lt;/strong> and &lt;strong>Target Architectures = ARM64&lt;/strong>. With the Meta Quest profile, keep its preconfigured graphics settings. With Android, apply OpenXR fixes appropriate to the headset. Return to &lt;strong>Project Validation&lt;/strong>, select Android and fix blocking errors.&lt;/li>
&lt;li>In &lt;strong>Build Profiles &amp;gt; Scene List&lt;/strong>, add the open scene: &lt;code>VRBasics&lt;/code> must be checked and first. Give the application a unique identifier, such as &lt;code>com.yourname.vrbasics&lt;/code>, in Player settings.&lt;/li>
&lt;li>Select the headset in &lt;strong>Run Device&lt;/strong>, refreshing the list if needed, then choose &lt;strong>Build And Run&lt;/strong>. Save the APK in a &lt;code>Builds&lt;/code> folder at the project root, outside &lt;code>Assets&lt;/code>. The first build often takes longer than subsequent builds.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Check:&lt;/strong> 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.&lt;/p>
&lt;h3 id="pc-headset-test-directly-in-the-editor">PC headset: test directly in the editor&lt;/h3>
&lt;p>On a &lt;strong>workstation compatible with the headset and its PC software&lt;/strong>, select the PC platform and enable &lt;strong>OpenXR in the PC tab&lt;/strong> of XR Plug-in Management. Configure a suitable controller profile, start the manufacturer&amp;rsquo;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.&lt;/p>
&lt;aside class="unity-concept unity-concept--advice" role="note" aria-labelledby="unity-concept-4">
&lt;p class="unity-concept__label">In the headset&lt;/p>
&lt;h4 id="unity-concept-4" class="unity-concept__title">Start with a comfortable experience&lt;/h4>
&lt;div class="unity-concept__body">Clear the play area and use the wrist straps. Test natural tracking and teleportation first; keep the horizon stable. Remove the headset if you feel uncomfortable. Work in pairs if possible: one person tests while the other observes the scene and collects feedback.&lt;/div>
&lt;/aside>
&lt;h2 id="4-move-by-teleportation">4. Move by teleportation&lt;/h2>
&lt;aside class="unity-concept" role="note" aria-labelledby="unity-concept-5">
&lt;p class="unity-concept__label">Key concept&lt;/p>
&lt;h4 id="unity-concept-5" class="unity-concept__title">Interactor, Interactable and Manager&lt;/h4>
&lt;div class="unity-concept__body">&lt;p>An &lt;strong>Interactor&lt;/strong> represents a way to act: a controller ray, nearby contact and so on. An &lt;strong>Interactable&lt;/strong> makes a target available: a teleport surface, a grabbable object or a selectable object. The &lt;strong>XR Interaction Manager&lt;/strong> coordinates their relationships.&lt;/p>
&lt;p>You need a target detectable through its &lt;strong>colliders&lt;/strong>, &lt;strong>compatible masks&lt;/strong> and a working &lt;strong>selection action&lt;/strong>. A visible ray does not guarantee these conditions are met.&lt;/p>
&lt;/div>
&lt;/aside>
&lt;ol>
&lt;li>In the rig&amp;rsquo;s &lt;strong>Locomotion&lt;/strong> group, locate &lt;strong>Locomotion Mediator&lt;/strong>, &lt;strong>XR Body Transformer&lt;/strong> and &lt;strong>Teleportation Provider&lt;/strong>. Check their references to the rig and mediator. Keep this XRI 3 structure: older tutorials using &lt;strong>Locomotion System&lt;/strong> do not describe this configuration.&lt;/li>
&lt;li>Add &lt;strong>Teleportation Area&lt;/strong> to &lt;code>Ground&lt;/code>. Assign the &lt;strong>XR Interaction Manager&lt;/strong> and the rig&amp;rsquo;s &lt;strong>Teleportation Provider&lt;/strong> to the corresponding fields. Check that the ground collider appears in &lt;strong>Colliders&lt;/strong>, or leave automatic discovery if it finds it correctly.&lt;/li>
&lt;li>Set its &lt;strong>Interaction Layer Mask&lt;/strong> to &lt;strong>Teleport&lt;/strong>. If this layer is missing, create it under &lt;strong>Project Settings &amp;gt; XR Plug-in Management &amp;gt; XR Interaction Toolkit &amp;gt; Interaction Layers&lt;/strong>, then use it on both the surface and teleport ray.&lt;/li>
&lt;li>On the rig&amp;rsquo;s controllers, find the dedicated teleport interactors. Their &lt;strong>Interaction Layer Mask&lt;/strong> must include &lt;strong>Teleport&lt;/strong>, and their &lt;strong>Raycast Mask&lt;/strong> must include the ground&amp;rsquo;s physics layer. Keep the sample&amp;rsquo;s preconfigured &lt;strong>Teleport Mode&lt;/strong> and confirmation actions. On each controller&amp;rsquo;s &lt;strong>Controller Input Action Manager&lt;/strong>, uncheck &lt;strong>Smooth Motion Enabled&lt;/strong> and &lt;strong>Smooth Turn Enabled&lt;/strong> to start with teleportation and snap turning. The prefab initially enables continuous movement on the left and teleportation on the right.&lt;/li>
&lt;li>In &lt;strong>XRI Default Input Actions&lt;/strong>, find the &lt;strong>XRI Left Locomotion&lt;/strong> and &lt;strong>XRI Right Locomotion&lt;/strong> maps. Read the &lt;strong>Teleport Mode&lt;/strong> and &lt;strong>Teleport Mode Cancel&lt;/strong> bindings. With the standard thumbstick controller bindings, push the thumbstick forward to aim, then release to confirm; check the actual binding and the surface&amp;rsquo;s &lt;strong>Teleport Trigger&lt;/strong> if behavior differs.&lt;/li>
&lt;li>Test three destinations on the ground. Also aim at the table: it should remain a surface you cannot teleport onto. Watch the XR Origin&amp;rsquo;s root Transform during the test.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Distinguish two masks:&lt;/strong> a GameObject&amp;rsquo;s physics &lt;strong>Layer&lt;/strong> and the &lt;strong>Raycast Mask&lt;/strong> filter Unity collisions/raycasts; the &lt;strong>Interaction Layer Mask&lt;/strong> filters XRI relationships. Creating a physics layer named Teleport does not create an XRI interaction layer. See &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/interaction-layers.html" target="_blank" rel="noopener">interaction layers&lt;/a>.&lt;/p>
&lt;p>&lt;strong>Check:&lt;/strong> a valid destination displays the ray&amp;rsquo;s visual feedback; confirming moves the rig onto the ground while preserving the headset&amp;rsquo;s tracked height. If the color does not change, check colliders and masks first, then actions.&lt;/p>
&lt;h3 id="compare-navigation-modes">Compare navigation modes&lt;/h3>
&lt;p>Locate &lt;strong>Snap Turn Provider&lt;/strong>, &lt;strong>Continuous Turn Provider&lt;/strong> and the continuous movement provider (&lt;strong>Dynamic Move Provider&lt;/strong> in the Starter Assets). Start with &lt;strong>teleportation and snap turning&lt;/strong>; use the controllers&amp;rsquo; &lt;strong>Controller Input Action Manager&lt;/strong> settings to select snap turning and disable continuous movement. Check that only one turning mode is active.&lt;/p>
&lt;p>Once the interaction works, briefly try continuous movement if you feel comfortable, then restore the initial configuration. &lt;strong>Explain:&lt;/strong> what moves the camera when you walk physically? What happens when you push the thumbstick? Which mode makes distances and orientation easiest to understand?&lt;/p>
&lt;p>Reference: &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/teleportation-area.html" target="_blank" rel="noopener">Teleportation Area&lt;/a>.&lt;/p>
&lt;h2 id="5-grab-and-throw-cubes">5. Grab and throw cubes&lt;/h2>
&lt;ol>
&lt;li>Add &lt;strong>XR Grab Interactable&lt;/strong> to a cube. Keep its &lt;strong>Box Collider&lt;/strong>, with Is Trigger disabled, and its &lt;strong>Rigidbody&lt;/strong>. Assign the manager. Keep &lt;strong>Interaction Layer Mask = Default&lt;/strong> for manipulable objects and check that the rig&amp;rsquo;s manipulation interactors accept this layer.&lt;/li>
&lt;li>Choose &lt;strong>Select Mode = Single&lt;/strong> and &lt;strong>Movement Type = Velocity Tracking&lt;/strong>. Leave &lt;strong>Throw On Detach&lt;/strong> enabled. This mode uses the Rigidbody during tracking and lets you start observing collisions; still avoid very fast movements through the table.&lt;/li>
&lt;li>Test grabbing at close range with the controller&amp;rsquo;s &lt;strong>grip&lt;/strong>, then releasing. Next, try at a distance using the rig&amp;rsquo;s manipulation ray. Inspect the &lt;strong>Select&lt;/strong> binding in &lt;strong>XRI Right Interaction&lt;/strong> or &lt;strong>XRI Left Interaction&lt;/strong> to confirm which button is used.&lt;/li>
&lt;li>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.&lt;/li>
&lt;li>Turn the cube into a &lt;code>GrabbableCube&lt;/code> prefab in &lt;code>Assets/Prefabs&lt;/code>. Equip the other cubes in the same way or replace them with instances. Keep teleportation objects on their dedicated interaction layer.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Check:&lt;/strong> 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 &lt;strong>Velocity Tracking&lt;/strong>, &lt;strong>Kinematic&lt;/strong> and &lt;strong>Instantaneous&lt;/strong>: observe responsiveness and contact with the table, then restore the initial mode.&lt;/p>
&lt;p>&lt;strong>Question:&lt;/strong> 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 &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/xr-grab-interactable.html" target="_blank" rel="noopener">XR Grab Interactable movement modes&lt;/a>.&lt;/p>
&lt;h2 id="6-spawn-a-cube-with-a-controller-action">6. Spawn a cube with a controller action&lt;/h2>
&lt;p>You used a keyboard or mouse action in the Unity lab. The principle remains the same: an &lt;strong>action&lt;/strong> represents an intention, and a &lt;strong>binding&lt;/strong> 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.&lt;/p>
&lt;h3 id="define-the-action">Define the action&lt;/h3>
&lt;ol>
&lt;li>In &lt;code>Assets&lt;/code>, create an &lt;code>Input&lt;/code> folder, then an asset through &lt;strong>Create &amp;gt; Input Actions&lt;/strong>, named &lt;code>VRLabActions&lt;/code>.&lt;/li>
&lt;li>Double-click it and create an &lt;strong>Action Map&lt;/strong> named &lt;code>Lab&lt;/code>, then a &lt;strong>Spawn&lt;/strong> action with &lt;strong>Action Type = Button&lt;/strong>.&lt;/li>
&lt;li>Add the binding &lt;strong>&lt;code>&amp;lt;XRController&amp;gt;{RightHand}/secondaryButton&lt;/code>&lt;/strong>: the right controller&amp;rsquo;s secondary button, usually &lt;strong>B&lt;/strong> on Quest. Keep the action triggered on press; do not use a Value or Pass Through pose action.&lt;/li>
&lt;li>Also add a &lt;strong>&lt;code>&amp;lt;Keyboard&amp;gt;/n&lt;/code>&lt;/strong> binding to check your script without a headset. Save the asset with &lt;strong>Save Asset&lt;/strong> if autosave is disabled.&lt;/li>
&lt;/ol>
&lt;h3 id="write-and-connect-objectspawn">Write and connect ObjectSpawn&lt;/h3>
&lt;p>Create &lt;code>ObjectSpawn.cs&lt;/code> using &lt;strong>Create &amp;gt; Scripting &amp;gt; MonoBehaviour Script&lt;/strong>, then attach it to an empty GameObject named &lt;code>CubeSpawner&lt;/code>. The file and class must have the same name.&lt;/p>
&lt;p>Expose these fields in the Inspector:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">UnityEngine&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">UnityEngine.InputSystem&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Fields to place inside the ObjectSpawn : MonoBehaviour class.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[SerializeField]&lt;/span> &lt;span class="k">private&lt;/span> &lt;span class="n">InputActionReference&lt;/span> &lt;span class="n">spawnAction&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[SerializeField]&lt;/span> &lt;span class="k">private&lt;/span> &lt;span class="n">GameObject&lt;/span> &lt;span class="n">cubePrefab&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[SerializeField]&lt;/span> &lt;span class="k">private&lt;/span> &lt;span class="n">Transform&lt;/span> &lt;span class="n">spawnPoint&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Your task:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>Create an empty GameObject named &lt;code>SpawnPoint&lt;/code> &lt;strong>at the scene root&lt;/strong>, position &lt;code>(0, 1.2, 2)&lt;/code>, rotation &lt;code>(0, 0, 0)&lt;/code>, scale &lt;code>(1, 1, 1)&lt;/code>. Check in Scene that it is above the tabletop. It is not a child of the table, so it does not inherit its scale.&lt;/li>
&lt;li>Assign &lt;strong>VRLabActions &amp;gt; Lab/Spawn&lt;/strong>, the &lt;strong>GrabbableCube&lt;/strong> prefab from Project and the &lt;strong>SpawnPoint&lt;/strong> Transform to the script&amp;rsquo;s three fields.&lt;/li>
&lt;li>In &lt;strong>OnEnable&lt;/strong>, subscribe a named method, such as &lt;code>SpawnCube(InputAction.CallbackContext context)&lt;/code>, to &lt;strong>&lt;code>spawnAction.action.performed&lt;/code>&lt;/strong>, then enable the action with &lt;strong>&lt;code>Enable()&lt;/code>&lt;/strong>.&lt;/li>
&lt;li>In &lt;strong>OnDisable&lt;/strong>, unsubscribe the same method with &lt;strong>&lt;code>-=&lt;/code>&lt;/strong>, then call &lt;strong>&lt;code>Disable()&lt;/code>&lt;/strong>. This component alone owns activation of this dedicated action. Do not call &lt;code>Disable()&lt;/code> this way on an action shared by the rig. For this exercise, do not add &lt;code>VRLabActions&lt;/code> to another Input Action Manager.&lt;/li>
&lt;li>In the callback, use &lt;strong>&lt;code>Instantiate(cubePrefab, spawnPoint.position, spawnPoint.rotation)&lt;/code>&lt;/strong>. Check references before using them and limit tests to a few cubes to avoid accumulating physical objects.&lt;/li>
&lt;/ol>
&lt;p>The snippets are guidance: &lt;strong>complete the class and methods yourself&lt;/strong>. As in the Unity lab, wait for compilation and fix the first Console error before entering Play.&lt;/p>
&lt;p>&lt;strong>Check:&lt;/strong> pressing N spawns a single cube above the table; holding it should not spawn a cube every frame. Disable and re-enable &lt;code>CubeSpawner&lt;/code> 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.&lt;/p>
&lt;p>&lt;strong>Explain:&lt;/strong> why use a named method so you can unsubscribe it? Why create a &lt;code>Spawn&lt;/code> action instead of reusing &lt;code>Select&lt;/code>? Reference: &lt;a href="https://docs.unity3d.com/Packages/com.unity.inputsystem@1.20/manual/Actions.html" target="_blank" rel="noopener">Input System actions&lt;/a>.&lt;/p>
&lt;h2 id="7-respond-to-hovering-and-selecting-an-object">7. Respond to hovering and selecting an object&lt;/h2>
&lt;p>Add a small colored marker to the table beside the cubes and keep it fixed. The goal is to &lt;strong>show hover feedback&lt;/strong>, then &lt;strong>persistently change its color with each selection&lt;/strong>.&lt;/p>
&lt;ol>
&lt;li>Create a cube named &lt;code>ColorTarget&lt;/code>, position &lt;code>(0.55, 0.95, 2)&lt;/code>, scale &lt;code>(0.2, 0.3, 0.2)&lt;/code>. Keep its Box Collider. Add &lt;strong>XR Simple Interactable&lt;/strong>, assign the manager, and set &lt;strong>Interaction Layer Mask = Default&lt;/strong>, &lt;strong>Select Mode = Single&lt;/strong>. Do not add XR Grab Interactable: this marker stays on the table.&lt;/li>
&lt;li>Create &lt;code>TargetFeedback.cs&lt;/code> and attach it to the marker. Expose a &lt;strong>Renderer&lt;/strong> reference, two persistent colors and a hover color. Assign the Renderer in the Inspector.&lt;/li>
&lt;li>Write three public methods without parameters: &lt;strong>&lt;code>BeginHover()&lt;/code>&lt;/strong>, &lt;strong>&lt;code>EndHover()&lt;/code>&lt;/strong> and &lt;strong>&lt;code>ToggleColor()&lt;/code>&lt;/strong>. 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.&lt;/li>
&lt;li>To isolate this marker&amp;rsquo;s color, you can obtain a material instance &lt;strong>once&lt;/strong> through &lt;code>targetRenderer.material&lt;/code> in &lt;strong>Awake&lt;/strong>, keep it, then modify its &lt;strong>color&lt;/strong> property. Destroy this instance in &lt;strong>OnDestroy&lt;/strong>. Modifying &lt;strong>sharedMaterial&lt;/strong> would also affect other objects using that asset; do not create a new instance every frame.&lt;/li>
&lt;li>Expand &lt;strong>Interactable Events&lt;/strong> on XR Simple Interactable. For each event, click &lt;strong>+&lt;/strong>, drag the &lt;code>ColorTarget&lt;/code> GameObject into the object field, then choose the appropriate &lt;strong>TargetFeedback&lt;/strong> method:&lt;/li>
&lt;/ol>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>XRI event&lt;/th>
&lt;th>Method to call&lt;/th>
&lt;th>Expected effect&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>First Hover Entered&lt;/strong>&lt;/td>
&lt;td>&lt;code>BeginHover()&lt;/code>&lt;/td>
&lt;td>Enable visual feedback on the first ray/contact.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Last Hover Exited&lt;/strong>&lt;/td>
&lt;td>&lt;code>EndHover()&lt;/code>&lt;/td>
&lt;td>Remove feedback after the last ray/contact leaves.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Select Entered&lt;/strong>&lt;/td>
&lt;td>&lt;code>ToggleColor()&lt;/code>&lt;/td>
&lt;td>Toggle the persistent color once per selection.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The &lt;strong>First/Last&lt;/strong> 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&amp;rsquo; &lt;strong>parameterless&lt;/strong> versions.&lt;/p>
&lt;p>&lt;strong>Check:&lt;/strong> point at the marker with one controller, select it using the &lt;strong>grip&lt;/strong> mapped to &lt;strong>Select&lt;/strong>, then point away: the new color should remain. Point at it with both controllers and move only one away: hover feedback should remain active.&lt;/p>
&lt;aside class="unity-concept" role="note" aria-labelledby="unity-concept-6">
&lt;p class="unity-concept__label">Key concept&lt;/p>
&lt;h4 id="unity-concept-6" class="unity-concept__title">Select and Activate are different actions&lt;/h4>
&lt;div class="unity-concept__body">&lt;p>&lt;strong>Hover&lt;/strong> means an interactor considers the target hovered. &lt;strong>Select&lt;/strong> establishes a selection, such as a grab. &lt;strong>Activate&lt;/strong> represents an additional action on a selected object, often mapped to the trigger in the rig&amp;rsquo;s bindings.&lt;/p>
&lt;p>In this exercise, “clicking” the marker means &lt;strong>Select&lt;/strong>: do not assume the trigger causes this event. Find the actual binding in the rig&amp;rsquo;s actions. The &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/xr-simple-interactable.html" target="_blank" rel="noopener">XR Simple Interactable&lt;/a> events are connected in the Inspector; your script describes the behavior they trigger.&lt;/p>
&lt;/div>
&lt;/aside>
&lt;h2 id="8-check-the-application-and-report-your-results">8. Check the application and report your results&lt;/h2>
&lt;p>Stop Play, save and disable the simulator. Rebuild the application and repeat the workflow on the target headset.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Test&lt;/th>
&lt;th>Expected result&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Turn and move your head&lt;/td>
&lt;td>Tracked view, consistent height and scale.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Move both controllers&lt;/td>
&lt;td>Visible poses and rays, without unexplained offsets.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Teleport onto the ground, then aim at the table&lt;/td>
&lt;td>Ground accepted, table rejected, height preserved.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Grab, place and throw a cube&lt;/td>
&lt;td>Manipulable object, contact with ground and table.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Press the Spawn button&lt;/td>
&lt;td>One cube per press, at the intended point, grabbable.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hover over and select the marker&lt;/td>
&lt;td>Temporary hover feedback, selected color preserved afterward.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Follow your session&amp;rsquo;s &lt;a href="../../tutorial/rapport-tp/">lab report guidelines&lt;/a>. Present the hardware and versions, a screenshot of the rig hierarchy, your scripts and the test results. Distinguish &lt;strong>simulator&lt;/strong>, &lt;strong>Play with a PC headset&lt;/strong> and &lt;strong>standalone APK&lt;/strong> tests; identify hardware tests you could not perform.&lt;/p>
&lt;p>In particular, answer these questions:&lt;/p>
&lt;ul>
&lt;li>What does the XR Origin do compared with the tracked camera?&lt;/li>
&lt;li>How does a physics layer differ from an XRI interaction layer?&lt;/li>
&lt;li>What connects a physical button to your cube spawning method?&lt;/li>
&lt;li>Why distinguish hovering, selection and activation?&lt;/li>
&lt;li>Which movement mode would you choose for a first visit to your scene, and why?&lt;/li>
&lt;/ul>
&lt;h3 id="further-exploration">Further exploration&lt;/h3>
&lt;ul>
&lt;li>Add a &lt;strong>Teleportation Anchor&lt;/strong> to reach a fixed observation point and compare it with a free teleport area.&lt;/li>
&lt;li>Add an &lt;strong>XR Socket Interactor&lt;/strong> to store a cube at a precise location.&lt;/li>
&lt;li>Add a &lt;strong>World Space&lt;/strong> interface with an &lt;strong>XR UI Input Module&lt;/strong> and a &lt;strong>Tracked Device Graphic Raycaster&lt;/strong>; use the ray to operate a reset button.&lt;/li>
&lt;li>Explore haptic feedback and check its effect in the headset, since the simulator cannot reproduce the sensation.&lt;/li>
&lt;li>Continue with the &lt;a href="../cybersickness-confort/">VR comfort lab&lt;/a> or the &lt;a href="../vr-engineering/">XR Engineering &amp;amp; Debugging lab&lt;/a> to structure and instrument a larger application.&lt;/li>
&lt;/ul>
&lt;h2 id="troubleshooting">Troubleshooting&lt;/h2>
&lt;p>Fix the first red &lt;strong>Console&lt;/strong> error first. When asking for help, always provide the step, active platform, versions and exact message.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Symptom&lt;/th>
&lt;th>Priority checks&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Camera does not move or has the wrong height&lt;/td>
&lt;td>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.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Headset detected over USB but no VR in Play&lt;/td>
&lt;td>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.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hands or rays do not respond&lt;/td>
&lt;td>Input System enabled, Input Action Manager and sample actions; suitable OpenXR profiles; controllers awake; no old FPS camera active.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Simulator does not respond&lt;/td>
&lt;td>Game has focus; only one simulator; preconfigured rig present; sample help and bindings; no competing headset session.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ray visible but no teleportation&lt;/td>
&lt;td>Ground collider, XRI Teleport layer on both sides, Raycast Mask, provider and mediator, Teleport Mode action, surface trigger mode.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cube cannot be grabbed or passes through objects&lt;/td>
&lt;td>Collider, Rigidbody, XR Grab Interactable, manager, manipulation masks and Select action; check Movement Type and gesture speed.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>N works but the headset button does not&lt;/td>
&lt;td>RightHand/secondaryButton binding, OpenXR profile, correct hand and APK rebuilt after changes.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>No cube or several cubes per press&lt;/td>
&lt;td>ObjectSpawn references, action enabled, single subscription and matching unsubscription; no instantiation in Update; no duplicate CubeSpawner.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>All objects change color&lt;/td>
&lt;td>The script probably modifies sharedMaterial; use a material instance kept for the marker.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>APK cannot be built or headset is missing&lt;/td>
&lt;td>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.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Old scene appears in the headset&lt;/td>
&lt;td>Active Scene List and profile, VRBasics saved, new Build And Run completed without errors.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="continue-exploring">Continue exploring&lt;/h3>
&lt;p>The &lt;a href="../3d-interaction/">3D interaction: comparing manipulation techniques&lt;/a> lab uses a timed construction task to compare direct grabbing, virtual rays, Near Far and two-handed interaction.&lt;/p>
&lt;h3 id="reference-documentation">Reference documentation&lt;/h3>
&lt;p>Documentation checked on &lt;strong>15 October 2026&lt;/strong>. Use the &lt;strong>3.3&lt;/strong> XRI branch to match the names in this lab.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/samples-starter-assets.html" target="_blank" rel="noopener">Starter Assets: rig, actions and components&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/xr-interaction-simulator-overview.html" target="_blank" rel="noopener">XR Interaction Simulator&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/xr-meta-quest-build-profile.html" target="_blank" rel="noopener">Meta Quest build profile in Unity 6.3&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.openxr@1.16/manual/features/metaquest.html" target="_blank" rel="noopener">OpenXR configuration for Meta Quest&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://docs.unity3d.com/Packages/com.unity.inputsystem@1.20/manual/Actions.html" target="_blank" rel="noopener">Input System actions&lt;/a>.&lt;/li>
&lt;/ul></description></item><item><title>Lab — Virtual reality engineering and debugging</title><link>https://www.etiennepeillard.com/teaching/lab/vr-engineering/</link><pubDate>Mon, 02 Mar 2026 00:00:00 +0000</pubDate><guid>https://www.etiennepeillard.com/teaching/lab/vr-engineering/</guid><description>&lt;h2 id="objective">Objective&lt;/h2>
&lt;p>This lab (~7 hours) aims to teach you how to &lt;strong>develop and maintain a VR application&lt;/strong> with:&lt;/p>
&lt;ul>
&lt;li>a &lt;strong>clear architecture&lt;/strong> (separation of responsibilities, explicit dependencies),&lt;/li>
&lt;li>an &lt;strong>efficient iteration workflow&lt;/strong> (test as much as possible without a headset, debug builds),&lt;/li>
&lt;li>&lt;strong>basic observability&lt;/strong> (VR overlay, structured logs),&lt;/li>
&lt;li>&lt;strong>XR debugging practices&lt;/strong>,&lt;/li>
&lt;li>an &lt;strong>introduction to VR profiling&lt;/strong> (optional, depending on the time remaining).&lt;/li>
&lt;/ul>
&lt;p>The application is a &lt;strong>VR solar system visualization workbench&lt;/strong>: a manipulable tabletop model, a world-space UI, viewing modes and time controls.&lt;/p>
&lt;hr>
&lt;h2 id="prerequisites">Prerequisites&lt;/h2>
&lt;h3 id="skills">Skills&lt;/h3>
&lt;ul>
&lt;li>C# / Unity basics (scenes, prefabs, scripts)&lt;/li>
&lt;li>Git basics (commit, tag, push)&lt;/li>
&lt;li>VR basics (comfort, world-space UI) are helpful but not essential&lt;/li>
&lt;/ul>
&lt;h3 id="tools">Tools&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Unity LTS&lt;/strong> (2022.3 or 2023 LTS)&lt;/li>
&lt;li>Unity packages:
&lt;ul>
&lt;li>&lt;strong>OpenXR&lt;/strong>&lt;/li>
&lt;li>&lt;strong>XR Interaction Toolkit (XRI)&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Input System&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>An OpenXR-compatible VR headset&lt;/li>
&lt;/ul>
&lt;h3 id="resources">Resources&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="PlanetData.cs">PlanetData.cs&lt;/a> (provides data and orbital calculation functions)&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="engineering-rules-required">Engineering rules (required)&lt;/h2>
&lt;p>In this lab, you must explicitly apply the following rules:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>No implicit dependencies&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>No &lt;code>FindObjectOfType&lt;/code>, no &lt;code>GameObject.Find&lt;/code>, no “magic” dependencies.&lt;/li>
&lt;li>Required references are injected through the Inspector or a &lt;em>bootstrapper&lt;/em>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>No business logic in &lt;code>Update()&lt;/code> by default&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Reserve &lt;code>Update()&lt;/code> for continuous operations that are &lt;em>actually necessary&lt;/em>.&lt;/li>
&lt;li>Prefer events, actions and controlled timers.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Separation of responsibilities&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Data models / services (calculations, time, conversion) ≠ views (GameObjects) ≠ controllers.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Debuggability&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Every critical behavior must produce useful logs.&lt;/li>
&lt;li>Implement a &lt;strong>DebugOverlay&lt;/strong> visible in the headset.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Performance&lt;/strong> &lt;em>(optional)&lt;/em>&lt;/p>
&lt;ul>
&lt;li>No avoidable per-frame allocations (strings/LINQ).&lt;/li>
&lt;li>Profiling is required (before/after evidence for one improvement).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="scenario-solar-system-vr-workbench">Scenario: Solar System VR Workbench&lt;/h2>
&lt;p>You will develop a VR application containing:&lt;/p>
&lt;ul>
&lt;li>a solar system tabletop model (with adjustable scale),&lt;/li>
&lt;li>planets positioned at a given time &lt;em>t&lt;/em> (calculation functions are already provided by &lt;code>PlanetData&lt;/code>),&lt;/li>
&lt;li>time controls (date + speed),&lt;/li>
&lt;li>orbits that can be shown or hidden,&lt;/li>
&lt;li>a “planet focus” mode (centering/inspection),&lt;/li>
&lt;li>a world-space VR UI to control the application,&lt;/li>
&lt;li>a debug overlay (FPS, state, recent events, warnings).&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="lab-structure">Lab structure&lt;/h2>
&lt;p>The lab consists of &lt;strong>5 main steps&lt;/strong> and &lt;strong>2 optional extensions&lt;/strong>.&lt;/p>
&lt;p>Required:&lt;/p>
&lt;p>1 — XR boot&lt;br>
2 — Architecture&lt;br>
3 — Events / simulation&lt;br>
4 — VR interactions&lt;br>
5 — UI + debug overlay&lt;/p>
&lt;p>Optional (if time allows):&lt;/p>
&lt;p>6 — Guided XR debugging&lt;br>
7 — Performance optimization&lt;/p>
&lt;hr>
&lt;h2 id="submission-organization-git">Submission organization (Git)&lt;/h2>
&lt;p>You must submit:&lt;/p>
&lt;ul>
&lt;li>a &lt;strong>Git repository&lt;/strong> (link),&lt;/li>
&lt;li>&lt;strong>Git tags&lt;/strong> for each step (v1.0, v1.1, … v2.0),&lt;/li>
&lt;li>a &lt;strong>short report&lt;/strong> (README or PDF):
&lt;ul>
&lt;li>screenshots (headset view or mirror),&lt;/li>
&lt;li>implemented features,&lt;/li>
&lt;li>architecture diagram (simple and readable),&lt;/li>
&lt;li>a “debug story” (if you completed step 6),&lt;/li>
&lt;li>a “perf story” (if you completed step 7).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="tag-reminder">Tag reminder&lt;/h3>
&lt;p>At each step:&lt;/p>
&lt;ul>
&lt;li>&lt;code>git add .&lt;/code>&lt;/li>
&lt;li>&lt;code>git commit -m &amp;quot;…&amp;quot;&lt;/code>&lt;/li>
&lt;li>&lt;code>git tag -a v1.x -m &amp;quot;…&amp;quot;&lt;/code>&lt;/li>
&lt;li>&lt;code>git push origin main&lt;/code>&lt;/li>
&lt;li>&lt;code>git push origin --tags&lt;/code>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="useful-concepts-for-this-lab">Useful concepts for this lab&lt;/h2>
&lt;p>Before starting, here are some important concepts we will use throughout the session. They are common in Unity projects and even more important in XR.&lt;/p>
&lt;h3 id="1--business-logic">1 — Business logic&lt;/h3>
&lt;p>&lt;strong>Business logic&lt;/strong> means the rules that define how your application works, independently of its visual presentation.&lt;/p>
&lt;p>Examples in this lab:&lt;/p>
&lt;p>Business logic:&lt;/p>
&lt;ul>
&lt;li>calculating a planet&amp;rsquo;s position at a given date&lt;/li>
&lt;li>changing the speed of time&lt;/li>
&lt;li>showing or hiding orbits&lt;/li>
&lt;li>changing the solar system&amp;rsquo;s scale&lt;/li>
&lt;/ul>
&lt;p>Display (not business logic):&lt;/p>
&lt;ul>
&lt;li>moving a GameObject&lt;/li>
&lt;li>changing UI text&lt;/li>
&lt;li>showing a panel&lt;/li>
&lt;/ul>
&lt;h6 id="bad-example">Bad example&lt;/h6>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">void&lt;/span> &lt;span class="n">Update&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">planet&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">transform&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">position&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">PlanetData&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ComputePosition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Now&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Calculation and display are mixed together.&lt;/p>
&lt;h6 id="good-example">Good example&lt;/h6>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">DateTime&lt;/span> &lt;span class="n">currentTime&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">void&lt;/span> &lt;span class="n">OnTimeChanged&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DateTime&lt;/span> &lt;span class="n">t&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">currentTime&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">t&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">UpdateView&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The logic decides &lt;strong>when&lt;/strong> to update; the view decides &lt;strong>how&lt;/strong> to display the result.&lt;/p>
&lt;h3 id="2--separation-of-responsibilities">2 — Separation of responsibilities&lt;/h3>
&lt;p>In a Unity project, it is very easy to put a lot of functionality in a single script. This works for small projects but quickly becomes difficult to maintain.&lt;/p>
&lt;p>We therefore separate the roles.&lt;/p>
&lt;h6 id="simple-structure-used-in-this-lab">Simple structure used in this lab&lt;/h6>
&lt;p>Data / Model ←→ Services ←→ Controllers ←→ Views&lt;/p>
&lt;h6 id="model-data">Model (data)&lt;/h6>
&lt;p>Contains only data.&lt;/p>
&lt;p>&lt;em>Example:&lt;/em>&lt;/p>
&lt;blockquote>
&lt;p>TimeModel&lt;/p>
&lt;ul>
&lt;li>currentTime&lt;/li>
&lt;li>timeSpeed&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;p>A model normally does not depend on Unity.&lt;/p>
&lt;h6 id="services">Services&lt;/h6>
&lt;p>Contain calculations or rules.&lt;/p>
&lt;p>&lt;em>Example:&lt;/em>&lt;/p>
&lt;blockquote>
&lt;p>PlanetEphemerisService&lt;/p>
&lt;ul>
&lt;li>computePosition()&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;h6 id="controllers">Controllers&lt;/h6>
&lt;p>Coordinate the application.&lt;/p>
&lt;p>They receive events and update views.&lt;/p>
&lt;p>&lt;em>Example:&lt;/em>&lt;/p>
&lt;blockquote>
&lt;p>SolarSystemController&lt;/p>
&lt;/blockquote>
&lt;h6 id="views">Views&lt;/h6>
&lt;p>Visible Unity objects.&lt;/p>
&lt;p>&lt;em>Examples:&lt;/em>&lt;/p>
&lt;blockquote>
&lt;ul>
&lt;li>PlanetView&lt;/li>
&lt;li>OrbitRenderer&lt;/li>
&lt;li>UI panels&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;h6 id="summary">Summary&lt;/h6>
&lt;ul>
&lt;li>Model → store state&lt;/li>
&lt;li>Service → calculate&lt;/li>
&lt;li>Controller → decide what to do&lt;/li>
&lt;li>View → display&lt;/li>
&lt;/ul>
&lt;h3 id="3--bootstrapper">3 — Bootstrapper&lt;/h3>
&lt;h6 id="the-problem">The problem&lt;/h6>
&lt;p>Many Unity projects contain:&lt;/p>
&lt;ul>
&lt;li>FindObjectOfType&lt;/li>
&lt;li>GameObject.Find&lt;/li>
&lt;li>Singletons used everywhere&lt;/li>
&lt;/ul>
&lt;p>These approaches work but make the project fragile.&lt;/p>
&lt;h6 id="a-simple-solution">A simple solution&lt;/h6>
&lt;p>Create an object that initializes the application.&lt;/p>
&lt;p>This is called a &lt;strong>Bootstrapper&lt;/strong>.&lt;/p>
&lt;p>Example:&lt;/p>
&lt;blockquote>
&lt;p>AppBootstrapper&lt;/p>
&lt;/blockquote>
&lt;p>Its role:&lt;/p>
&lt;ul>
&lt;li>create services&lt;/li>
&lt;li>connect objects&lt;/li>
&lt;li>initialize data&lt;/li>
&lt;/ul>
&lt;h6 id="simple-example">Simple example&lt;/h6>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">AppBootstrapper&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">MonoBehaviour&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="n">SolarSystemController&lt;/span> &lt;span class="n">controller&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">void&lt;/span> &lt;span class="n">Start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">timeModel&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">TimeModel&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">controller&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Init&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">timeModel&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Dependencies become visible and controlled.&lt;/p>
&lt;h3 id="4--events">4 — Events&lt;/h3>
&lt;p>Events allow different objects to communicate without being tightly coupled.&lt;/p>
&lt;p>Without events:&lt;/p>
&lt;p>UI → Manager → Controller → Camera&lt;/p>
&lt;p>Everything depends on everything else.&lt;/p>
&lt;p>With events:&lt;/p>
&lt;p>UI → Event → Interested objects&lt;/p>
&lt;p>Each system remains independent.&lt;/p>
&lt;h6 id="example">Example&lt;/h6>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">event&lt;/span> &lt;span class="n">Action&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">OnTimeChanged&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When the date changes:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">OnTimeChanged&lt;/span>&lt;span class="p">?.&lt;/span>&lt;span class="n">Invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">newTime&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Another script can listen for this event:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">void&lt;/span> &lt;span class="n">Start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">planetManager&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnTimeChanged&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">UpdatePosition&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="5--actions">5 — Actions&lt;/h3>
&lt;p>An &lt;strong>Action&lt;/strong> is simply a function stored in a variable.&lt;/p>
&lt;p>Example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">Action&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">callback&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In Unity, actions are used for:&lt;/p>
&lt;ul>
&lt;li>events&lt;/li>
&lt;li>callbacks&lt;/li>
&lt;li>UI interactions&lt;/li>
&lt;/ul>
&lt;h3 id="6--managing-simulation-state">6 — Managing simulation state&lt;/h3>
&lt;p>In this application, the central variable is not really a technical “timer”. What matters is simply &lt;strong>the simulation date&lt;/strong>.&lt;/p>
&lt;p>In other words, we manipulate a &lt;strong>global state&lt;/strong>:&lt;/p>
&lt;p>Simulated date → planet positions → display&lt;/p>
&lt;p>When this date changes:&lt;/p>
&lt;ul>
&lt;li>planet positions must be recalculated&lt;/li>
&lt;li>some visualizations must be updated&lt;/li>
&lt;li>the interface can display the new value&lt;/li>
&lt;/ul>
&lt;p>Instead of constantly recalculating in &lt;code>Update()&lt;/code>, trigger an &lt;strong>event only when the state changes&lt;/strong>.&lt;/p>
&lt;h4 id="bad-approach">Bad approach&lt;/h4>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">void&lt;/span> &lt;span class="n">Update&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">UpdatePlanets&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">currentDate&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Each object constantly checks whether something has changed.&lt;/p>
&lt;h4 id="recommended-approach">Recommended approach&lt;/h4>
&lt;p>Use an event to signal a state change.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">event&lt;/span> &lt;span class="n">Action&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">OnTimeChanged&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When the date changes:&lt;/p>
&lt;p>UI → PlanetManager → event → interested objects&lt;/p>
&lt;p>Example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">void&lt;/span> &lt;span class="n">Start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">PlanetManager&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">current&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnTimeChanged&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">UpdatePosition&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This provides:&lt;/p>
&lt;ul>
&lt;li>fewer unnecessary calculations&lt;/li>
&lt;li>more readable code&lt;/li>
&lt;li>easier debugging&lt;/li>
&lt;li>better VR performance&lt;/li>
&lt;/ul>
&lt;h3 id="7--why-this-matters-even-more-in-xr">7 — Why this matters even more in XR&lt;/h3>
&lt;p>In VR and XR:&lt;/p>
&lt;ul>
&lt;li>builds take longer&lt;/li>
&lt;li>bugs are harder to diagnose&lt;/li>
&lt;li>performance is critical&lt;/li>
&lt;/ul>
&lt;p>A clear architecture makes it possible to:&lt;/p>
&lt;ul>
&lt;li>test some parts without a headset&lt;/li>
&lt;li>understand a bug quickly&lt;/li>
&lt;li>modify an interaction without breaking the whole project&lt;/li>
&lt;/ul>
&lt;h3 id="8--scriptableobject">8 — ScriptableObject&lt;/h3>
&lt;p>A &lt;strong>ScriptableObject&lt;/strong> is a Unity asset used to store &lt;strong>editable parameters&lt;/strong>.&lt;/p>
&lt;p>Unlike a &lt;code>MonoBehaviour&lt;/code>, it is not attached to a GameObject.&lt;/p>
&lt;p>Example in this lab:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[CreateAssetMenu(menuName = &amp;#34;XR/Solar System Config&amp;#34;)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">SolarSystemConfig&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">ScriptableObject&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="kt">float&lt;/span> &lt;span class="n">distanceScale&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="kt">float&lt;/span> &lt;span class="n">planetSizeScale&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">showOrbits&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then create the asset in Unity:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Assets → Create → XR → Solar System Config
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Assign it in the Inspector.&lt;/p>
&lt;p>Benefits:&lt;/p>
&lt;ul>
&lt;li>centralized configuration&lt;/li>
&lt;li>editable without changing code&lt;/li>
&lt;li>reusable across scenes&lt;/li>
&lt;/ul>
&lt;h3 id="what-to-remember">What to remember&lt;/h3>
&lt;p>Throughout the lab, keep asking yourself:&lt;/p>
&lt;ul>
&lt;li>Does this script do too many things?&lt;/li>
&lt;li>Does this code depend on another object unnecessarily?&lt;/li>
&lt;li>Can I understand this system quickly?&lt;/li>
&lt;li>Could I easily debug this on a VR headset?&lt;/li>
&lt;/ul>
&lt;p>If the answer to either of the last two questions is “no”, you probably need to simplify or separate responsibilities more clearly.&lt;/p>
&lt;hr>
&lt;h2 id="step-1--xr-boot--minimal-scene-tag-v10--45-min">Step 1 — XR boot + minimal scene (Tag: v1.0) — ~45 min&lt;/h2>
&lt;h3 id="objectives">Objectives&lt;/h3>
&lt;ul>
&lt;li>Have a &lt;strong>working&lt;/strong> VR scene and a &lt;strong>debuggable&lt;/strong> build pipeline.&lt;/li>
&lt;/ul>
&lt;h3 id="tasks">Tasks&lt;/h3>
&lt;ol>
&lt;li>Create the Unity project.&lt;/li>
&lt;li>Install and enable:
&lt;ul>
&lt;li>OpenXR&lt;/li>
&lt;li>XR Interaction Toolkit
&lt;ul>
&lt;li>You can add the Starter Assets (for a complete XR rig) and the XR Simulator (to test without a headset) from the package samples.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Input System&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Configure the project:
&lt;ul>
&lt;li>Enable the plug-in providers required for your headset (OpenXR, Oculus, etc.) in Player Settings / XR Plug-in Management.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Add a VR rig (XRI):
&lt;ul>
&lt;li>XR Origin&lt;/li>
&lt;li>XR Interaction Manager&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Create a &lt;code>Boot&lt;/code> scene:
&lt;ul>
&lt;li>ground + reference frame (for example, X/Y/Z axes to show the world origin in the scene)&lt;/li>
&lt;li>an interactable cube (grab) to validate XRI&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Configure a &lt;strong>debug&lt;/strong> build:
&lt;ul>
&lt;li>Development Build&lt;/li>
&lt;li>Script Debugging&lt;/li>
&lt;li>(if possible) Autoconnect Profiler&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="expected-deliverables">Expected deliverables&lt;/h3>
&lt;ul>
&lt;li>Screenshot (headset mirror view or XR simulator) showing the cube being grabbed in VR&lt;/li>
&lt;li>Repository tagged &lt;code>v1.0-boot&lt;/code> (remember the &lt;a href="https://github.com/github/gitignore/blob/main/Unity.gitignore" target="_blank" rel="noopener">.gitignore&lt;/a>)&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="step-2--architecture-models-services-controllers">Step 2 — Architecture: models, services, controllers&lt;/h2>
&lt;p>Tag: &lt;strong>v1.1-architecture&lt;/strong> — ~1h15&lt;/p>
&lt;h3 id="objective-1">Objective&lt;/h3>
&lt;p>Before adding VR, interactions and UI, we will &lt;strong>structure the application properly&lt;/strong>.&lt;/p>
&lt;p>In XR projects, problems often come from:&lt;/p>
&lt;ul>
&lt;li>scripts referencing each other in every direction&lt;/li>
&lt;li>logic mixed with display code&lt;/li>
&lt;li>invisible dependencies&lt;/li>
&lt;li>code that cannot be tested without a headset&lt;/li>
&lt;/ul>
&lt;p>We will build a simple architecture:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Bootstrapper
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Models / Config
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Services
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Controllers
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Views (Unity)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="1--folder-organization">1 — Folder organization&lt;/h3>
&lt;p>Create the following folders:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Scripts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── Bootstrap
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── Models
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── Services
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── Controllers
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── Views
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── Config
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Purpose:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Folder&lt;/th>
&lt;th>Role&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Bootstrap&lt;/td>
&lt;td>entry point&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Models&lt;/td>
&lt;td>simulation state&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Services&lt;/td>
&lt;td>calculations&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Controllers&lt;/td>
&lt;td>coordination&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Views&lt;/td>
&lt;td>Unity GameObjects&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Config&lt;/td>
&lt;td>editable parameters&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="2--the-time-model">2 — The time model&lt;/h3>
&lt;p>In our simulation, the central variable is &lt;strong>simulated time&lt;/strong>.&lt;/p>
&lt;p>Create:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Scripts/Models/TimeModel.cs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">TimeModel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="n">DateTime&lt;/span> &lt;span class="n">CurrentTime&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">private&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="kt">float&lt;/span> &lt;span class="n">TimeScale&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">private&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">1f&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">IsPlaying&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">private&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">event&lt;/span> &lt;span class="n">Action&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">OnTimeChanged&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">SetTime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DateTime&lt;/span> &lt;span class="n">t&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">CurrentTime&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">t&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">OnTimeChanged&lt;/span>&lt;span class="p">?.&lt;/span>&lt;span class="n">Invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CurrentTime&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">SetScale&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">float&lt;/span> &lt;span class="n">scale&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">TimeScale&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">scale&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Play&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">IsPlaying&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Pause&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">IsPlaying&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Important:&lt;/p>
&lt;ul>
&lt;li>this script &lt;strong>does not depend on Unity&lt;/strong>&lt;/li>
&lt;li>it contains &lt;strong>only state and events&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;h3 id="3--solar-system-configuration">3 — Solar system configuration&lt;/h3>
&lt;p>Some parameters must be editable in Unity.&lt;/p>
&lt;p>Create:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Scripts/Config/SolarSystemConfig.cs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">UnityEngine&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[CreateAssetMenu(menuName = &amp;#34;XR/Solar System Config&amp;#34;)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">SolarSystemConfig&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">ScriptableObject&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="kt">float&lt;/span> &lt;span class="n">distanceScale&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0.000001f&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="kt">float&lt;/span> &lt;span class="n">planetSizeScale&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0.01f&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">showOrbits&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then create the asset:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Assets → Create → XR → Solar System Config
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="4--using-the-provided-astronomical-data">4 — Using the provided astronomical data&lt;/h3>
&lt;p>The lab already provides a class:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">PlanetData.cs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It contains:&lt;/p>
&lt;ul>
&lt;li>orbital parameters&lt;/li>
&lt;li>orbit calculations&lt;/li>
&lt;li>the following function:&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">PlanetData&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetPlanetPosition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">PlanetData&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Planet&lt;/span> &lt;span class="n">planet&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">DateTime&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>⚠️ This class is &lt;strong>the official source for planet positions&lt;/strong>.
Students &lt;strong>must not modify this file&lt;/strong>.&lt;/p>
&lt;h3 id="5--planet-position-service">5 — Planet position service&lt;/h3>
&lt;p>Even though the function already exists, we access it through a &lt;strong>service&lt;/strong>.&lt;/p>
&lt;p>Why?&lt;/p>
&lt;ul>
&lt;li>keep the architecture clean&lt;/li>
&lt;li>allow the implementation to be replaced later&lt;/li>
&lt;li>make testing easier&lt;/li>
&lt;/ul>
&lt;p>Create:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Scripts/Services/IPlanetEphemerisService.cs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">UnityEngine&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">interface&lt;/span> &lt;span class="nc">IPlanetEphemerisService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Vector3&lt;/span> &lt;span class="n">GetPlanetPosition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">PlanetData&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Planet&lt;/span> &lt;span class="n">planet&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">DateTime&lt;/span> &lt;span class="n">date&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Implementation:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Scripts/Services/PlanetEphemerisService.cs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">UnityEngine&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">PlanetEphemerisService&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">IPlanetEphemerisService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="n">Vector3&lt;/span> &lt;span class="n">GetPlanetPosition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">PlanetData&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Planet&lt;/span> &lt;span class="n">planet&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">DateTime&lt;/span> &lt;span class="n">date&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">PlanetData&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetPlanetPosition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">planet&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">date&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This service acts as an &lt;strong>abstraction layer&lt;/strong>.&lt;/p>
&lt;h3 id="6--planet-view">6 — Planet view&lt;/h3>
&lt;p>Create:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Scripts/Views/PlanetView.cs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">UnityEngine&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">PlanetView&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">MonoBehaviour&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="n">PlanetData&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Planet&lt;/span> &lt;span class="n">planet&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">SetPosition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Vector3&lt;/span> &lt;span class="n">pos&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">transform&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">localPosition&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">pos&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Role:&lt;/p>
&lt;ul>
&lt;li>represent a planet in the scene&lt;/li>
&lt;li>apply the calculated positions&lt;/li>
&lt;/ul>
&lt;h3 id="7--solar-system-controller">7 — Solar system controller&lt;/h3>
&lt;p>The controller connects:&lt;/p>
&lt;ul>
&lt;li>the model&lt;/li>
&lt;li>the service&lt;/li>
&lt;li>the views&lt;/li>
&lt;/ul>
&lt;p>Create:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Scripts/Controllers/PlanetSystemController.cs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">UnityEngine&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">PlanetSystemController&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">TimeModel&lt;/span> &lt;span class="n">timeModel&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">IPlanetEphemerisService&lt;/span> &lt;span class="n">ephemeris&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">PlanetView&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">planets&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="n">PlanetSystemController&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">TimeModel&lt;/span> &lt;span class="n">timeModel&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">IPlanetEphemerisService&lt;/span> &lt;span class="n">ephemeris&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">PlanetView&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">planets&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">timeModel&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">timeModel&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ephemeris&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">ephemeris&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">planets&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">planets&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">timeModel&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnTimeChanged&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">UpdatePlanets&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">void&lt;/span> &lt;span class="n">UpdatePlanets&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DateTime&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Debug&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[TIME] Updating planets &amp;#34;&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">planet&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">planets&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Vector3&lt;/span> &lt;span class="n">pos&lt;/span> &lt;span class="p">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ephemeris&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetPlanetPosition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">planet&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">planet&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">planet&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SetPosition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pos&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Responsibilities:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Element&lt;/th>
&lt;th>Role&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Model&lt;/td>
&lt;td>simulation state&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Service&lt;/td>
&lt;td>position calculation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Controller&lt;/td>
&lt;td>coordination&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>View&lt;/td>
&lt;td>display&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="8--bootstrapper">8 — Bootstrapper&lt;/h3>
&lt;p>Create:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Scripts/Bootstrap/AppBootstrapper.cs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">UnityEngine&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">AppBootstrapper&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">MonoBehaviour&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="n">SolarSystemConfig&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="n">PlanetView&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">planets&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">TimeModel&lt;/span> &lt;span class="n">timeModel&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">PlanetSystemController&lt;/span> &lt;span class="n">controller&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">void&lt;/span> &lt;span class="n">Start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Debug&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[BOOT] Initializing application&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">timeModel&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">TimeModel&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">ephemeris&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">PlanetEphemerisService&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">controller&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">PlanetSystemController&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">timeModel&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ephemeris&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">planets&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">timeModel&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SetTime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Now&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Role:&lt;/p>
&lt;ul>
&lt;li>create the main objects&lt;/li>
&lt;li>connect dependencies&lt;/li>
&lt;li>start the simulation&lt;/li>
&lt;/ul>
&lt;p>This is called the &lt;strong>composition root&lt;/strong>.&lt;/p>
&lt;h3 id="9--scene-setup">9 — Scene setup&lt;/h3>
&lt;p>Create a GameObject:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">App
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Add:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">AppBootstrapper
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In the Inspector:&lt;/p>
&lt;ul>
&lt;li>assign &lt;code>SolarSystemConfig&lt;/code>&lt;/li>
&lt;li>assign the &lt;code>PlanetView&lt;/code> references&lt;/li>
&lt;/ul>
&lt;p>Possible structure:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">App
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SolarSystem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├ Sun
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├ Mercury
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├ Venus
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├ Earth
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├ Mars
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├ Jupiter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├ Saturn
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├ Uranus
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └ Neptune
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Each planet has a:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">PlanetView
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="10--quick-test">10 — Quick test&lt;/h3>
&lt;p>Run the scene.&lt;/p>
&lt;p>Expected result:&lt;/p>
&lt;ul>
&lt;li>the planets appear&lt;/li>
&lt;li>they are positioned correctly&lt;/li>
&lt;li>a &lt;code>[BOOT]&lt;/code> log appears&lt;/li>
&lt;/ul>
&lt;p>If nothing happens, check that:&lt;/p>
&lt;ul>
&lt;li>the PlanetView references are assigned&lt;/li>
&lt;li>the bootstrapper is active&lt;/li>
&lt;li>the scene is saved&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>If you follow the steps carefully, you should notice a small bug. Modify the service to fix it.&lt;/p>
&lt;/blockquote>
&lt;h3 id="11--architecture-check">11 — Architecture check&lt;/h3>
&lt;p>Your code must meet these requirements:&lt;/p>
&lt;ul>
&lt;li>no &lt;code>FindObjectOfType&lt;/code>&lt;/li>
&lt;li>no &lt;code>GameObject.Find&lt;/code>&lt;/li>
&lt;li>visible dependencies&lt;/li>
&lt;li>a model independent of Unity&lt;/li>
&lt;li>logic separated from display&lt;/li>
&lt;/ul>
&lt;h3 id="12--architecture-diagram">12 — Architecture diagram&lt;/h3>
&lt;script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js">&lt;/script>
&lt;script>
document.addEventListener("DOMContentLoaded", () => {
const mermaidApi = window.mermaid;
if (!mermaidApi) return;
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
mermaidApi.initialize({
theme: isDark ? "dark" : "default",
logLevel: "fatal",
securityLevel: "loose",
startOnLoad: false
});
mermaidApi.run({ querySelector: ".mermaid" });
});
&lt;/script>
&lt;div class="mermaid">
flowchart TD
subgraph BOOT[Bootstrap]
AB[AppBootstrapper - MonoBehaviour]
end
subgraph MC[Models and Config]
TM[TimeModel - model]
SSC[SolarSystemConfig - ScriptableObject]
end
subgraph SVC[Services]
IES[IPlanetEphemerisService interface]
PES[PlanetEphemerisService implementation]
PD[PlanetData static data]
end
subgraph CTRL[Controllers]
PSC[PlanetSystemController controller]
end
subgraph VIEW[Views]
PV[PlanetView array MonoBehaviour]
GO[Planet GameObjects]
end
AB -->|creates| TM
AB -->|reads| SSC
AB -->|creates| PES
AB -->|injects| PSC
AB -->|assigns refs| PV
PES -.->|implements| IES
PSC -->|uses| IES
PSC -->|uses| TM
PSC -->|updates| PV
PES -->|calls GetPlanetPosition| PD
PV -->|attached to| GO
TM -->|OnTimeChanged| PSC
PSC -->|SetPosition| PV
&lt;/div>
&lt;script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js">&lt;/script>
&lt;script>
document.addEventListener("DOMContentLoaded", () => {
const mermaidApi = window.mermaid;
if (!mermaidApi) return;
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
mermaidApi.initialize({
theme: isDark ? "dark" : "default",
logLevel: "fatal",
securityLevel: "loose",
startOnLoad: false
});
mermaidApi.run({ querySelector: ".mermaid" });
});
&lt;/script>
&lt;div class="mermaid">
classDiagram
class AppBootstrapper {
+SolarSystemConfig config
+PlanetView[] planets
-TimeModel timeModel
-PlanetSystemController controller
+Start()
}
class TimeModel {
+DateTime CurrentTime
+float TimeScale
+bool IsPlaying
+OnTimeChanged : ActionDateTime
+SetTime(DateTime)
+SetScale(float)
+Play()
+Pause()
}
class SolarSystemConfig {
+float distanceScale
+float planetSizeScale
+bool showOrbits
}
class IPlanetEphemerisService {
+GetPlanetPosition(Planet, DateTime) Vector3
}
class PlanetEphemerisService {
+GetPlanetPosition(Planet, DateTime) Vector3
}
class PlanetData {
+GetPlanetPosition(Planet, DateTime) Vector3
}
class PlanetSystemController {
-TimeModel timeModel
-IPlanetEphemerisService ephemeris
-PlanetView[] planets
+PlanetSystemController(TimeModel, IPlanetEphemerisService, PlanetView[])
}
class PlanetView {
+Planet planet
+SetPosition(Vector3)
}
AppBootstrapper --> TimeModel : creates
AppBootstrapper --> PlanetEphemerisService : creates
AppBootstrapper --> PlanetSystemController : creates
AppBootstrapper --> PlanetView : references inspector
AppBootstrapper --> SolarSystemConfig : references asset
PlanetSystemController --> TimeModel : subscribes
PlanetSystemController --> IPlanetEphemerisService : uses
PlanetEphemerisService ..|> IPlanetEphemerisService : implements
PlanetEphemerisService --> PlanetData : calls
PlanetSystemController --> PlanetView : updates
&lt;/div>
&lt;h3 id="expected-result">Expected result&lt;/h3>
&lt;p>At the end of this step:&lt;/p>
&lt;ul>
&lt;li>the application is structured&lt;/li>
&lt;li>the planets use &lt;code>PlanetData&lt;/code>&lt;/li>
&lt;li>dependencies are explicit&lt;/li>
&lt;li>the simulation is driven by a model&lt;/li>
&lt;/ul>
&lt;p>Git tag:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">v1.1-architecture
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="step-3--planet-positioning--events">Step 3 — Planet positioning + events&lt;/h2>
&lt;p>Tag: &lt;strong>v1.2-events&lt;/strong> — ~1h15&lt;/p>
&lt;h3 id="objective-2">Objective&lt;/h3>
&lt;p>Implement the &lt;strong>main simulation flow&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>time changes,&lt;/li>
&lt;li>an event is triggered,&lt;/li>
&lt;li>the controller updates the planets.&lt;/li>
&lt;/ul>
&lt;p>We deliberately avoid running all the logic in &lt;code>Update()&lt;/code>.&lt;/p>
&lt;p>Expected flow:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">TimeController
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">TimeModel
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OnTimeChanged
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PlanetSystemController
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PlanetView
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We already created most of these elements in the previous step. Now we need to connect them properly using events.&lt;/p>
&lt;p>Responsibilities:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Element&lt;/th>
&lt;th>Role&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Model&lt;/td>
&lt;td>time state&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Service&lt;/td>
&lt;td>position calculation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Controller&lt;/td>
&lt;td>applies positions&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>View&lt;/td>
&lt;td>moves objects&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="1--trigger-updates-through-an-event">1 — Trigger updates through an event&lt;/h3>
&lt;blockquote>
&lt;p>This was already implemented in the previous step, but it is important to understand how it works.&lt;/p>
&lt;/blockquote>
&lt;p>In &lt;code>TimeModel&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">SetTime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DateTime&lt;/span> &lt;span class="n">t&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">CurrentTime&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">t&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">OnTimeChanged&lt;/span>&lt;span class="p">?.&lt;/span>&lt;span class="n">Invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CurrentTime&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In the controller:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">timeModel&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnTimeChanged&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">UpdatePlanets&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The planets therefore update &lt;strong>only when time changes&lt;/strong>.&lt;/p>
&lt;h3 id="2--advancing-time">2 — Advancing time&lt;/h3>
&lt;p>Create a small Unity component:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">TimeController
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">TimeController&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">MonoBehaviour&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="kt">float&lt;/span> &lt;span class="n">secondsPerDay&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">1f&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">TimeModel&lt;/span> &lt;span class="n">model&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">DateTime&lt;/span> &lt;span class="n">current&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Init&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">TimeModel&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">current&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">DateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Now&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SetTime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">current&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">void&lt;/span> &lt;span class="n">Update&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsPlaying&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">current&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">current&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddDays&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">deltaTime&lt;/span> &lt;span class="p">*&lt;/span> &lt;span class="n">secondsPerDay&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SetTime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">current&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In &lt;code>AppBootstrapper&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">timeController&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Init&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">timeModel&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3--verification">3 — Verification&lt;/h3>
&lt;p>When the scene starts, the Console should show:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">[BOOT] Initializing application
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[TIME] Updating planets ...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The planets should move.&lt;/p>
&lt;p>Checklist:&lt;/p>
&lt;ul>
&lt;li>no &lt;code>FindObjectOfType&lt;/code>&lt;/li>
&lt;li>no &lt;code>GameObject.Find&lt;/code>&lt;/li>
&lt;li>updates go through &lt;strong>OnTimeChanged&lt;/strong>&lt;/li>
&lt;li>the &lt;code>PlanetView&lt;/code> references are assigned&lt;/li>
&lt;/ul>
&lt;h3 id="optional-orbits">(Optional) Orbits&lt;/h3>
&lt;p>To display an orbit:&lt;/p>
&lt;ul>
&lt;li>add a &lt;strong>LineRenderer&lt;/strong>&lt;/li>
&lt;li>calculate several positions&lt;/li>
&lt;li>do not recalculate every frame&lt;/li>
&lt;/ul>
&lt;p>Principle:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">samples&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">t&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddDays&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span> &lt;span class="p">*&lt;/span> &lt;span class="m">10&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">points&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">ephemeris&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetPlanetPosition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">planet&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">t&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="expected-result-1">Expected result&lt;/h3>
&lt;p>At the end:&lt;/p>
&lt;ul>
&lt;li>the simulation is driven by &lt;code>TimeModel&lt;/code>&lt;/li>
&lt;li>the planets correctly follow the simulated time&lt;/li>
&lt;li>updates go through the &lt;code>OnTimeChanged&lt;/code> event&lt;/li>
&lt;li>the architecture remains clear and testable&lt;/li>
&lt;/ul>
&lt;p>Your &lt;strong>report&lt;/strong> must also include:&lt;/p>
&lt;ul>
&lt;li>a simple diagram of the &lt;code>TimeModel → Controller → PlanetView&lt;/code> flow&lt;/li>
&lt;li>a short explanation of how it works&lt;/li>
&lt;li>a screenshot of the application&lt;/li>
&lt;/ul>
&lt;p>Tag:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">v1.2-events
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="step-4--vr-interactions-grab--focus--scale">Step 4 — VR interactions: grab / focus / scale&lt;/h2>
&lt;p>Tag: &lt;strong>v1.3-vr-interactions&lt;/strong> — ~1h30&lt;/p>
&lt;h3 id="objective-3">Objective&lt;/h3>
&lt;p>In this step, you will turn your simulation into &lt;strong>an object that can be manipulated in VR&lt;/strong>.&lt;/p>
&lt;p>The user must be able to:&lt;/p>
&lt;ul>
&lt;li>move the solar system,&lt;/li>
&lt;li>change its scale,&lt;/li>
&lt;li>select a planet to obtain information.&lt;/li>
&lt;/ul>
&lt;p>The goal is to make these interactions work and &lt;strong>integrate them cleanly into the project architecture&lt;/strong>.&lt;/p>
&lt;p>XR interactions should trigger &lt;strong>intentions&lt;/strong>, which are then applied by a clearly defined system (controller / manager), rather than directly manipulating all the objects in the scene.&lt;/p>
&lt;p>Recommended flow:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">XR Interaction
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Interaction script
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Controller
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Views
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="1--prepare-a-manipulable-tabletop">1 — Prepare a manipulable tabletop&lt;/h3>
&lt;p>To make interactions easier, organize the scene around a manipulable root object.&lt;/p>
&lt;p>Create a structure similar to:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">SolarSystemRoot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├ SolarSystem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├ Sun
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├ Mercury
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├ Venus
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └ ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └ Handle
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>SolarSystemRoot&lt;/code> becomes the object you will:&lt;/p>
&lt;ul>
&lt;li>move&lt;/li>
&lt;li>rotate&lt;/li>
&lt;li>scale&lt;/li>
&lt;/ul>
&lt;p>All planets must be children of this object.&lt;/p>
&lt;p>Why?&lt;/p>
&lt;ul>
&lt;li>it makes interactions much simpler,&lt;/li>
&lt;li>it avoids modifying each planet individually,&lt;/li>
&lt;li>it provides a central point for controllers.&lt;/li>
&lt;/ul>
&lt;h3 id="2--grab-interaction">2 — Grab interaction&lt;/h3>
&lt;h4 id="objective-4">Objective&lt;/h4>
&lt;p>Allow the user to &lt;strong>grab the solar system and move it&lt;/strong> through space.&lt;/p>
&lt;h5 id="tasks-1">Tasks&lt;/h5>
&lt;p>Create an object to serve as a &lt;strong>manipulation handle&lt;/strong>.&lt;/p>
&lt;p>Add an XR interactable that allows the user to:&lt;/p>
&lt;ul>
&lt;li>grab the object with the controller,&lt;/li>
&lt;li>move the entire system.&lt;/li>
&lt;/ul>
&lt;p>Some necessary components:&lt;/p>
&lt;ul>
&lt;li>collider&lt;/li>
&lt;li>rigidbody (often kinematic)&lt;/li>
&lt;li>XR interaction component&lt;/li>
&lt;/ul>
&lt;p>Expected test:&lt;/p>
&lt;ul>
&lt;li>the user can grab the object,&lt;/li>
&lt;li>move it,&lt;/li>
&lt;li>release it.&lt;/li>
&lt;/ul>
&lt;h5 id="best-practices">Best practices&lt;/h5>
&lt;ul>
&lt;li>do not attach this interaction directly to the planets,&lt;/li>
&lt;li>keep a dedicated handle object,&lt;/li>
&lt;li>add useful logs:&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">[XR] Table grabbed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[XR] Table released
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3--scale-control">3 — Scale control&lt;/h3>
&lt;h4 id="objective-5">Objective&lt;/h4>
&lt;p>Allow the user to &lt;strong>zoom in / out&lt;/strong> on the solar system.&lt;/p>
&lt;p>The scale must:&lt;/p>
&lt;ul>
&lt;li>remain within reasonable limits,&lt;/li>
&lt;li>be centralized in a single script,&lt;/li>
&lt;li>produce useful logs.&lt;/li>
&lt;/ul>
&lt;h5 id="implementation">Implementation&lt;/h5>
&lt;p>Create a system responsible for:&lt;/p>
&lt;ul>
&lt;li>receiving a scale value,&lt;/li>
&lt;li>clamping it if necessary,&lt;/li>
&lt;li>applying the transformation to &lt;code>SolarSystemRoot&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>For example, you could create a dedicated component:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">ScaleController
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This component could expose a method such as:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">SetScale(float value)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This component is responsible for actually applying the scale.&lt;/p>
&lt;h5 id="user-interaction">User interaction&lt;/h5>
&lt;p>You are free to choose how to change the scale:&lt;/p>
&lt;ul>
&lt;li>a VR UI slider&lt;/li>
&lt;li>a + button&lt;/li>
&lt;li>a rotating knob&lt;/li>
&lt;li>a two-handed gesture (bonus)&lt;/li>
&lt;/ul>
&lt;p>In all cases:&lt;/p>
&lt;ul>
&lt;li>the interaction must not manipulate the transform directly&lt;/li>
&lt;li>it calls a controller&lt;/li>
&lt;/ul>
&lt;h5 id="expected-logs">Expected logs&lt;/h5>
&lt;p>Examples:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">[INPUT] Scale requested
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[XR] Scale applied
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[WARN] Scale clamped
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="4--selecting-a-planet">4 — Selecting a planet&lt;/h3>
&lt;h4 id="objective-6">Objective&lt;/h4>
&lt;p>Allow the user to &lt;strong>point at a planet with an XR ray&lt;/strong> and select it.&lt;/p>
&lt;p>When a planet is selected:&lt;/p>
&lt;ul>
&lt;li>an event must be triggered,&lt;/li>
&lt;li>the system must know &lt;strong>which planet is active&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;h5 id="setup">Setup&lt;/h5>
&lt;p>Each planet must be able to:&lt;/p>
&lt;ul>
&lt;li>be detected by a ray interactor,&lt;/li>
&lt;li>signal its selection.&lt;/li>
&lt;/ul>
&lt;p>For example, you could create a script such as:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">PlanetSelectable
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This script can:&lt;/p>
&lt;ul>
&lt;li>reference the &lt;code>PlanetView&lt;/code>,&lt;/li>
&lt;li>trigger an event or call a central system.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Important:&lt;/strong> the selection script must not contain all the focus or UI logic.&lt;/p>
&lt;p>It only signals that &lt;em>a planet has been selected&lt;/em>.&lt;/p>
&lt;h3 id="5--focus-mode">5 — Focus mode&lt;/h3>
&lt;h4 id="objective-7">Objective&lt;/h4>
&lt;p>When a planet is selected:&lt;/p>
&lt;ul>
&lt;li>the planet must be highlighted (for example, centered, enlarged, etc.),&lt;/li>
&lt;li>an information panel appears.&lt;/li>
&lt;/ul>
&lt;h5 id="implementation-1">Implementation&lt;/h5>
&lt;p>Create a system responsible for focus.&lt;/p>
&lt;p>For example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">FocusController
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Possible responsibilities:&lt;/p>
&lt;ul>
&lt;li>move the XR origin or camera,&lt;/li>
&lt;li>activate an information panel,&lt;/li>
&lt;li>record the active planet.&lt;/li>
&lt;/ul>
&lt;p>The panel could display:&lt;/p>
&lt;ul>
&lt;li>planet name&lt;/li>
&lt;li>distance from the Sun&lt;/li>
&lt;li>orbital period&lt;/li>
&lt;li>current simulated time&lt;/li>
&lt;/ul>
&lt;p>You can choose the exact content.&lt;/p>
&lt;h3 id="6--recommended-organization">6 — Recommended organization&lt;/h3>
&lt;p>One possible division of responsibilities:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Element&lt;/th>
&lt;th>Role&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>XR Interactable&lt;/td>
&lt;td>capture the interaction&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Interaction script&lt;/td>
&lt;td>translate the user&amp;rsquo;s action&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Controller&lt;/td>
&lt;td>apply the logic&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>View&lt;/td>
&lt;td>display the result&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Example flow:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Ray Interactor
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PlanetSelectable
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PlanetSelectionSystem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FocusController
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">UI
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="7--checks">7 — Checks&lt;/h3>
&lt;p>Before moving on to the next step, check:&lt;/p>
&lt;h5 id="grab">Grab&lt;/h5>
&lt;ul>
&lt;li>the solar system can be moved&lt;/li>
&lt;li>movement is stable&lt;/li>
&lt;li>logs appear&lt;/li>
&lt;/ul>
&lt;h5 id="scale">Scale&lt;/h5>
&lt;ul>
&lt;li>the scale can change&lt;/li>
&lt;li>limits are respected&lt;/li>
&lt;li>transformations remain consistent&lt;/li>
&lt;/ul>
&lt;h5 id="focus">Focus&lt;/h5>
&lt;ul>
&lt;li>ray selection works&lt;/li>
&lt;li>the correct planet is detected&lt;/li>
&lt;li>visual feedback appears&lt;/li>
&lt;/ul>
&lt;h3 id="expected-result-2">Expected result&lt;/h3>
&lt;p>At the end of this step, the user can:&lt;/p>
&lt;ul>
&lt;li>manipulate the solar system,&lt;/li>
&lt;li>change its scale,&lt;/li>
&lt;li>select a planet,&lt;/li>
&lt;li>see associated information.&lt;/li>
&lt;/ul>
&lt;p>The architecture must remain:&lt;/p>
&lt;ul>
&lt;li>readable,&lt;/li>
&lt;li>decoupled,&lt;/li>
&lt;li>debuggable.&lt;/li>
&lt;/ul>
&lt;p>Git tag:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">v1.3-vr-interactions
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="step-5--world-space-ui--instrumentation-tag-v14--1h00">Step 5 — World-space UI + instrumentation (Tag: v1.4) — ~1h00&lt;/h2>
&lt;h3 id="objectives-1">Objectives&lt;/h3>
&lt;p>Make the application controllable and debuggable &lt;em>inside the headset&lt;/em>.&lt;/p>
&lt;h3 id="tasks-2">Tasks&lt;/h3>
&lt;h4 id="a-world-space-ui">A) World-space UI&lt;/h4>
&lt;p>Create a VR panel with at least:&lt;/p>
&lt;ul>
&lt;li>Date (or scrubbing)&lt;/li>
&lt;li>Play/pause + speed (x1, x10, x100…)&lt;/li>
&lt;li>Orbit visibility toggle&lt;/li>
&lt;li>Reset viewpoint / reset scale&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>The UI panel can be an interactive world-space Canvas using the XR Interaction Toolkit.&lt;/p>
&lt;/blockquote>
&lt;h4 id="b-debugoverlay-required">B) DebugOverlay (required)&lt;/h4>
&lt;p>Create a discreet overlay displaying:&lt;/p>
&lt;ul>
&lt;li>FPS / frame time (approximate)&lt;/li>
&lt;li>Current date + speed&lt;/li>
&lt;li>Last user action&lt;/li>
&lt;li>Key warnings (scale out of bounds, suspected performance issues, event spam)&lt;/li>
&lt;/ul>
&lt;p>The DebugOverlay can be a simple TextMeshPro panel updated periodically.&lt;/p>
&lt;p>Unity allows you to capture all logs (Debug.Log, warnings, errors) through:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Application.logMessageReceived
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can use this callback to:&lt;/p>
&lt;ul>
&lt;li>capture important messages&lt;/li>
&lt;li>keep recent logs&lt;/li>
&lt;li>display them in the overlay&lt;/li>
&lt;/ul>
&lt;h5 id="example-1">Example&lt;/h5>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">UnityEngine&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">TMPro&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Collections.Generic&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">DebugOverlay&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">MonoBehaviour&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="n">TextMeshProUGUI&lt;/span> &lt;span class="n">text&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Queue&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">lines&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Queue&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">maxLines&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">15&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">void&lt;/span> &lt;span class="n">OnEnable&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Application&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">logMessageReceived&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">HandleLog&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">void&lt;/span> &lt;span class="n">OnDisable&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Application&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">logMessageReceived&lt;/span> &lt;span class="p">-=&lt;/span> &lt;span class="n">HandleLog&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">void&lt;/span> &lt;span class="n">HandleLog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">logString&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">stackTrace&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">LogType&lt;/span> &lt;span class="n">type&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lines&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Enqueue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">logString&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">lines&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Count&lt;/span> &lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">maxLines&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lines&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dequeue&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">text&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">text&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">lines&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="c-structured-logs">C) Structured logs&lt;/h4>
&lt;p>All critical events must produce logs in the following format:&lt;/p>
&lt;ul>
&lt;li>&lt;code>[TIME] …&lt;/code>&lt;/li>
&lt;li>&lt;code>[INPUT] …&lt;/code>&lt;/li>
&lt;li>&lt;code>[XR] …&lt;/code>&lt;/li>
&lt;li>&lt;code>[PERF] …&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="expected-deliverables-1">Expected deliverables&lt;/h3>
&lt;ul>
&lt;li>Screenshot showing the UI panel + debug overlay visible in VR&lt;/li>
&lt;li>Tag &lt;code>v1.4-ui-debug&lt;/code>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="step-6--guided-xr-debugging-tag-v15--optional">Step 6 — Guided XR debugging (Tag: v1.5) — (Optional)&lt;/h2>
&lt;h3 id="objectives-2">Objectives&lt;/h3>
&lt;p>Learn a reproducible XR debugging method.&lt;/p>
&lt;h3 id="required-work">Required work&lt;/h3>
&lt;p>You must address at least &lt;strong>2 tickets&lt;/strong> from the following 3 (depending on the starter project provided by the instructor):&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Ticket A — Input works in the Editor but not on the headset&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Ticket B — Events fire twice after reloading the scene&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Ticket C — Inconsistent scale/reference frames: objects appear “far away” or “huge”&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>For each ticket:&lt;/p>
&lt;ul>
&lt;li>provide a &lt;strong>minimal reproduction&lt;/strong>&lt;/li>
&lt;li>describe the &lt;strong>method&lt;/strong> (instrumentation → hypothesis → test → fix)&lt;/li>
&lt;li>add a simple regression test (for example, log + guard + assert)&lt;/li>
&lt;/ul>
&lt;h3 id="expected-deliverables-2">Expected deliverables&lt;/h3>
&lt;ul>
&lt;li>“Debug story” section in the README&lt;/li>
&lt;li>Tag &lt;code>v1.5-debug-tickets&lt;/code>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="step-7--xr-performance-tag-v20--optional">Step 7 — XR performance (Tag: v2.0) — (Optional)&lt;/h2>
&lt;h3 id="objectives-3">Objectives&lt;/h3>
&lt;p>Identify and fix a typical VR performance problem (CPU/GPU/GC).&lt;/p>
&lt;h3 id="tasks-3">Tasks&lt;/h3>
&lt;ol>
&lt;li>Measure (Profiler):
&lt;ul>
&lt;li>CPU main thread&lt;/li>
&lt;li>GC allocations&lt;/li>
&lt;li>scripts running in Update&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Choose &lt;strong>one bottleneck&lt;/strong> and improve it:
&lt;ul>
&lt;li>orbit caching&lt;/li>
&lt;li>pooling&lt;/li>
&lt;li>reducing update frequency&lt;/li>
&lt;li>eliminating allocations&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Demonstrate the improvement:
&lt;ul>
&lt;li>before/after profiler screenshots&lt;/li>
&lt;li>describe the change (2–5 lines)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="expected-deliverables-3">Expected deliverables&lt;/h3>
&lt;ul>
&lt;li>“Perf story” section in the README&lt;/li>
&lt;li>Tag &lt;code>v2.0-final&lt;/code>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="bonus-tasks-optional">Bonus tasks (optional)&lt;/h2>
&lt;ul>
&lt;li>Proper planet rotation (axial + orbital)&lt;/li>
&lt;li>Skybox / celestial sphere&lt;/li>
&lt;li>“Planet surface” mode (viewpoint, constellations)&lt;/li>
&lt;li>Teleportation + comfort (snap turn, vignette)&lt;/li>
&lt;li>Small in-VR “debug console” with category filters&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="assessment-criteria-indicative">Assessment criteria (indicative)&lt;/h2>
&lt;ul>
&lt;li>Architecture &amp;amp; code quality — 40%&lt;/li>
&lt;li>VR interactions — 30%&lt;/li>
&lt;li>Observability — 20%&lt;/li>
&lt;/ul>
&lt;p>Bonus:&lt;/p>
&lt;ul>
&lt;li>Debugging (tickets + method)&lt;/li>
&lt;li>Performance (measurement + improvement)&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="quick-checklist-before-submission">Quick checklist (before submission)&lt;/h2>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Public/private repository accessible, tags present&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Application works on the headset&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> World-space UI works&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> DebugOverlay works&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> README includes screenshots + diagrams&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> No &lt;code>FindObjectOfType&lt;/code> / &lt;code>GameObject.Find&lt;/code> in the final code&lt;/li>
&lt;/ul></description></item></channel></rss>